Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
Showing
55 changed files
with
1384 additions
and
240 deletions
| ... | @@ -23,6 +23,10 @@ import org.apache.felix.scr.annotations.ReferenceCardinality; | ... | @@ -23,6 +23,10 @@ import org.apache.felix.scr.annotations.ReferenceCardinality; |
| 23 | import org.onlab.onos.cluster.ClusterEvent; | 23 | import org.onlab.onos.cluster.ClusterEvent; |
| 24 | import org.onlab.onos.cluster.ClusterEventListener; | 24 | import org.onlab.onos.cluster.ClusterEventListener; |
| 25 | import org.onlab.onos.cluster.ClusterService; | 25 | import org.onlab.onos.cluster.ClusterService; |
| 26 | +import org.onlab.onos.cluster.NodeId; | ||
| 27 | +import org.onlab.onos.mastership.MastershipEvent; | ||
| 28 | +import org.onlab.onos.mastership.MastershipListener; | ||
| 29 | +import org.onlab.onos.mastership.MastershipService; | ||
| 26 | import org.onlab.onos.net.device.DeviceEvent; | 30 | import org.onlab.onos.net.device.DeviceEvent; |
| 27 | import org.onlab.onos.net.device.DeviceListener; | 31 | import org.onlab.onos.net.device.DeviceListener; |
| 28 | import org.onlab.onos.net.device.DeviceService; | 32 | import org.onlab.onos.net.device.DeviceService; |
| ... | @@ -50,15 +54,20 @@ public class FooComponent { | ... | @@ -50,15 +54,20 @@ public class FooComponent { |
| 50 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 54 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 51 | protected IntentService intentService; | 55 | protected IntentService intentService; |
| 52 | 56 | ||
| 57 | + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | ||
| 58 | + protected MastershipService mastershipService; | ||
| 59 | + | ||
| 53 | private final ClusterEventListener clusterListener = new InnerClusterListener(); | 60 | private final ClusterEventListener clusterListener = new InnerClusterListener(); |
| 54 | private final DeviceListener deviceListener = new InnerDeviceListener(); | 61 | private final DeviceListener deviceListener = new InnerDeviceListener(); |
| 55 | private final IntentListener intentListener = new InnerIntentListener(); | 62 | private final IntentListener intentListener = new InnerIntentListener(); |
| 63 | + private final MastershipListener mastershipListener = new InnerMastershipListener(); | ||
| 56 | 64 | ||
| 57 | @Activate | 65 | @Activate |
| 58 | public void activate() { | 66 | public void activate() { |
| 59 | clusterService.addListener(clusterListener); | 67 | clusterService.addListener(clusterListener); |
| 60 | deviceService.addListener(deviceListener); | 68 | deviceService.addListener(deviceListener); |
| 61 | intentService.addListener(intentListener); | 69 | intentService.addListener(intentListener); |
| 70 | + mastershipService.addListener(mastershipListener); | ||
| 62 | log.info("Started"); | 71 | log.info("Started"); |
| 63 | } | 72 | } |
| 64 | 73 | ||
| ... | @@ -67,6 +76,7 @@ public class FooComponent { | ... | @@ -67,6 +76,7 @@ public class FooComponent { |
| 67 | clusterService.removeListener(clusterListener); | 76 | clusterService.removeListener(clusterListener); |
| 68 | deviceService.removeListener(deviceListener); | 77 | deviceService.removeListener(deviceListener); |
| 69 | intentService.removeListener(intentListener); | 78 | intentService.removeListener(intentListener); |
| 79 | + mastershipService.removeListener(mastershipListener); | ||
| 70 | log.info("Stopped"); | 80 | log.info("Stopped"); |
| 71 | } | 81 | } |
| 72 | 82 | ||
| ... | @@ -100,6 +110,18 @@ public class FooComponent { | ... | @@ -100,6 +110,18 @@ public class FooComponent { |
| 100 | log.info(message, event.subject()); | 110 | log.info(message, event.subject()); |
| 101 | } | 111 | } |
| 102 | } | 112 | } |
| 113 | + | ||
| 114 | + private class InnerMastershipListener implements MastershipListener { | ||
| 115 | + @Override | ||
| 116 | + public void event(MastershipEvent event) { | ||
| 117 | + final NodeId myId = clusterService.getLocalNode().id(); | ||
| 118 | + if (myId.equals(event.roleInfo().master())) { | ||
| 119 | + log.info("I have control/I wish you luck {}", event); | ||
| 120 | + } else { | ||
| 121 | + log.info("you have control {}", event); | ||
| 122 | + } | ||
| 123 | + } | ||
| 124 | + } | ||
| 103 | } | 125 | } |
| 104 | 126 | ||
| 105 | 127 | ... | ... |
| ... | @@ -15,14 +15,16 @@ | ... | @@ -15,14 +15,16 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onlab.onos.cli.net; | 16 | package org.onlab.onos.cli.net; |
| 17 | 17 | ||
| 18 | +import java.util.List; | ||
| 19 | + | ||
| 18 | import org.apache.karaf.shell.commands.Argument; | 20 | import org.apache.karaf.shell.commands.Argument; |
| 19 | import org.apache.karaf.shell.commands.Command; | 21 | import org.apache.karaf.shell.commands.Command; |
| 20 | -import org.onlab.onos.cli.AbstractShellCommand; | ||
| 21 | import org.onlab.onos.net.HostId; | 22 | import org.onlab.onos.net.HostId; |
| 22 | import org.onlab.onos.net.flow.DefaultTrafficSelector; | 23 | import org.onlab.onos.net.flow.DefaultTrafficSelector; |
| 23 | import org.onlab.onos.net.flow.DefaultTrafficTreatment; | 24 | import org.onlab.onos.net.flow.DefaultTrafficTreatment; |
| 24 | import org.onlab.onos.net.flow.TrafficSelector; | 25 | import org.onlab.onos.net.flow.TrafficSelector; |
| 25 | import org.onlab.onos.net.flow.TrafficTreatment; | 26 | import org.onlab.onos.net.flow.TrafficTreatment; |
| 27 | +import org.onlab.onos.net.intent.Constraint; | ||
| 26 | import org.onlab.onos.net.intent.HostToHostIntent; | 28 | import org.onlab.onos.net.intent.HostToHostIntent; |
| 27 | import org.onlab.onos.net.intent.IntentService; | 29 | import org.onlab.onos.net.intent.IntentService; |
| 28 | 30 | ||
| ... | @@ -31,7 +33,7 @@ import org.onlab.onos.net.intent.IntentService; | ... | @@ -31,7 +33,7 @@ import org.onlab.onos.net.intent.IntentService; |
| 31 | */ | 33 | */ |
| 32 | @Command(scope = "onos", name = "add-host-intent", | 34 | @Command(scope = "onos", name = "add-host-intent", |
| 33 | description = "Installs host-to-host connectivity intent") | 35 | description = "Installs host-to-host connectivity intent") |
| 34 | -public class AddHostToHostIntentCommand extends AbstractShellCommand { | 36 | +public class AddHostToHostIntentCommand extends ConnectivityIntentCommand { |
| 35 | 37 | ||
| 36 | @Argument(index = 0, name = "one", description = "One host ID", | 38 | @Argument(index = 0, name = "one", description = "One host ID", |
| 37 | required = true, multiValued = false) | 39 | required = true, multiValued = false) |
| ... | @@ -50,9 +52,11 @@ public class AddHostToHostIntentCommand extends AbstractShellCommand { | ... | @@ -50,9 +52,11 @@ public class AddHostToHostIntentCommand extends AbstractShellCommand { |
| 50 | 52 | ||
| 51 | TrafficSelector selector = DefaultTrafficSelector.builder().build(); | 53 | TrafficSelector selector = DefaultTrafficSelector.builder().build(); |
| 52 | TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); | 54 | TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); |
| 55 | + List<Constraint> constraints = buildConstraints(); | ||
| 53 | 56 | ||
| 54 | HostToHostIntent intent = new HostToHostIntent(appId(), oneId, twoId, | 57 | HostToHostIntent intent = new HostToHostIntent(appId(), oneId, twoId, |
| 55 | - selector, treatment); | 58 | + selector, treatment, |
| 59 | + constraints); | ||
| 56 | service.submit(intent); | 60 | service.submit(intent); |
| 57 | } | 61 | } |
| 58 | 62 | ... | ... |
| ... | @@ -23,11 +23,13 @@ import org.onlab.onos.net.PortNumber; | ... | @@ -23,11 +23,13 @@ import org.onlab.onos.net.PortNumber; |
| 23 | import org.onlab.onos.net.flow.DefaultTrafficTreatment; | 23 | import org.onlab.onos.net.flow.DefaultTrafficTreatment; |
| 24 | import org.onlab.onos.net.flow.TrafficSelector; | 24 | import org.onlab.onos.net.flow.TrafficSelector; |
| 25 | import org.onlab.onos.net.flow.TrafficTreatment; | 25 | import org.onlab.onos.net.flow.TrafficTreatment; |
| 26 | +import org.onlab.onos.net.intent.Constraint; | ||
| 26 | import org.onlab.onos.net.intent.Intent; | 27 | import org.onlab.onos.net.intent.Intent; |
| 27 | import org.onlab.onos.net.intent.IntentService; | 28 | import org.onlab.onos.net.intent.IntentService; |
| 28 | import org.onlab.onos.net.intent.MultiPointToSinglePointIntent; | 29 | import org.onlab.onos.net.intent.MultiPointToSinglePointIntent; |
| 29 | 30 | ||
| 30 | import java.util.HashSet; | 31 | import java.util.HashSet; |
| 32 | +import java.util.List; | ||
| 31 | import java.util.Set; | 33 | import java.util.Set; |
| 32 | 34 | ||
| 33 | import static org.onlab.onos.net.DeviceId.deviceId; | 35 | import static org.onlab.onos.net.DeviceId.deviceId; |
| ... | @@ -69,9 +71,11 @@ public class AddMultiPointToSinglePointIntentCommand extends ConnectivityIntentC | ... | @@ -69,9 +71,11 @@ public class AddMultiPointToSinglePointIntentCommand extends ConnectivityIntentC |
| 69 | 71 | ||
| 70 | TrafficSelector selector = buildTrafficSelector(); | 72 | TrafficSelector selector = buildTrafficSelector(); |
| 71 | TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); | 73 | TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); |
| 74 | + List<Constraint> constraints = buildConstraints(); | ||
| 72 | 75 | ||
| 73 | Intent intent = new MultiPointToSinglePointIntent(appId(), selector, treatment, | 76 | Intent intent = new MultiPointToSinglePointIntent(appId(), selector, treatment, |
| 74 | - ingressPoints, egress); | 77 | + ingressPoints, egress, |
| 78 | + constraints); | ||
| 75 | service.submit(intent); | 79 | service.submit(intent); |
| 76 | } | 80 | } |
| 77 | 81 | ... | ... |
| ... | @@ -15,6 +15,8 @@ | ... | @@ -15,6 +15,8 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onlab.onos.cli.net; | 16 | package org.onlab.onos.cli.net; |
| 17 | 17 | ||
| 18 | +import java.util.List; | ||
| 19 | + | ||
| 18 | import org.apache.karaf.shell.commands.Argument; | 20 | import org.apache.karaf.shell.commands.Argument; |
| 19 | import org.apache.karaf.shell.commands.Command; | 21 | import org.apache.karaf.shell.commands.Command; |
| 20 | import org.onlab.onos.net.ConnectPoint; | 22 | import org.onlab.onos.net.ConnectPoint; |
| ... | @@ -22,6 +24,7 @@ import org.onlab.onos.net.DeviceId; | ... | @@ -22,6 +24,7 @@ import org.onlab.onos.net.DeviceId; |
| 22 | import org.onlab.onos.net.PortNumber; | 24 | import org.onlab.onos.net.PortNumber; |
| 23 | import org.onlab.onos.net.flow.TrafficSelector; | 25 | import org.onlab.onos.net.flow.TrafficSelector; |
| 24 | import org.onlab.onos.net.flow.TrafficTreatment; | 26 | import org.onlab.onos.net.flow.TrafficTreatment; |
| 27 | +import org.onlab.onos.net.intent.Constraint; | ||
| 25 | import org.onlab.onos.net.intent.Intent; | 28 | import org.onlab.onos.net.intent.Intent; |
| 26 | import org.onlab.onos.net.intent.IntentService; | 29 | import org.onlab.onos.net.intent.IntentService; |
| 27 | import org.onlab.onos.net.intent.PointToPointIntent; | 30 | import org.onlab.onos.net.intent.PointToPointIntent; |
| ... | @@ -63,8 +66,10 @@ public class AddPointToPointIntentCommand extends ConnectivityIntentCommand { | ... | @@ -63,8 +66,10 @@ public class AddPointToPointIntentCommand extends ConnectivityIntentCommand { |
| 63 | TrafficSelector selector = buildTrafficSelector(); | 66 | TrafficSelector selector = buildTrafficSelector(); |
| 64 | TrafficTreatment treatment = builder().build(); | 67 | TrafficTreatment treatment = builder().build(); |
| 65 | 68 | ||
| 69 | + List<Constraint> constraints = buildConstraints(); | ||
| 70 | + | ||
| 66 | Intent intent = new PointToPointIntent(appId(), selector, treatment, | 71 | Intent intent = new PointToPointIntent(appId(), selector, treatment, |
| 67 | - ingress, egress); | 72 | + ingress, egress, constraints); |
| 68 | service.submit(intent); | 73 | service.submit(intent); |
| 69 | } | 74 | } |
| 70 | 75 | ... | ... |
| 1 | -/* | ||
| 2 | - * Licensed to the Apache Software Foundation (ASF) under one | ||
| 3 | - * or more contributor license agreements. See the NOTICE file | ||
| 4 | - * distributed with this work for additional information | ||
| 5 | - * regarding copyright ownership. The ASF licenses this file | ||
| 6 | - * to you under the Apache License, Version 2.0 (the | ||
| 7 | - * "License"); you may not use this file except in compliance | ||
| 8 | - * with the License. You may obtain a copy of the License at | ||
| 9 | - * | ||
| 10 | - * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | - * | ||
| 12 | - * Unless required by applicable law or agreed to in writing, | ||
| 13 | - * software distributed under the License is distributed on an | ||
| 14 | - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| 15 | - * KIND, either express or implied. See the License for the | ||
| 16 | - * specific language governing permissions and limitations | ||
| 17 | - * under the License. | ||
| 18 | - */ | ||
| 19 | -package org.onlab.onos.cli.net; | ||
| 20 | - | ||
| 21 | -import org.apache.karaf.shell.commands.Argument; | ||
| 22 | -import org.apache.karaf.shell.commands.Command; | ||
| 23 | -import org.onlab.onos.net.ConnectPoint; | ||
| 24 | -import org.onlab.onos.net.DeviceId; | ||
| 25 | -import org.onlab.onos.net.PortNumber; | ||
| 26 | -import org.onlab.onos.net.flow.TrafficSelector; | ||
| 27 | -import org.onlab.onos.net.flow.TrafficTreatment; | ||
| 28 | -import org.onlab.onos.net.intent.Intent; | ||
| 29 | -import org.onlab.onos.net.intent.IntentService; | ||
| 30 | -import org.onlab.onos.net.intent.PointToPointIntent; | ||
| 31 | - | ||
| 32 | -import static org.onlab.onos.net.DeviceId.deviceId; | ||
| 33 | -import static org.onlab.onos.net.PortNumber.portNumber; | ||
| 34 | -import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder; | ||
| 35 | - | ||
| 36 | -/** | ||
| 37 | - * Installs point-to-point connectivity intents. | ||
| 38 | - */ | ||
| 39 | -@Command(scope = "onos", name = "add-point-intent-bw", | ||
| 40 | - description = "Installs point-to-point connectivity intent with bandwidth constraint") | ||
| 41 | -public class AddPointToPointIntentWithBandwidthConstraintCommand extends ConnectivityIntentCommand { | ||
| 42 | - | ||
| 43 | - @Argument(index = 0, name = "ingressDevice", | ||
| 44 | - description = "Ingress Device/Port Description", | ||
| 45 | - required = true, multiValued = false) | ||
| 46 | - String ingressDeviceString = null; | ||
| 47 | - | ||
| 48 | - @Argument(index = 1, name = "egressDevice", | ||
| 49 | - description = "Egress Device/Port Description", | ||
| 50 | - required = true, multiValued = false) | ||
| 51 | - String egressDeviceString = null; | ||
| 52 | - | ||
| 53 | - @Argument(index = 2, name = "bandwidth", | ||
| 54 | - description = "Bandwidth", | ||
| 55 | - required = true, multiValued = false) | ||
| 56 | - String bandwidthString = null; | ||
| 57 | - | ||
| 58 | - @Override | ||
| 59 | - protected void execute() { | ||
| 60 | - IntentService service = get(IntentService.class); | ||
| 61 | - | ||
| 62 | - DeviceId ingressDeviceId = deviceId(getDeviceId(ingressDeviceString)); | ||
| 63 | - PortNumber ingressPortNumber = portNumber(getPortNumber(ingressDeviceString)); | ||
| 64 | - ConnectPoint ingress = new ConnectPoint(ingressDeviceId, ingressPortNumber); | ||
| 65 | - | ||
| 66 | - DeviceId egressDeviceId = deviceId(getDeviceId(egressDeviceString)); | ||
| 67 | - PortNumber egressPortNumber = portNumber(getPortNumber(egressDeviceString)); | ||
| 68 | - ConnectPoint egress = new ConnectPoint(egressDeviceId, egressPortNumber); | ||
| 69 | - | ||
| 70 | - long bandwidth = Long.parseLong(bandwidthString); | ||
| 71 | - | ||
| 72 | - TrafficSelector selector = buildTrafficSelector(); | ||
| 73 | - TrafficTreatment treatment = builder().build(); | ||
| 74 | - | ||
| 75 | - // FIXME: add bandwitdh constraint | ||
| 76 | - Intent intent = new PointToPointIntent( | ||
| 77 | - appId(), selector, treatment, | ||
| 78 | - ingress, egress); | ||
| 79 | - service.submit(intent); | ||
| 80 | - } | ||
| 81 | - | ||
| 82 | - /** | ||
| 83 | - * Extracts the port number portion of the ConnectPoint. | ||
| 84 | - * | ||
| 85 | - * @param deviceString string representing the device/port | ||
| 86 | - * @return port number as a string, empty string if the port is not found | ||
| 87 | - */ | ||
| 88 | - private String getPortNumber(String deviceString) { | ||
| 89 | - int slash = deviceString.indexOf('/'); | ||
| 90 | - if (slash <= 0) { | ||
| 91 | - return ""; | ||
| 92 | - } | ||
| 93 | - return deviceString.substring(slash + 1, deviceString.length()); | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | - /** | ||
| 97 | - * Extracts the device ID portion of the ConnectPoint. | ||
| 98 | - * | ||
| 99 | - * @param deviceString string representing the device/port | ||
| 100 | - * @return device ID string | ||
| 101 | - */ | ||
| 102 | - private String getDeviceId(String deviceString) { | ||
| 103 | - int slash = deviceString.indexOf('/'); | ||
| 104 | - if (slash <= 0) { | ||
| 105 | - return ""; | ||
| 106 | - } | ||
| 107 | - return deviceString.substring(0, slash); | ||
| 108 | - } | ||
| 109 | -} |
| ... | @@ -15,14 +15,21 @@ | ... | @@ -15,14 +15,21 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onlab.onos.cli.net; | 16 | package org.onlab.onos.cli.net; |
| 17 | 17 | ||
| 18 | +import java.util.LinkedList; | ||
| 19 | +import java.util.List; | ||
| 20 | + | ||
| 18 | import org.apache.karaf.shell.commands.Option; | 21 | import org.apache.karaf.shell.commands.Option; |
| 19 | import org.onlab.onos.cli.AbstractShellCommand; | 22 | import org.onlab.onos.cli.AbstractShellCommand; |
| 20 | import org.onlab.onos.net.flow.DefaultTrafficSelector; | 23 | import org.onlab.onos.net.flow.DefaultTrafficSelector; |
| 21 | import org.onlab.onos.net.flow.TrafficSelector; | 24 | import org.onlab.onos.net.flow.TrafficSelector; |
| 25 | +import org.onlab.onos.net.intent.Constraint; | ||
| 26 | +import org.onlab.onos.net.intent.constraint.BandwidthConstraint; | ||
| 27 | +import org.onlab.onos.net.intent.constraint.LambdaConstraint; | ||
| 28 | +import org.onlab.onos.net.resource.Bandwidth; | ||
| 22 | import org.onlab.packet.Ethernet; | 29 | import org.onlab.packet.Ethernet; |
| 23 | import org.onlab.packet.MacAddress; | 30 | import org.onlab.packet.MacAddress; |
| 24 | 31 | ||
| 25 | -import com.google.common.base.Strings; | 32 | +import static com.google.common.base.Strings.isNullOrEmpty; |
| 26 | 33 | ||
| 27 | /** | 34 | /** |
| 28 | * Base class for command line operations for connectivity based intents. | 35 | * Base class for command line operations for connectivity based intents. |
| ... | @@ -41,6 +48,14 @@ public abstract class ConnectivityIntentCommand extends AbstractShellCommand { | ... | @@ -41,6 +48,14 @@ public abstract class ConnectivityIntentCommand extends AbstractShellCommand { |
| 41 | required = false, multiValued = false) | 48 | required = false, multiValued = false) |
| 42 | private String ethTypeString = ""; | 49 | private String ethTypeString = ""; |
| 43 | 50 | ||
| 51 | + @Option(name = "-b", aliases = "--bandwidth", description = "Bandwidth", | ||
| 52 | + required = false, multiValued = false) | ||
| 53 | + private String bandwidthString = ""; | ||
| 54 | + | ||
| 55 | + @Option(name = "-l", aliases = "--lambda", description = "Lambda", | ||
| 56 | + required = false, multiValued = false) | ||
| 57 | + private boolean lambda = false; | ||
| 58 | + | ||
| 44 | /** | 59 | /** |
| 45 | * Constructs a traffic selector based on the command line arguments | 60 | * Constructs a traffic selector based on the command line arguments |
| 46 | * presented to the command. | 61 | * presented to the command. |
| ... | @@ -50,21 +65,43 @@ public abstract class ConnectivityIntentCommand extends AbstractShellCommand { | ... | @@ -50,21 +65,43 @@ public abstract class ConnectivityIntentCommand extends AbstractShellCommand { |
| 50 | TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); | 65 | TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); |
| 51 | Short ethType = Ethernet.TYPE_IPV4; | 66 | Short ethType = Ethernet.TYPE_IPV4; |
| 52 | 67 | ||
| 53 | - if (!Strings.isNullOrEmpty(ethTypeString)) { | 68 | + if (!isNullOrEmpty(ethTypeString)) { |
| 54 | EthType ethTypeParameter = EthType.valueOf(ethTypeString); | 69 | EthType ethTypeParameter = EthType.valueOf(ethTypeString); |
| 55 | ethType = ethTypeParameter.value(); | 70 | ethType = ethTypeParameter.value(); |
| 56 | } | 71 | } |
| 57 | selectorBuilder.matchEthType(ethType); | 72 | selectorBuilder.matchEthType(ethType); |
| 58 | 73 | ||
| 59 | - if (!Strings.isNullOrEmpty(srcMacString)) { | 74 | + if (!isNullOrEmpty(srcMacString)) { |
| 60 | selectorBuilder.matchEthSrc(MacAddress.valueOf(srcMacString)); | 75 | selectorBuilder.matchEthSrc(MacAddress.valueOf(srcMacString)); |
| 61 | } | 76 | } |
| 62 | 77 | ||
| 63 | - if (!Strings.isNullOrEmpty(dstMacString)) { | 78 | + if (!isNullOrEmpty(dstMacString)) { |
| 64 | selectorBuilder.matchEthDst(MacAddress.valueOf(dstMacString)); | 79 | selectorBuilder.matchEthDst(MacAddress.valueOf(dstMacString)); |
| 65 | } | 80 | } |
| 66 | 81 | ||
| 67 | return selectorBuilder.build(); | 82 | return selectorBuilder.build(); |
| 68 | } | 83 | } |
| 69 | 84 | ||
| 85 | + /** | ||
| 86 | + * Builds the constraint list for this command based on the command line | ||
| 87 | + * parameters. | ||
| 88 | + * | ||
| 89 | + * @return List of constraint objects describing the constraints requested | ||
| 90 | + */ | ||
| 91 | + protected List<Constraint> buildConstraints() { | ||
| 92 | + final List<Constraint> constraints = new LinkedList<>(); | ||
| 93 | + | ||
| 94 | + // Check for a bandwidth specification | ||
| 95 | + if (!isNullOrEmpty(bandwidthString)) { | ||
| 96 | + final double bandwidthValue = Double.parseDouble(bandwidthString); | ||
| 97 | + constraints.add(new BandwidthConstraint(Bandwidth.valueOf(bandwidthValue))); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + // Check for a lambda specification | ||
| 101 | + if (lambda) { | ||
| 102 | + constraints.add(new LambdaConstraint(null)); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + return constraints; | ||
| 106 | + } | ||
| 70 | } | 107 | } | ... | ... |
| ... | @@ -25,6 +25,7 @@ import org.apache.karaf.shell.commands.Option; | ... | @@ -25,6 +25,7 @@ import org.apache.karaf.shell.commands.Option; |
| 25 | import org.onlab.onos.cli.Comparators; | 25 | import org.onlab.onos.cli.Comparators; |
| 26 | import org.onlab.onos.net.Device; | 26 | import org.onlab.onos.net.Device; |
| 27 | import org.onlab.onos.net.Port; | 27 | import org.onlab.onos.net.Port; |
| 28 | +import org.onlab.onos.net.PortNumber; | ||
| 28 | import org.onlab.onos.net.device.DeviceService; | 29 | import org.onlab.onos.net.device.DeviceService; |
| 29 | 30 | ||
| 30 | import java.util.ArrayList; | 31 | import java.util.ArrayList; |
| ... | @@ -108,7 +109,7 @@ public class DevicePortsListCommand extends DevicesListCommand { | ... | @@ -108,7 +109,7 @@ public class DevicePortsListCommand extends DevicesListCommand { |
| 108 | for (Port port : service.getPorts(device.id())) { | 109 | for (Port port : service.getPorts(device.id())) { |
| 109 | if (isIncluded(port)) { | 110 | if (isIncluded(port)) { |
| 110 | ports.add(mapper.createObjectNode() | 111 | ports.add(mapper.createObjectNode() |
| 111 | - .put("port", port.number().toString()) | 112 | + .put("port", portName(port.number())) |
| 112 | .put("isEnabled", port.isEnabled()) | 113 | .put("isEnabled", port.isEnabled()) |
| 113 | .put("type", port.type().toString().toLowerCase()) | 114 | .put("type", port.type().toString().toLowerCase()) |
| 114 | .put("portSpeed", port.portSpeed()) | 115 | .put("portSpeed", port.portSpeed()) |
| ... | @@ -120,6 +121,10 @@ public class DevicePortsListCommand extends DevicesListCommand { | ... | @@ -120,6 +121,10 @@ public class DevicePortsListCommand extends DevicesListCommand { |
| 120 | return result; | 121 | return result; |
| 121 | } | 122 | } |
| 122 | 123 | ||
| 124 | + private String portName(PortNumber port) { | ||
| 125 | + return port.equals(PortNumber.LOCAL) ? "local" : port.toString(); | ||
| 126 | + } | ||
| 127 | + | ||
| 123 | // Determines if a port should be included in output. | 128 | // Determines if a port should be included in output. |
| 124 | private boolean isIncluded(Port port) { | 129 | private boolean isIncluded(Port port) { |
| 125 | return enabled && port.isEnabled() || disabled && !port.isEnabled() || | 130 | return enabled && port.isEnabled() || disabled && !port.isEnabled() || |
| ... | @@ -133,7 +138,8 @@ public class DevicePortsListCommand extends DevicesListCommand { | ... | @@ -133,7 +138,8 @@ public class DevicePortsListCommand extends DevicesListCommand { |
| 133 | Collections.sort(ports, Comparators.PORT_COMPARATOR); | 138 | Collections.sort(ports, Comparators.PORT_COMPARATOR); |
| 134 | for (Port port : ports) { | 139 | for (Port port : ports) { |
| 135 | if (isIncluded(port)) { | 140 | if (isIncluded(port)) { |
| 136 | - print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled", | 141 | + print(FMT, portName(port.number()), |
| 142 | + port.isEnabled() ? "enabled" : "disabled", | ||
| 137 | port.type().toString().toLowerCase(), port.portSpeed(), | 143 | port.type().toString().toLowerCase(), port.portSpeed(), |
| 138 | annotations(port.annotations())); | 144 | annotations(port.annotations())); |
| 139 | } | 145 | } | ... | ... |
| ... | @@ -116,17 +116,6 @@ | ... | @@ -116,17 +116,6 @@ |
| 116 | </optional-completers> | 116 | </optional-completers> |
| 117 | </command> | 117 | </command> |
| 118 | <command> | 118 | <command> |
| 119 | - <action class="org.onlab.onos.cli.net.AddPointToPointIntentWithBandwidthConstraintCommand"/> | ||
| 120 | - <completers> | ||
| 121 | - <ref component-id="connectPointCompleter"/> | ||
| 122 | - <ref component-id="connectPointCompleter"/> | ||
| 123 | - <null/> | ||
| 124 | - </completers> | ||
| 125 | - <optional-completers> | ||
| 126 | - <entry key="-t" value-ref="ethTypeCompleter"/> | ||
| 127 | - </optional-completers> | ||
| 128 | - </command> | ||
| 129 | - <command> | ||
| 130 | <action class="org.onlab.onos.cli.net.AddOpticalIntentCommand"/> | 119 | <action class="org.onlab.onos.cli.net.AddOpticalIntentCommand"/> |
| 131 | <completers> | 120 | <completers> |
| 132 | <ref component-id="connectPointCompleter"/> | 121 | <ref component-id="connectPointCompleter"/> | ... | ... |
| ... | @@ -40,6 +40,10 @@ | ... | @@ -40,6 +40,10 @@ |
| 40 | <groupId>joda-time</groupId> | 40 | <groupId>joda-time</groupId> |
| 41 | <artifactId>joda-time</artifactId> | 41 | <artifactId>joda-time</artifactId> |
| 42 | </dependency> | 42 | </dependency> |
| 43 | + <dependency> | ||
| 44 | + <groupId>org.easymock</groupId> | ||
| 45 | + <artifactId>easymock</artifactId> | ||
| 46 | + </dependency> | ||
| 43 | </dependencies> | 47 | </dependencies> |
| 44 | 48 | ||
| 45 | </project> | 49 | </project> | ... | ... |
| ... | @@ -105,6 +105,7 @@ public final class HostToHostIntent extends ConnectivityIntent { | ... | @@ -105,6 +105,7 @@ public final class HostToHostIntent extends ConnectivityIntent { |
| 105 | .add("appId", appId()) | 105 | .add("appId", appId()) |
| 106 | .add("selector", selector()) | 106 | .add("selector", selector()) |
| 107 | .add("treatment", treatment()) | 107 | .add("treatment", treatment()) |
| 108 | + .add("constraints", constraints()) | ||
| 108 | .add("one", one) | 109 | .add("one", one) |
| 109 | .add("two", two) | 110 | .add("two", two) |
| 110 | .toString(); | 111 | .toString(); | ... | ... |
| ... | @@ -22,6 +22,7 @@ import org.onlab.onos.net.ConnectPoint; | ... | @@ -22,6 +22,7 @@ import org.onlab.onos.net.ConnectPoint; |
| 22 | import org.onlab.onos.net.flow.TrafficSelector; | 22 | import org.onlab.onos.net.flow.TrafficSelector; |
| 23 | import org.onlab.onos.net.flow.TrafficTreatment; | 23 | import org.onlab.onos.net.flow.TrafficTreatment; |
| 24 | 24 | ||
| 25 | +import java.util.List; | ||
| 25 | import java.util.Set; | 26 | import java.util.Set; |
| 26 | 27 | ||
| 27 | import static com.google.common.base.Preconditions.checkArgument; | 28 | import static com.google.common.base.Preconditions.checkArgument; |
| ... | @@ -65,6 +66,38 @@ public final class MultiPointToSinglePointIntent extends ConnectivityIntent { | ... | @@ -65,6 +66,38 @@ public final class MultiPointToSinglePointIntent extends ConnectivityIntent { |
| 65 | } | 66 | } |
| 66 | 67 | ||
| 67 | /** | 68 | /** |
| 69 | + * Creates a new multi-to-single point connectivity intent for the specified | ||
| 70 | + * traffic selector and treatment. | ||
| 71 | + * | ||
| 72 | + * @param appId application identifier | ||
| 73 | + * @param selector traffic selector | ||
| 74 | + * @param treatment treatment | ||
| 75 | + * @param ingressPoints set of ports from which ingress traffic originates | ||
| 76 | + * @param egressPoint port to which traffic will egress | ||
| 77 | + * @param constraints constraints to apply to the intent | ||
| 78 | + * @throws NullPointerException if {@code ingressPoints} or | ||
| 79 | + * {@code egressPoint} is null. | ||
| 80 | + * @throws IllegalArgumentException if the size of {@code ingressPoints} is | ||
| 81 | + * not more than 1 | ||
| 82 | + */ | ||
| 83 | + public MultiPointToSinglePointIntent(ApplicationId appId, | ||
| 84 | + TrafficSelector selector, | ||
| 85 | + TrafficTreatment treatment, | ||
| 86 | + Set<ConnectPoint> ingressPoints, | ||
| 87 | + ConnectPoint egressPoint, | ||
| 88 | + List<Constraint> constraints) { | ||
| 89 | + super(id(MultiPointToSinglePointIntent.class, selector, treatment, | ||
| 90 | + ingressPoints, egressPoint), appId, null, selector, treatment, | ||
| 91 | + constraints); | ||
| 92 | + | ||
| 93 | + checkNotNull(ingressPoints); | ||
| 94 | + checkArgument(!ingressPoints.isEmpty(), "Ingress point set cannot be empty"); | ||
| 95 | + | ||
| 96 | + this.ingressPoints = Sets.newHashSet(ingressPoints); | ||
| 97 | + this.egressPoint = checkNotNull(egressPoint); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + /** | ||
| 68 | * Constructor for serializer. | 101 | * Constructor for serializer. |
| 69 | */ | 102 | */ |
| 70 | protected MultiPointToSinglePointIntent() { | 103 | protected MultiPointToSinglePointIntent() { |
| ... | @@ -101,6 +134,7 @@ public final class MultiPointToSinglePointIntent extends ConnectivityIntent { | ... | @@ -101,6 +134,7 @@ public final class MultiPointToSinglePointIntent extends ConnectivityIntent { |
| 101 | .add("treatment", treatment()) | 134 | .add("treatment", treatment()) |
| 102 | .add("ingress", ingressPoints()) | 135 | .add("ingress", ingressPoints()) |
| 103 | .add("egress", egressPoint()) | 136 | .add("egress", egressPoint()) |
| 137 | + .add("constraints", constraints()) | ||
| 104 | .toString(); | 138 | .toString(); |
| 105 | } | 139 | } |
| 106 | } | 140 | } | ... | ... |
| ... | @@ -15,6 +15,8 @@ | ... | @@ -15,6 +15,8 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onlab.onos.net.intent; | 16 | package org.onlab.onos.net.intent; |
| 17 | 17 | ||
| 18 | +import java.util.List; | ||
| 19 | + | ||
| 18 | import com.google.common.base.MoreObjects; | 20 | import com.google.common.base.MoreObjects; |
| 19 | import org.onlab.onos.core.ApplicationId; | 21 | import org.onlab.onos.core.ApplicationId; |
| 20 | import org.onlab.onos.net.Path; | 22 | import org.onlab.onos.net.Path; |
| ... | @@ -46,6 +48,24 @@ public class PathIntent extends ConnectivityIntent { | ... | @@ -46,6 +48,24 @@ public class PathIntent extends ConnectivityIntent { |
| 46 | } | 48 | } |
| 47 | 49 | ||
| 48 | /** | 50 | /** |
| 51 | + * Creates a new point-to-point intent with the supplied ingress/egress | ||
| 52 | + * ports and using the specified explicit path. | ||
| 53 | + * | ||
| 54 | + * @param appId application identifier | ||
| 55 | + * @param selector traffic selector | ||
| 56 | + * @param treatment treatment | ||
| 57 | + * @param path traversed links | ||
| 58 | + * @param constraints optional list of constraints | ||
| 59 | + * @throws NullPointerException {@code path} is null | ||
| 60 | + */ | ||
| 61 | + public PathIntent(ApplicationId appId, TrafficSelector selector, | ||
| 62 | + TrafficTreatment treatment, Path path, List<Constraint> constraints) { | ||
| 63 | + super(id(PathIntent.class, selector, treatment, path, constraints), appId, | ||
| 64 | + resources(path.links()), selector, treatment, constraints); | ||
| 65 | + this.path = path; | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + /** | ||
| 49 | * Constructor for serializer. | 69 | * Constructor for serializer. |
| 50 | */ | 70 | */ |
| 51 | protected PathIntent() { | 71 | protected PathIntent() { |
| ... | @@ -75,6 +95,7 @@ public class PathIntent extends ConnectivityIntent { | ... | @@ -75,6 +95,7 @@ public class PathIntent extends ConnectivityIntent { |
| 75 | .add("appId", appId()) | 95 | .add("appId", appId()) |
| 76 | .add("selector", selector()) | 96 | .add("selector", selector()) |
| 77 | .add("treatment", treatment()) | 97 | .add("treatment", treatment()) |
| 98 | + .add("constraints", constraints()) | ||
| 78 | .add("path", path) | 99 | .add("path", path) |
| 79 | .toString(); | 100 | .toString(); |
| 80 | } | 101 | } | ... | ... |
| 1 | +/* | ||
| 2 | + * Copyright 2014 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onlab.onos.net.intent.constraint; | ||
| 17 | + | ||
| 18 | +import com.google.common.base.MoreObjects; | ||
| 19 | +import com.google.common.collect.ImmutableList; | ||
| 20 | +import org.onlab.onos.net.ElementId; | ||
| 21 | +import org.onlab.onos.net.Link; | ||
| 22 | +import org.onlab.onos.net.Path; | ||
| 23 | +import org.onlab.onos.net.intent.Constraint; | ||
| 24 | +import org.onlab.onos.net.resource.LinkResourceService; | ||
| 25 | + | ||
| 26 | +import java.util.LinkedList; | ||
| 27 | +import java.util.List; | ||
| 28 | +import java.util.Objects; | ||
| 29 | + | ||
| 30 | +import static com.google.common.base.Preconditions.checkArgument; | ||
| 31 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + * Constraint that evaluates elements passed through in order. | ||
| 35 | + */ | ||
| 36 | +public class WaypointConstraint implements Constraint { | ||
| 37 | + | ||
| 38 | + private final List<ElementId> waypoints; | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * Creates a new waypoint constraint. | ||
| 42 | + * | ||
| 43 | + * @param waypoints waypoints | ||
| 44 | + */ | ||
| 45 | + public WaypointConstraint(ElementId... waypoints) { | ||
| 46 | + checkNotNull(waypoints, "waypoints cannot be null"); | ||
| 47 | + checkArgument(waypoints.length > 0, "length of waypoints should be more than 0"); | ||
| 48 | + this.waypoints = ImmutableList.copyOf(waypoints); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + public List<ElementId> waypoints() { | ||
| 52 | + return waypoints; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + @Override | ||
| 56 | + public double cost(Link link, LinkResourceService resourceService) { | ||
| 57 | + // Always consider the number of hops | ||
| 58 | + return 1; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @Override | ||
| 62 | + public boolean validate(Path path, LinkResourceService resourceService) { | ||
| 63 | + LinkedList<ElementId> waypoints = new LinkedList<>(this.waypoints); | ||
| 64 | + ElementId current = waypoints.poll(); | ||
| 65 | + // This is safe because Path class ensures the number of links are more than 0 | ||
| 66 | + Link firstLink = path.links().get(0); | ||
| 67 | + if (firstLink.src().elementId().equals(current)) { | ||
| 68 | + current = waypoints.poll(); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + for (Link link : path.links()) { | ||
| 72 | + if (link.dst().elementId().equals(current)) { | ||
| 73 | + current = waypoints.poll(); | ||
| 74 | + // Empty waypoints means passing through all waypoints in the specified order | ||
| 75 | + if (current == null) { | ||
| 76 | + return true; | ||
| 77 | + } | ||
| 78 | + } | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + return false; | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + @Override | ||
| 85 | + public int hashCode() { | ||
| 86 | + return Objects.hash(waypoints); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + @Override | ||
| 90 | + public boolean equals(Object obj) { | ||
| 91 | + if (this == obj) { | ||
| 92 | + return true; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + if (!(obj instanceof WaypointConstraint)) { | ||
| 96 | + return false; | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + final WaypointConstraint that = (WaypointConstraint) obj; | ||
| 100 | + return Objects.equals(this.waypoints, that.waypoints); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + @Override | ||
| 104 | + public String toString() { | ||
| 105 | + return MoreObjects.toStringHelper(this) | ||
| 106 | + .add("waypoints", waypoints) | ||
| 107 | + .toString(); | ||
| 108 | + } | ||
| 109 | +} |
| 1 | package org.onlab.onos.store.service; | 1 | package org.onlab.onos.store.service; |
| 2 | 2 | ||
| 3 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
| 4 | + | ||
| 5 | +import java.util.Objects; | ||
| 6 | + | ||
| 3 | import com.google.common.base.MoreObjects; | 7 | import com.google.common.base.MoreObjects; |
| 4 | 8 | ||
| 5 | /** | 9 | /** |
| ... | @@ -10,9 +14,21 @@ public class ReadRequest { | ... | @@ -10,9 +14,21 @@ public class ReadRequest { |
| 10 | private final String tableName; | 14 | private final String tableName; |
| 11 | private final String key; | 15 | private final String key; |
| 12 | 16 | ||
| 17 | + /** | ||
| 18 | + * Creates a read request, | ||
| 19 | + * which will retrieve the specified key from the table. | ||
| 20 | + * | ||
| 21 | + * @param tableName name of the table | ||
| 22 | + * @param key key in the table | ||
| 23 | + * @return ReadRequest | ||
| 24 | + */ | ||
| 25 | + public static ReadRequest get(String tableName, String key) { | ||
| 26 | + return new ReadRequest(tableName, key); | ||
| 27 | + } | ||
| 28 | + | ||
| 13 | public ReadRequest(String tableName, String key) { | 29 | public ReadRequest(String tableName, String key) { |
| 14 | - this.tableName = tableName; | 30 | + this.tableName = checkNotNull(tableName); |
| 15 | - this.key = key; | 31 | + this.key = checkNotNull(key); |
| 16 | } | 32 | } |
| 17 | 33 | ||
| 18 | /** | 34 | /** |
| ... | @@ -38,4 +54,26 @@ public class ReadRequest { | ... | @@ -38,4 +54,26 @@ public class ReadRequest { |
| 38 | .add("key", key) | 54 | .add("key", key) |
| 39 | .toString(); | 55 | .toString(); |
| 40 | } | 56 | } |
| 57 | + | ||
| 58 | + @Override | ||
| 59 | + public int hashCode() { | ||
| 60 | + return Objects.hash(key, tableName); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Override | ||
| 64 | + public boolean equals(Object obj) { | ||
| 65 | + if (this == obj) { | ||
| 66 | + return true; | ||
| 67 | + } | ||
| 68 | + if (obj == null) { | ||
| 69 | + return false; | ||
| 70 | + } | ||
| 71 | + if (getClass() != obj.getClass()) { | ||
| 72 | + return false; | ||
| 73 | + } | ||
| 74 | + ReadRequest other = (ReadRequest) obj; | ||
| 75 | + return Objects.equals(this.key, other.key) && | ||
| 76 | + Objects.equals(this.tableName, other.tableName); | ||
| 77 | + } | ||
| 78 | + | ||
| 41 | } | 79 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -38,6 +38,28 @@ public class VersionedValue { | ... | @@ -38,6 +38,28 @@ public class VersionedValue { |
| 38 | return version; | 38 | return version; |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | + /** | ||
| 42 | + * Creates a copy of given VersionedValue. | ||
| 43 | + * | ||
| 44 | + * @param original VersionedValue to create a copy | ||
| 45 | + * @return same as original if original or it's value is null, | ||
| 46 | + * otherwise creates a copy. | ||
| 47 | + */ | ||
| 48 | + public static VersionedValue copy(VersionedValue original) { | ||
| 49 | + if (original == null) { | ||
| 50 | + return null; | ||
| 51 | + } | ||
| 52 | + if (original.value == null) { | ||
| 53 | + // immutable, no need to copy | ||
| 54 | + return original; | ||
| 55 | + } else { | ||
| 56 | + return new VersionedValue( | ||
| 57 | + Arrays.copyOf(original.value, | ||
| 58 | + original.value.length), | ||
| 59 | + original.version); | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + | ||
| 41 | @Override | 63 | @Override |
| 42 | public String toString() { | 64 | public String toString() { |
| 43 | return MoreObjects.toStringHelper(getClass()) | 65 | return MoreObjects.toStringHelper(getClass()) | ... | ... |
| 1 | +/* | ||
| 2 | + * Copyright 2014 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onlab.onos.net.intent.constraint; | ||
| 17 | + | ||
| 18 | +import com.google.common.testing.EqualsTester; | ||
| 19 | +import org.junit.Before; | ||
| 20 | +import org.junit.Test; | ||
| 21 | +import org.onlab.onos.net.DefaultLink; | ||
| 22 | +import org.onlab.onos.net.DefaultPath; | ||
| 23 | +import org.onlab.onos.net.DeviceId; | ||
| 24 | +import org.onlab.onos.net.Path; | ||
| 25 | +import org.onlab.onos.net.PortNumber; | ||
| 26 | +import org.onlab.onos.net.intent.Constraint; | ||
| 27 | +import org.onlab.onos.net.provider.ProviderId; | ||
| 28 | +import org.onlab.onos.net.resource.LinkResourceService; | ||
| 29 | + | ||
| 30 | +import java.util.Arrays; | ||
| 31 | + | ||
| 32 | +import static org.easymock.EasyMock.createMock; | ||
| 33 | +import static org.hamcrest.Matchers.is; | ||
| 34 | +import static org.junit.Assert.assertThat; | ||
| 35 | +import static org.onlab.onos.net.DefaultLinkTest.cp; | ||
| 36 | +import static org.onlab.onos.net.DeviceId.deviceId; | ||
| 37 | +import static org.onlab.onos.net.Link.Type.DIRECT; | ||
| 38 | + | ||
| 39 | +/** | ||
| 40 | + * Test for constraint of intermediate elements. | ||
| 41 | + */ | ||
| 42 | +public class WaypointConstraintTest { | ||
| 43 | + | ||
| 44 | + public static final DeviceId DID1 = deviceId("of:1"); | ||
| 45 | + public static final DeviceId DID2 = deviceId("of:2"); | ||
| 46 | + public static final DeviceId DID3 = deviceId("of:3"); | ||
| 47 | + public static final DeviceId DID4 = deviceId("of:4"); | ||
| 48 | + public static final PortNumber PN1 = PortNumber.portNumber(1); | ||
| 49 | + public static final PortNumber PN2 = PortNumber.portNumber(2); | ||
| 50 | + public static final PortNumber PN3 = PortNumber.portNumber(3); | ||
| 51 | + public static final PortNumber PN4 = PortNumber.portNumber(4); | ||
| 52 | + public static final ProviderId PROVIDER_ID = new ProviderId("of", "foo"); | ||
| 53 | + | ||
| 54 | + private WaypointConstraint sut; | ||
| 55 | + private LinkResourceService linkResourceService; | ||
| 56 | + | ||
| 57 | + private Path path; | ||
| 58 | + private DefaultLink link2; | ||
| 59 | + private DefaultLink link1; | ||
| 60 | + | ||
| 61 | + @Before | ||
| 62 | + public void setUp() { | ||
| 63 | + linkResourceService = createMock(LinkResourceService.class); | ||
| 64 | + | ||
| 65 | + link1 = new DefaultLink(PROVIDER_ID, cp(DID1, PN1), cp(DID2, PN2), DIRECT); | ||
| 66 | + link2 = new DefaultLink(PROVIDER_ID, cp(DID2, PN3), cp(DID3, PN4), DIRECT); | ||
| 67 | + path = new DefaultPath(PROVIDER_ID, Arrays.asList(link1, link2), 10); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * Tests that all of the specified waypoints are included in the specified path in order. | ||
| 72 | + */ | ||
| 73 | + @Test | ||
| 74 | + public void testSatisfyWaypoints() { | ||
| 75 | + sut = new WaypointConstraint(DID1, DID2, DID3); | ||
| 76 | + | ||
| 77 | + assertThat(sut.validate(path, linkResourceService), is(true)); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + /** | ||
| 81 | + * Tests that the specified path does not includes the specified waypoint. | ||
| 82 | + */ | ||
| 83 | + @Test | ||
| 84 | + public void testNotSatisfyWaypoint() { | ||
| 85 | + sut = new WaypointConstraint(DID4); | ||
| 86 | + | ||
| 87 | + assertThat(sut.validate(path, linkResourceService), is(false)); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + @Test | ||
| 91 | + public void testEquality() { | ||
| 92 | + Constraint c1 = new WaypointConstraint(DID1, DID2); | ||
| 93 | + Constraint c2 = new WaypointConstraint(DID1, DID2); | ||
| 94 | + | ||
| 95 | + Constraint c3 = new WaypointConstraint(DID2); | ||
| 96 | + Constraint c4 = new WaypointConstraint(DID3); | ||
| 97 | + | ||
| 98 | + new EqualsTester() | ||
| 99 | + .addEqualityGroup(c1, c2) | ||
| 100 | + .addEqualityGroup(c3) | ||
| 101 | + .addEqualityGroup(c4) | ||
| 102 | + .testEquals(); | ||
| 103 | + } | ||
| 104 | +} |
| ... | @@ -118,13 +118,14 @@ public abstract class ConnectivityIntentCompiler<T extends ConnectivityIntent> | ... | @@ -118,13 +118,14 @@ public abstract class ConnectivityIntentCompiler<T extends ConnectivityIntent> |
| 118 | 118 | ||
| 119 | @Override | 119 | @Override |
| 120 | public double weight(TopologyEdge edge) { | 120 | public double weight(TopologyEdge edge) { |
| 121 | - if (constraints == null) { | 121 | + if (constraints == null || !constraints.iterator().hasNext()) { |
| 122 | return 1.0; | 122 | return 1.0; |
| 123 | } | 123 | } |
| 124 | 124 | ||
| 125 | // iterate over all constraints in order and return the weight of | 125 | // iterate over all constraints in order and return the weight of |
| 126 | // the first one with fast fail over the first failure | 126 | // the first one with fast fail over the first failure |
| 127 | Iterator<Constraint> it = constraints.iterator(); | 127 | Iterator<Constraint> it = constraints.iterator(); |
| 128 | + | ||
| 128 | double cost = it.next().cost(edge.link(), resourceService); | 129 | double cost = it.next().cost(edge.link(), resourceService); |
| 129 | while (it.hasNext() && cost > 0) { | 130 | while (it.hasNext() && cost > 0) { |
| 130 | if (it.next().cost(edge.link(), resourceService) < 0) { | 131 | if (it.next().cost(edge.link(), resourceService) < 0) { |
| ... | @@ -132,6 +133,7 @@ public abstract class ConnectivityIntentCompiler<T extends ConnectivityIntent> | ... | @@ -132,6 +133,7 @@ public abstract class ConnectivityIntentCompiler<T extends ConnectivityIntent> |
| 132 | } | 133 | } |
| 133 | } | 134 | } |
| 134 | return cost; | 135 | return cost; |
| 136 | + | ||
| 135 | } | 137 | } |
| 136 | } | 138 | } |
| 137 | 139 | ... | ... |
| ... | @@ -70,7 +70,8 @@ public class HostToHostIntentCompiler | ... | @@ -70,7 +70,8 @@ public class HostToHostIntentCompiler |
| 70 | HostToHostIntent intent) { | 70 | HostToHostIntent intent) { |
| 71 | TrafficSelector selector = builder(intent.selector()) | 71 | TrafficSelector selector = builder(intent.selector()) |
| 72 | .matchEthSrc(src.mac()).matchEthDst(dst.mac()).build(); | 72 | .matchEthSrc(src.mac()).matchEthDst(dst.mac()).build(); |
| 73 | - return new PathIntent(intent.appId(), selector, intent.treatment(), path); | 73 | + return new PathIntent(intent.appId(), selector, intent.treatment(), |
| 74 | + path, intent.constraints()); | ||
| 74 | } | 75 | } |
| 75 | 76 | ||
| 76 | } | 77 | } | ... | ... |
| ... | @@ -77,7 +77,7 @@ import com.google.common.collect.Lists; | ... | @@ -77,7 +77,7 @@ import com.google.common.collect.Lists; |
| 77 | @Service | 77 | @Service |
| 78 | public class IntentManager | 78 | public class IntentManager |
| 79 | implements IntentService, IntentExtensionService { | 79 | implements IntentService, IntentExtensionService { |
| 80 | - private final Logger log = getLogger(getClass()); | 80 | + private static final Logger log = getLogger(IntentManager.class); |
| 81 | 81 | ||
| 82 | public static final String INTENT_NULL = "Intent cannot be null"; | 82 | public static final String INTENT_NULL = "Intent cannot be null"; |
| 83 | public static final String INTENT_ID_NULL = "Intent ID cannot be null"; | 83 | public static final String INTENT_ID_NULL = "Intent ID cannot be null"; | ... | ... |
| ... | @@ -77,7 +77,8 @@ public class PointToPointIntentCompiler | ... | @@ -77,7 +77,8 @@ public class PointToPointIntentCompiler |
| 77 | private Intent createPathIntent(Path path, | 77 | private Intent createPathIntent(Path path, |
| 78 | PointToPointIntent intent) { | 78 | PointToPointIntent intent) { |
| 79 | return new PathIntent(intent.appId(), | 79 | return new PathIntent(intent.appId(), |
| 80 | - intent.selector(), intent.treatment(), path); | 80 | + intent.selector(), intent.treatment(), path, |
| 81 | + intent.constraints()); | ||
| 81 | } | 82 | } |
| 82 | 83 | ||
| 83 | } | 84 | } | ... | ... |
| ... | @@ -28,6 +28,7 @@ import org.onlab.onos.cluster.DefaultControllerNode; | ... | @@ -28,6 +28,7 @@ import org.onlab.onos.cluster.DefaultControllerNode; |
| 28 | import org.onlab.onos.cluster.NodeId; | 28 | import org.onlab.onos.cluster.NodeId; |
| 29 | import org.onlab.onos.event.impl.TestEventDispatcher; | 29 | import org.onlab.onos.event.impl.TestEventDispatcher; |
| 30 | import org.onlab.onos.mastership.MastershipService; | 30 | import org.onlab.onos.mastership.MastershipService; |
| 31 | +import org.onlab.onos.mastership.MastershipStore; | ||
| 31 | import org.onlab.onos.mastership.MastershipTermService; | 32 | import org.onlab.onos.mastership.MastershipTermService; |
| 32 | import org.onlab.onos.net.DeviceId; | 33 | import org.onlab.onos.net.DeviceId; |
| 33 | import org.onlab.onos.store.trivial.impl.SimpleMastershipStore; | 34 | import org.onlab.onos.store.trivial.impl.SimpleMastershipStore; |
| ... | @@ -57,9 +58,9 @@ public class MastershipManagerTest { | ... | @@ -57,9 +58,9 @@ public class MastershipManagerTest { |
| 57 | public void setUp() { | 58 | public void setUp() { |
| 58 | mgr = new MastershipManager(); | 59 | mgr = new MastershipManager(); |
| 59 | service = mgr; | 60 | service = mgr; |
| 60 | - mgr.store = new SimpleMastershipStore(); | ||
| 61 | mgr.eventDispatcher = new TestEventDispatcher(); | 61 | mgr.eventDispatcher = new TestEventDispatcher(); |
| 62 | mgr.clusterService = new TestClusterService(); | 62 | mgr.clusterService = new TestClusterService(); |
| 63 | + mgr.store = new TestSimpleMastershipStore(mgr.clusterService); | ||
| 63 | mgr.activate(); | 64 | mgr.activate(); |
| 64 | } | 65 | } |
| 65 | 66 | ||
| ... | @@ -74,7 +75,8 @@ public class MastershipManagerTest { | ... | @@ -74,7 +75,8 @@ public class MastershipManagerTest { |
| 74 | @Test | 75 | @Test |
| 75 | public void setRole() { | 76 | public void setRole() { |
| 76 | mgr.setRole(NID_OTHER, DEV_MASTER, MASTER); | 77 | mgr.setRole(NID_OTHER, DEV_MASTER, MASTER); |
| 77 | - assertEquals("wrong local role:", STANDBY, mgr.getLocalRole(DEV_MASTER)); | 78 | + assertEquals("wrong local role:", NONE, mgr.getLocalRole(DEV_MASTER)); |
| 79 | + assertEquals("wrong obtained role:", STANDBY, mgr.requestRoleFor(DEV_MASTER)); | ||
| 78 | 80 | ||
| 79 | //set to master | 81 | //set to master |
| 80 | mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); | 82 | mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); |
| ... | @@ -182,4 +184,12 @@ public class MastershipManagerTest { | ... | @@ -182,4 +184,12 @@ public class MastershipManagerTest { |
| 182 | } | 184 | } |
| 183 | 185 | ||
| 184 | } | 186 | } |
| 187 | + | ||
| 188 | + private final class TestSimpleMastershipStore extends SimpleMastershipStore | ||
| 189 | + implements MastershipStore { | ||
| 190 | + | ||
| 191 | + public TestSimpleMastershipStore(ClusterService clusterService) { | ||
| 192 | + super.clusterService = clusterService; | ||
| 193 | + } | ||
| 194 | + } | ||
| 185 | } | 195 | } | ... | ... |
| ... | @@ -64,6 +64,12 @@ | ... | @@ -64,6 +64,12 @@ |
| 64 | --> | 64 | --> |
| 65 | 65 | ||
| 66 | <dependency> | 66 | <dependency> |
| 67 | + <groupId>org.mapdb</groupId> | ||
| 68 | + <artifactId>mapdb</artifactId> | ||
| 69 | + <version>1.0.6</version> | ||
| 70 | + </dependency> | ||
| 71 | + | ||
| 72 | + <dependency> | ||
| 67 | <groupId>com.fasterxml.jackson.core</groupId> | 73 | <groupId>com.fasterxml.jackson.core</groupId> |
| 68 | <artifactId>jackson-databind</artifactId> | 74 | <artifactId>jackson-databind</artifactId> |
| 69 | </dependency> | 75 | </dependency> | ... | ... |
| ... | @@ -159,7 +159,7 @@ public class ClusterCommunicationManager | ... | @@ -159,7 +159,7 @@ public class ClusterCommunicationManager |
| 159 | return messagingService.sendAndReceive(nodeEp, message.subject().value(), SERIALIZER.encode(message)); | 159 | return messagingService.sendAndReceive(nodeEp, message.subject().value(), SERIALIZER.encode(message)); |
| 160 | 160 | ||
| 161 | } catch (IOException e) { | 161 | } catch (IOException e) { |
| 162 | - log.error("Failed interaction with remote nodeId: " + toNodeId, e); | 162 | + log.trace("Failed interaction with remote nodeId: " + toNodeId, e); |
| 163 | throw e; | 163 | throw e; |
| 164 | } | 164 | } |
| 165 | } | 165 | } | ... | ... |
| ... | @@ -283,16 +283,15 @@ implements MastershipStore { | ... | @@ -283,16 +283,15 @@ implements MastershipStore { |
| 283 | case MASTER: | 283 | case MASTER: |
| 284 | NodeId newMaster = reelect(nodeId, deviceId, rv); | 284 | NodeId newMaster = reelect(nodeId, deviceId, rv); |
| 285 | rv.reassign(nodeId, NONE, STANDBY); | 285 | rv.reassign(nodeId, NONE, STANDBY); |
| 286 | + updateTerm(deviceId); | ||
| 286 | if (newMaster != null) { | 287 | if (newMaster != null) { |
| 287 | - updateTerm(deviceId); | ||
| 288 | roleMap.put(deviceId, rv); | 288 | roleMap.put(deviceId, rv); |
| 289 | return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo()); | 289 | return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo()); |
| 290 | } else { | 290 | } else { |
| 291 | // no master candidate | 291 | // no master candidate |
| 292 | roleMap.put(deviceId, rv); | 292 | roleMap.put(deviceId, rv); |
| 293 | - // FIXME: Should there be new event type? | 293 | + // TODO: Should there be new event type for no MASTER? |
| 294 | - // or should we issue null Master event? | 294 | + return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo()); |
| 295 | - return null; | ||
| 296 | } | 295 | } |
| 297 | case STANDBY: | 296 | case STANDBY: |
| 298 | return null; | 297 | return null; | ... | ... |
| ... | @@ -149,12 +149,12 @@ public class ClusterMessagingProtocol | ... | @@ -149,12 +149,12 @@ public class ClusterMessagingProtocol |
| 149 | 149 | ||
| 150 | @Activate | 150 | @Activate |
| 151 | public void activate() { | 151 | public void activate() { |
| 152 | - log.info("Started."); | 152 | + log.info("Started"); |
| 153 | } | 153 | } |
| 154 | 154 | ||
| 155 | @Deactivate | 155 | @Deactivate |
| 156 | public void deactivate() { | 156 | public void deactivate() { |
| 157 | - log.info("Stopped."); | 157 | + log.info("Stopped"); |
| 158 | } | 158 | } |
| 159 | 159 | ||
| 160 | @Override | 160 | @Override | ... | ... |
| ... | @@ -132,8 +132,8 @@ public class ClusterMessagingProtocolClient implements ProtocolClient { | ... | @@ -132,8 +132,8 @@ public class ClusterMessagingProtocolClient implements ProtocolClient { |
| 132 | } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) { | 132 | } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) { |
| 133 | if (message.subject().equals(ClusterMessagingProtocol.COPYCAT_SYNC) || | 133 | if (message.subject().equals(ClusterMessagingProtocol.COPYCAT_SYNC) || |
| 134 | message.subject().equals(ClusterMessagingProtocol.COPYCAT_PING)) { | 134 | message.subject().equals(ClusterMessagingProtocol.COPYCAT_PING)) { |
| 135 | - log.warn("Request to {} failed. Will retry " | 135 | + log.warn("{} Request to {} failed. Will retry in {} ms", |
| 136 | - + "in {} ms", remoteNode, RETRY_INTERVAL_MILLIS); | 136 | + message.subject(), remoteNode, RETRY_INTERVAL_MILLIS); |
| 137 | THREAD_POOL.schedule( | 137 | THREAD_POOL.schedule( |
| 138 | this, | 138 | this, |
| 139 | RETRY_INTERVAL_MILLIS, | 139 | RETRY_INTERVAL_MILLIS, | ... | ... |
| ... | @@ -3,12 +3,17 @@ package org.onlab.onos.store.service.impl; | ... | @@ -3,12 +3,17 @@ package org.onlab.onos.store.service.impl; |
| 3 | import static org.slf4j.LoggerFactory.getLogger; | 3 | import static org.slf4j.LoggerFactory.getLogger; |
| 4 | 4 | ||
| 5 | import java.util.concurrent.CompletableFuture; | 5 | import java.util.concurrent.CompletableFuture; |
| 6 | +import java.util.function.BiConsumer; | ||
| 6 | 7 | ||
| 7 | import net.kuujo.copycat.protocol.PingRequest; | 8 | import net.kuujo.copycat.protocol.PingRequest; |
| 9 | +import net.kuujo.copycat.protocol.PingResponse; | ||
| 8 | import net.kuujo.copycat.protocol.PollRequest; | 10 | import net.kuujo.copycat.protocol.PollRequest; |
| 11 | +import net.kuujo.copycat.protocol.PollResponse; | ||
| 9 | import net.kuujo.copycat.protocol.RequestHandler; | 12 | import net.kuujo.copycat.protocol.RequestHandler; |
| 10 | import net.kuujo.copycat.protocol.SubmitRequest; | 13 | import net.kuujo.copycat.protocol.SubmitRequest; |
| 14 | +import net.kuujo.copycat.protocol.SubmitResponse; | ||
| 11 | import net.kuujo.copycat.protocol.SyncRequest; | 15 | import net.kuujo.copycat.protocol.SyncRequest; |
| 16 | +import net.kuujo.copycat.protocol.SyncResponse; | ||
| 12 | import net.kuujo.copycat.spi.protocol.ProtocolServer; | 17 | import net.kuujo.copycat.spi.protocol.ProtocolServer; |
| 13 | 18 | ||
| 14 | import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; | 19 | import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; |
| ... | @@ -57,37 +62,37 @@ public class ClusterMessagingProtocolServer implements ProtocolServer { | ... | @@ -57,37 +62,37 @@ public class ClusterMessagingProtocolServer implements ProtocolServer { |
| 57 | public void handle(ClusterMessage message) { | 62 | public void handle(ClusterMessage message) { |
| 58 | T request = ClusterMessagingProtocol.SERIALIZER.decode(message.payload()); | 63 | T request = ClusterMessagingProtocol.SERIALIZER.decode(message.payload()); |
| 59 | if (request.getClass().equals(PingRequest.class)) { | 64 | if (request.getClass().equals(PingRequest.class)) { |
| 60 | - handler.ping((PingRequest) request).whenComplete((response, error) -> { | 65 | + handler.ping((PingRequest) request).whenComplete(new PostExecutionTask<PingResponse>(message)); |
| 61 | - try { | ||
| 62 | - message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response)); | ||
| 63 | - } catch (Exception e) { | ||
| 64 | - log.error("Failed to respond to ping request", e); | ||
| 65 | - } | ||
| 66 | - }); | ||
| 67 | } else if (request.getClass().equals(PollRequest.class)) { | 66 | } else if (request.getClass().equals(PollRequest.class)) { |
| 68 | - handler.poll((PollRequest) request).whenComplete((response, error) -> { | 67 | + handler.poll((PollRequest) request).whenComplete(new PostExecutionTask<PollResponse>(message)); |
| 69 | - try { | ||
| 70 | - message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response)); | ||
| 71 | - } catch (Exception e) { | ||
| 72 | - log.error("Failed to respond to poll request", e); | ||
| 73 | - } | ||
| 74 | - }); | ||
| 75 | } else if (request.getClass().equals(SyncRequest.class)) { | 68 | } else if (request.getClass().equals(SyncRequest.class)) { |
| 76 | - handler.sync((SyncRequest) request).whenComplete((response, error) -> { | 69 | + handler.sync((SyncRequest) request).whenComplete(new PostExecutionTask<SyncResponse>(message)); |
| 77 | - try { | ||
| 78 | - message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response)); | ||
| 79 | - } catch (Exception e) { | ||
| 80 | - log.error("Failed to respond to sync request", e); | ||
| 81 | - } | ||
| 82 | - }); | ||
| 83 | } else if (request.getClass().equals(SubmitRequest.class)) { | 70 | } else if (request.getClass().equals(SubmitRequest.class)) { |
| 84 | - handler.submit((SubmitRequest) request).whenComplete((response, error) -> { | 71 | + handler.submit((SubmitRequest) request).whenComplete(new PostExecutionTask<SubmitResponse>(message)); |
| 72 | + } else { | ||
| 73 | + throw new IllegalStateException("Unknown request type: " + request.getClass().getName()); | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + private class PostExecutionTask<R> implements BiConsumer<R, Throwable> { | ||
| 78 | + | ||
| 79 | + private final ClusterMessage message; | ||
| 80 | + | ||
| 81 | + public PostExecutionTask(ClusterMessage message) { | ||
| 82 | + this.message = message; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @Override | ||
| 86 | + public void accept(R response, Throwable t) { | ||
| 87 | + if (t != null) { | ||
| 88 | + log.error("Processing for " + message.subject() + " failed.", t); | ||
| 89 | + } else { | ||
| 85 | try { | 90 | try { |
| 86 | message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response)); | 91 | message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response)); |
| 87 | } catch (Exception e) { | 92 | } catch (Exception e) { |
| 88 | - log.error("Failed to respond to submit request", e); | 93 | + log.error("Failed to respond to " + response.getClass().getName(), e); |
| 89 | } | 94 | } |
| 90 | - }); | 95 | + } |
| 91 | } | 96 | } |
| 92 | } | 97 | } |
| 93 | } | 98 | } | ... | ... |
| ... | @@ -5,20 +5,26 @@ import static org.slf4j.LoggerFactory.getLogger; | ... | @@ -5,20 +5,26 @@ import static org.slf4j.LoggerFactory.getLogger; |
| 5 | import java.util.ArrayList; | 5 | import java.util.ArrayList; |
| 6 | import java.util.Arrays; | 6 | import java.util.Arrays; |
| 7 | import java.util.List; | 7 | import java.util.List; |
| 8 | +import java.util.concurrent.CountDownLatch; | ||
| 9 | +import java.util.concurrent.TimeUnit; | ||
| 8 | 10 | ||
| 9 | import net.kuujo.copycat.Copycat; | 11 | import net.kuujo.copycat.Copycat; |
| 10 | import net.kuujo.copycat.StateMachine; | 12 | import net.kuujo.copycat.StateMachine; |
| 13 | +import net.kuujo.copycat.cluster.ClusterConfig; | ||
| 11 | import net.kuujo.copycat.cluster.TcpCluster; | 14 | import net.kuujo.copycat.cluster.TcpCluster; |
| 12 | import net.kuujo.copycat.cluster.TcpClusterConfig; | 15 | import net.kuujo.copycat.cluster.TcpClusterConfig; |
| 13 | import net.kuujo.copycat.cluster.TcpMember; | 16 | import net.kuujo.copycat.cluster.TcpMember; |
| 14 | import net.kuujo.copycat.log.InMemoryLog; | 17 | import net.kuujo.copycat.log.InMemoryLog; |
| 15 | import net.kuujo.copycat.log.Log; | 18 | import net.kuujo.copycat.log.Log; |
| 19 | + | ||
| 16 | import org.apache.felix.scr.annotations.Activate; | 20 | import org.apache.felix.scr.annotations.Activate; |
| 17 | import org.apache.felix.scr.annotations.Component; | 21 | import org.apache.felix.scr.annotations.Component; |
| 18 | import org.apache.felix.scr.annotations.Deactivate; | 22 | import org.apache.felix.scr.annotations.Deactivate; |
| 19 | import org.apache.felix.scr.annotations.Reference; | 23 | import org.apache.felix.scr.annotations.Reference; |
| 20 | import org.apache.felix.scr.annotations.ReferenceCardinality; | 24 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
| 21 | import org.apache.felix.scr.annotations.Service; | 25 | import org.apache.felix.scr.annotations.Service; |
| 26 | +import org.onlab.onos.cluster.ClusterEvent; | ||
| 27 | +import org.onlab.onos.cluster.ClusterEventListener; | ||
| 22 | import org.onlab.onos.cluster.ClusterService; | 28 | import org.onlab.onos.cluster.ClusterService; |
| 23 | import org.onlab.onos.cluster.ControllerNode; | 29 | import org.onlab.onos.cluster.ControllerNode; |
| 24 | import org.onlab.onos.store.service.DatabaseAdminService; | 30 | import org.onlab.onos.store.service.DatabaseAdminService; |
| ... | @@ -35,8 +41,6 @@ import org.onlab.onos.store.service.WriteRequest; | ... | @@ -35,8 +41,6 @@ import org.onlab.onos.store.service.WriteRequest; |
| 35 | import org.onlab.onos.store.service.WriteResult; | 41 | import org.onlab.onos.store.service.WriteResult; |
| 36 | import org.slf4j.Logger; | 42 | import org.slf4j.Logger; |
| 37 | 43 | ||
| 38 | -import com.google.common.collect.Lists; | ||
| 39 | - | ||
| 40 | /** | 44 | /** |
| 41 | * Strongly consistent and durable state management service based on | 45 | * Strongly consistent and durable state management service based on |
| 42 | * Copycat implementation of Raft consensus protocol. | 46 | * Copycat implementation of Raft consensus protocol. |
| ... | @@ -58,17 +62,34 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ... | @@ -58,17 +62,34 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { |
| 58 | private Copycat copycat; | 62 | private Copycat copycat; |
| 59 | private DatabaseClient client; | 63 | private DatabaseClient client; |
| 60 | 64 | ||
| 65 | + // guarded by synchronized block | ||
| 66 | + private ClusterConfig<TcpMember> clusterConfig; | ||
| 67 | + | ||
| 68 | + private CountDownLatch clusterEventLatch; | ||
| 69 | + | ||
| 70 | + private ClusterEventListener clusterEventListener; | ||
| 71 | + | ||
| 61 | @Activate | 72 | @Activate |
| 62 | public void activate() { | 73 | public void activate() { |
| 63 | - log.info("Starting."); | ||
| 64 | 74 | ||
| 65 | - // TODO: Not every node can be part of the consensus ring. | 75 | + // TODO: Not every node should be part of the consensus ring. |
| 66 | 76 | ||
| 77 | + final ControllerNode localNode = clusterService.getLocalNode(); | ||
| 67 | TcpMember localMember = | 78 | TcpMember localMember = |
| 68 | new TcpMember( | 79 | new TcpMember( |
| 69 | - clusterService.getLocalNode().ip().toString(), | 80 | + localNode.ip().toString(), |
| 70 | - clusterService.getLocalNode().tcpPort()); | 81 | + localNode.tcpPort()); |
| 71 | - List<TcpMember> remoteMembers = Lists.newArrayList(); | 82 | + |
| 83 | + clusterConfig = new TcpClusterConfig(); | ||
| 84 | + clusterConfig.setLocalMember(localMember); | ||
| 85 | + | ||
| 86 | + List<TcpMember> remoteMembers = new ArrayList<>(clusterService.getNodes().size()); | ||
| 87 | + | ||
| 88 | + clusterEventLatch = new CountDownLatch(1); | ||
| 89 | + clusterEventListener = new InternalClusterEventListener(); | ||
| 90 | + clusterService.addListener(clusterEventListener); | ||
| 91 | + | ||
| 92 | + // note: from this point beyond, clusterConfig requires synchronization | ||
| 72 | 93 | ||
| 73 | for (ControllerNode node : clusterService.getNodes()) { | 94 | for (ControllerNode node : clusterService.getNodes()) { |
| 74 | TcpMember member = new TcpMember(node.ip().toString(), node.tcpPort()); | 95 | TcpMember member = new TcpMember(node.ip().toString(), node.tcpPort()); |
| ... | @@ -77,20 +98,37 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ... | @@ -77,20 +98,37 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { |
| 77 | } | 98 | } |
| 78 | } | 99 | } |
| 79 | 100 | ||
| 80 | - // Configure the cluster. | 101 | + if (remoteMembers.isEmpty()) { |
| 81 | - TcpClusterConfig config = new TcpClusterConfig(); | 102 | + log.info("This node is the only node in the cluster. " |
| 103 | + + "Waiting for others to show up."); | ||
| 104 | + // FIXME: hack trying to relax cases forming multiple consensus rings. | ||
| 105 | + // add seed node configuration to avoid this | ||
| 106 | + | ||
| 107 | + // If the node is alone on it's own, wait some time | ||
| 108 | + // hoping other will come up soon | ||
| 109 | + try { | ||
| 110 | + if (!clusterEventLatch.await(120, TimeUnit.SECONDS)) { | ||
| 111 | + log.info("Starting as single node cluster"); | ||
| 112 | + } | ||
| 113 | + } catch (InterruptedException e) { | ||
| 114 | + log.info("Interrupted waiting for others", e); | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + final TcpCluster cluster; | ||
| 119 | + synchronized (clusterConfig) { | ||
| 120 | + clusterConfig.addRemoteMembers(remoteMembers); | ||
| 82 | 121 | ||
| 83 | - config.setLocalMember(localMember); | 122 | + // Create the cluster. |
| 84 | - config.setRemoteMembers(remoteMembers.toArray(new TcpMember[]{})); | 123 | + cluster = new TcpCluster(clusterConfig); |
| 124 | + } | ||
| 125 | + log.info("Starting cluster: {}", cluster); | ||
| 85 | 126 | ||
| 86 | - // Create the cluster. | ||
| 87 | - TcpCluster cluster = new TcpCluster(config); | ||
| 88 | 127 | ||
| 89 | StateMachine stateMachine = new DatabaseStateMachine(); | 128 | StateMachine stateMachine = new DatabaseStateMachine(); |
| 90 | - ControllerNode thisNode = clusterService.getLocalNode(); | ||
| 91 | // FIXME resolve Chronicle + OSGi issue | 129 | // FIXME resolve Chronicle + OSGi issue |
| 92 | //Log consensusLog = new ChronicleLog(LOG_FILE_PREFIX + "_" + thisNode.id()); | 130 | //Log consensusLog = new ChronicleLog(LOG_FILE_PREFIX + "_" + thisNode.id()); |
| 93 | - Log consensusLog = new InMemoryLog(); | 131 | + Log consensusLog = new KryoRegisteredInMemoryLog(); |
| 94 | 132 | ||
| 95 | copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol); | 133 | copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol); |
| 96 | copycat.start(); | 134 | copycat.start(); |
| ... | @@ -102,6 +140,7 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ... | @@ -102,6 +140,7 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { |
| 102 | 140 | ||
| 103 | @Deactivate | 141 | @Deactivate |
| 104 | public void deactivate() { | 142 | public void deactivate() { |
| 143 | + clusterService.removeListener(clusterEventListener); | ||
| 105 | copycat.stop(); | 144 | copycat.stop(); |
| 106 | log.info("Stopped."); | 145 | log.info("Stopped."); |
| 107 | } | 146 | } |
| ... | @@ -179,6 +218,53 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ... | @@ -179,6 +218,53 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { |
| 179 | 218 | ||
| 180 | } | 219 | } |
| 181 | 220 | ||
| 221 | + private final class InternalClusterEventListener | ||
| 222 | + implements ClusterEventListener { | ||
| 223 | + | ||
| 224 | + @Override | ||
| 225 | + public void event(ClusterEvent event) { | ||
| 226 | + // TODO: Not every node should be part of the consensus ring. | ||
| 227 | + | ||
| 228 | + final ControllerNode node = event.subject(); | ||
| 229 | + final TcpMember tcpMember = new TcpMember(node.ip().toString(), | ||
| 230 | + node.tcpPort()); | ||
| 231 | + | ||
| 232 | + log.trace("{}", event); | ||
| 233 | + switch (event.type()) { | ||
| 234 | + case INSTANCE_ACTIVATED: | ||
| 235 | + case INSTANCE_ADDED: | ||
| 236 | + log.info("{} was added to the cluster", tcpMember); | ||
| 237 | + synchronized (clusterConfig) { | ||
| 238 | + clusterConfig.addRemoteMember(tcpMember); | ||
| 239 | + } | ||
| 240 | + break; | ||
| 241 | + case INSTANCE_DEACTIVATED: | ||
| 242 | + case INSTANCE_REMOVED: | ||
| 243 | + log.info("{} was removed from the cluster", tcpMember); | ||
| 244 | + synchronized (clusterConfig) { | ||
| 245 | + clusterConfig.removeRemoteMember(tcpMember); | ||
| 246 | + } | ||
| 247 | + break; | ||
| 248 | + default: | ||
| 249 | + break; | ||
| 250 | + } | ||
| 251 | + if (copycat != null) { | ||
| 252 | + log.debug("Current cluster: {}", copycat.cluster()); | ||
| 253 | + } | ||
| 254 | + clusterEventLatch.countDown(); | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + public static final class KryoRegisteredInMemoryLog extends InMemoryLog { | ||
| 260 | + public KryoRegisteredInMemoryLog() { | ||
| 261 | + super(); | ||
| 262 | + // required to deserialize object across bundles | ||
| 263 | + super.kryo.register(TcpMember.class, new TcpMemberSerializer()); | ||
| 264 | + super.kryo.register(TcpClusterConfig.class, new TcpClusterConfigSerializer()); | ||
| 265 | + } | ||
| 266 | + } | ||
| 267 | + | ||
| 182 | private class DatabaseOperationResult<R, E extends DatabaseException> implements OptionalResult<R, E> { | 268 | private class DatabaseOperationResult<R, E extends DatabaseException> implements OptionalResult<R, E> { |
| 183 | 269 | ||
| 184 | private final R result; | 270 | private final R result; | ... | ... |
| 1 | package org.onlab.onos.store.service.impl; | 1 | package org.onlab.onos.store.service.impl; |
| 2 | 2 | ||
| 3 | +import static org.slf4j.LoggerFactory.getLogger; | ||
| 4 | + | ||
| 3 | import java.util.ArrayList; | 5 | import java.util.ArrayList; |
| 4 | import java.util.List; | 6 | import java.util.List; |
| 5 | import java.util.Map; | 7 | import java.util.Map; |
| 6 | -import java.util.Set; | ||
| 7 | - | ||
| 8 | import net.kuujo.copycat.Command; | 8 | import net.kuujo.copycat.Command; |
| 9 | import net.kuujo.copycat.Query; | 9 | import net.kuujo.copycat.Query; |
| 10 | import net.kuujo.copycat.StateMachine; | 10 | import net.kuujo.copycat.StateMachine; |
| ... | @@ -16,7 +16,9 @@ import org.onlab.onos.store.service.VersionedValue; | ... | @@ -16,7 +16,9 @@ import org.onlab.onos.store.service.VersionedValue; |
| 16 | import org.onlab.onos.store.service.WriteRequest; | 16 | import org.onlab.onos.store.service.WriteRequest; |
| 17 | import org.onlab.onos.store.service.WriteResult; | 17 | import org.onlab.onos.store.service.WriteResult; |
| 18 | import org.onlab.util.KryoNamespace; | 18 | import org.onlab.util.KryoNamespace; |
| 19 | +import org.slf4j.Logger; | ||
| 19 | 20 | ||
| 21 | +import com.google.common.collect.ImmutableList; | ||
| 20 | import com.google.common.collect.Maps; | 22 | import com.google.common.collect.Maps; |
| 21 | 23 | ||
| 22 | /** | 24 | /** |
| ... | @@ -28,6 +30,8 @@ import com.google.common.collect.Maps; | ... | @@ -28,6 +30,8 @@ import com.google.common.collect.Maps; |
| 28 | */ | 30 | */ |
| 29 | public class DatabaseStateMachine implements StateMachine { | 31 | public class DatabaseStateMachine implements StateMachine { |
| 30 | 32 | ||
| 33 | + private final Logger log = getLogger(getClass()); | ||
| 34 | + | ||
| 31 | public static final KryoSerializer SERIALIZER = new KryoSerializer() { | 35 | public static final KryoSerializer SERIALIZER = new KryoSerializer() { |
| 32 | @Override | 36 | @Override |
| 33 | protected void setupKryoPool() { | 37 | protected void setupKryoPool() { |
| ... | @@ -59,8 +63,8 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -59,8 +63,8 @@ public class DatabaseStateMachine implements StateMachine { |
| 59 | } | 63 | } |
| 60 | 64 | ||
| 61 | @Query | 65 | @Query |
| 62 | - public Set<String> listTables() { | 66 | + public List<String> listTables() { |
| 63 | - return state.getTables().keySet(); | 67 | + return ImmutableList.copyOf(state.getTables().keySet()); |
| 64 | } | 68 | } |
| 65 | 69 | ||
| 66 | @Query | 70 | @Query |
| ... | @@ -72,7 +76,7 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -72,7 +76,7 @@ public class DatabaseStateMachine implements StateMachine { |
| 72 | results.add(new InternalReadResult(InternalReadResult.Status.NO_SUCH_TABLE, null)); | 76 | results.add(new InternalReadResult(InternalReadResult.Status.NO_SUCH_TABLE, null)); |
| 73 | continue; | 77 | continue; |
| 74 | } | 78 | } |
| 75 | - VersionedValue value = table.get(request.key()); | 79 | + VersionedValue value = VersionedValue.copy(table.get(request.key())); |
| 76 | results.add(new InternalReadResult( | 80 | results.add(new InternalReadResult( |
| 77 | InternalReadResult.Status.OK, | 81 | InternalReadResult.Status.OK, |
| 78 | new ReadResult( | 82 | new ReadResult( |
| ... | @@ -85,6 +89,8 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -85,6 +89,8 @@ public class DatabaseStateMachine implements StateMachine { |
| 85 | 89 | ||
| 86 | @Command | 90 | @Command |
| 87 | public List<InternalWriteResult> write(List<WriteRequest> requests) { | 91 | public List<InternalWriteResult> write(List<WriteRequest> requests) { |
| 92 | + | ||
| 93 | + // applicability check | ||
| 88 | boolean abort = false; | 94 | boolean abort = false; |
| 89 | List<InternalWriteResult.Status> validationResults = new ArrayList<>(requests.size()); | 95 | List<InternalWriteResult.Status> validationResults = new ArrayList<>(requests.size()); |
| 90 | for (WriteRequest request : requests) { | 96 | for (WriteRequest request : requests) { |
| ... | @@ -128,8 +134,13 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -128,8 +134,13 @@ public class DatabaseStateMachine implements StateMachine { |
| 128 | return results; | 134 | return results; |
| 129 | } | 135 | } |
| 130 | 136 | ||
| 137 | + // apply changes | ||
| 131 | for (WriteRequest request : requests) { | 138 | for (WriteRequest request : requests) { |
| 132 | Map<String, VersionedValue> table = state.getTables().get(request.tableName()); | 139 | Map<String, VersionedValue> table = state.getTables().get(request.tableName()); |
| 140 | + // FIXME: If this method could be called by multiple thread, | ||
| 141 | + // synchronization scope is wrong. | ||
| 142 | + // Whole function including applicability check needs to be protected. | ||
| 143 | + // Confirm copycat's thread safety requirement for StateMachine | ||
| 133 | synchronized (table) { | 144 | synchronized (table) { |
| 134 | VersionedValue previousValue = | 145 | VersionedValue previousValue = |
| 135 | table.put(request.key(), new VersionedValue(request.newValue(), state.nextVersion())); | 146 | table.put(request.key(), new VersionedValue(request.newValue(), state.nextVersion())); |
| ... | @@ -161,8 +172,8 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -161,8 +172,8 @@ public class DatabaseStateMachine implements StateMachine { |
| 161 | try { | 172 | try { |
| 162 | return SERIALIZER.encode(state); | 173 | return SERIALIZER.encode(state); |
| 163 | } catch (Exception e) { | 174 | } catch (Exception e) { |
| 164 | - e.printStackTrace(); | 175 | + log.error("Failed to take snapshot", e); |
| 165 | - return null; | 176 | + throw new SnapshotException(e); |
| 166 | } | 177 | } |
| 167 | } | 178 | } |
| 168 | 179 | ||
| ... | @@ -171,7 +182,8 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -171,7 +182,8 @@ public class DatabaseStateMachine implements StateMachine { |
| 171 | try { | 182 | try { |
| 172 | this.state = SERIALIZER.decode(data); | 183 | this.state = SERIALIZER.decode(data); |
| 173 | } catch (Exception e) { | 184 | } catch (Exception e) { |
| 174 | - e.printStackTrace(); | 185 | + log.error("Failed to install from snapshot", e); |
| 186 | + throw new SnapshotException(e); | ||
| 175 | } | 187 | } |
| 176 | } | 188 | } |
| 177 | } | 189 | } | ... | ... |
| 1 | +package org.onlab.onos.store.service.impl; | ||
| 2 | + | ||
| 3 | +import static com.google.common.base.Preconditions.checkArgument; | ||
| 4 | +import static com.google.common.base.Preconditions.checkState; | ||
| 5 | + | ||
| 6 | +import java.io.File; | ||
| 7 | +import java.io.IOException; | ||
| 8 | +import java.util.ArrayList; | ||
| 9 | +import java.util.Arrays; | ||
| 10 | +import java.util.List; | ||
| 11 | +import java.util.concurrent.ConcurrentNavigableMap; | ||
| 12 | + | ||
| 13 | +import net.kuujo.copycat.log.Entry; | ||
| 14 | +import net.kuujo.copycat.log.Log; | ||
| 15 | +import net.kuujo.copycat.log.LogIndexOutOfBoundsException; | ||
| 16 | + | ||
| 17 | +import org.mapdb.Atomic; | ||
| 18 | +import org.mapdb.BTreeMap; | ||
| 19 | +import org.mapdb.DB; | ||
| 20 | +import org.mapdb.DBMaker; | ||
| 21 | +import org.mapdb.TxBlock; | ||
| 22 | +import org.mapdb.TxMaker; | ||
| 23 | +import org.onlab.onos.store.serializers.StoreSerializer; | ||
| 24 | + | ||
| 25 | +import com.google.common.collect.Lists; | ||
| 26 | + | ||
| 27 | +/** | ||
| 28 | + * MapDB based log implementation. | ||
| 29 | + */ | ||
| 30 | +public class MapDBLog implements Log { | ||
| 31 | + | ||
| 32 | + private final File dbFile; | ||
| 33 | + private TxMaker txMaker; | ||
| 34 | + private final StoreSerializer serializer; | ||
| 35 | + private static final String LOG_NAME = "log"; | ||
| 36 | + private static final String SIZE_FIELD_NAME = "size"; | ||
| 37 | + | ||
| 38 | + public MapDBLog(File dbFile, StoreSerializer serializer) { | ||
| 39 | + this.dbFile = dbFile; | ||
| 40 | + this.serializer = serializer; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + @Override | ||
| 44 | + public void open() throws IOException { | ||
| 45 | + txMaker = DBMaker | ||
| 46 | + .newFileDB(dbFile) | ||
| 47 | + .makeTxMaker(); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @Override | ||
| 51 | + public void close() throws IOException { | ||
| 52 | + assertIsOpen(); | ||
| 53 | + txMaker.close(); | ||
| 54 | + txMaker = null; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + @Override | ||
| 58 | + public boolean isOpen() { | ||
| 59 | + return txMaker != null; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + protected void assertIsOpen() { | ||
| 63 | + checkState(isOpen(), "The log is not currently open."); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + @Override | ||
| 67 | + public long appendEntry(Entry entry) { | ||
| 68 | + checkArgument(entry != null, "expecting non-null entry"); | ||
| 69 | + return appendEntries(entry).get(0); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + @Override | ||
| 73 | + public List<Long> appendEntries(Entry... entries) { | ||
| 74 | + checkArgument(entries != null, "expecting non-null entries"); | ||
| 75 | + return appendEntries(Arrays.asList(entries)); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + @Override | ||
| 79 | + public List<Long> appendEntries(List<Entry> entries) { | ||
| 80 | + assertIsOpen(); | ||
| 81 | + checkArgument(entries != null, "expecting non-null entries"); | ||
| 82 | + final List<Long> indices = Lists.newArrayList(); | ||
| 83 | + | ||
| 84 | + txMaker.execute(new TxBlock() { | ||
| 85 | + @Override | ||
| 86 | + public void tx(DB db) { | ||
| 87 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 88 | + Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME); | ||
| 89 | + long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1; | ||
| 90 | + for (Entry entry : entries) { | ||
| 91 | + byte[] entryBytes = serializer.encode(entry); | ||
| 92 | + log.put(nextIndex, entryBytes); | ||
| 93 | + size.addAndGet(entryBytes.length); | ||
| 94 | + indices.add(nextIndex); | ||
| 95 | + nextIndex++; | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + }); | ||
| 99 | + | ||
| 100 | + return indices; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + @Override | ||
| 104 | + public boolean containsEntry(long index) { | ||
| 105 | + assertIsOpen(); | ||
| 106 | + DB db = txMaker.makeTx(); | ||
| 107 | + try { | ||
| 108 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 109 | + return log.containsKey(index); | ||
| 110 | + } finally { | ||
| 111 | + db.close(); | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + @Override | ||
| 116 | + public void delete() throws IOException { | ||
| 117 | + assertIsOpen(); | ||
| 118 | + txMaker.execute(new TxBlock() { | ||
| 119 | + @Override | ||
| 120 | + public void tx(DB db) { | ||
| 121 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 122 | + Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME); | ||
| 123 | + log.clear(); | ||
| 124 | + size.set(0); | ||
| 125 | + } | ||
| 126 | + }); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + @Override | ||
| 130 | + public <T extends Entry> T firstEntry() { | ||
| 131 | + assertIsOpen(); | ||
| 132 | + DB db = txMaker.makeTx(); | ||
| 133 | + try { | ||
| 134 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 135 | + return log.isEmpty() ? null : serializer.decode(log.firstEntry().getValue()); | ||
| 136 | + } finally { | ||
| 137 | + db.close(); | ||
| 138 | + } | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + @Override | ||
| 142 | + public long firstIndex() { | ||
| 143 | + assertIsOpen(); | ||
| 144 | + DB db = txMaker.makeTx(); | ||
| 145 | + try { | ||
| 146 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 147 | + return log.isEmpty() ? 0 : log.firstKey(); | ||
| 148 | + } finally { | ||
| 149 | + db.close(); | ||
| 150 | + } | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + @Override | ||
| 154 | + public <T extends Entry> List<T> getEntries(long from, long to) { | ||
| 155 | + assertIsOpen(); | ||
| 156 | + DB db = txMaker.makeTx(); | ||
| 157 | + try { | ||
| 158 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 159 | + if (log.isEmpty()) { | ||
| 160 | + throw new LogIndexOutOfBoundsException("Log is empty"); | ||
| 161 | + } else if (from < log.firstKey()) { | ||
| 162 | + throw new LogIndexOutOfBoundsException("From index out of bounds."); | ||
| 163 | + } else if (to > log.lastKey()) { | ||
| 164 | + throw new LogIndexOutOfBoundsException("To index out of bounds."); | ||
| 165 | + } | ||
| 166 | + List<T> entries = new ArrayList<>((int) (to - from + 1)); | ||
| 167 | + for (long i = from; i <= to; i++) { | ||
| 168 | + T entry = serializer.decode(log.get(i)); | ||
| 169 | + entries.add(entry); | ||
| 170 | + } | ||
| 171 | + return entries; | ||
| 172 | + } finally { | ||
| 173 | + db.close(); | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + @Override | ||
| 178 | + public <T extends Entry> T getEntry(long index) { | ||
| 179 | + assertIsOpen(); | ||
| 180 | + DB db = txMaker.makeTx(); | ||
| 181 | + try { | ||
| 182 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 183 | + byte[] entryBytes = log.get(index); | ||
| 184 | + return entryBytes == null ? null : serializer.decode(entryBytes); | ||
| 185 | + } finally { | ||
| 186 | + db.close(); | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + @Override | ||
| 191 | + public boolean isEmpty() { | ||
| 192 | + assertIsOpen(); | ||
| 193 | + DB db = txMaker.makeTx(); | ||
| 194 | + try { | ||
| 195 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 196 | + return log.isEmpty(); | ||
| 197 | + } finally { | ||
| 198 | + db.close(); | ||
| 199 | + } | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + @Override | ||
| 203 | + public <T extends Entry> T lastEntry() { | ||
| 204 | + assertIsOpen(); | ||
| 205 | + DB db = txMaker.makeTx(); | ||
| 206 | + try { | ||
| 207 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 208 | + return log.isEmpty() ? null : serializer.decode(log.lastEntry().getValue()); | ||
| 209 | + } finally { | ||
| 210 | + db.close(); | ||
| 211 | + } | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + @Override | ||
| 215 | + public long lastIndex() { | ||
| 216 | + assertIsOpen(); | ||
| 217 | + DB db = txMaker.makeTx(); | ||
| 218 | + try { | ||
| 219 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 220 | + return log.isEmpty() ? 0 : log.lastKey(); | ||
| 221 | + } finally { | ||
| 222 | + db.close(); | ||
| 223 | + } | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + @Override | ||
| 227 | + public void removeAfter(long index) { | ||
| 228 | + assertIsOpen(); | ||
| 229 | + txMaker.execute(new TxBlock() { | ||
| 230 | + @Override | ||
| 231 | + public void tx(DB db) { | ||
| 232 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 233 | + Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME); | ||
| 234 | + long startIndex = index + 1; | ||
| 235 | + long endIndex = log.lastKey(); | ||
| 236 | + for (long i = startIndex; i <= endIndex; ++i) { | ||
| 237 | + byte[] entryBytes = log.remove(i); | ||
| 238 | + size.addAndGet(-1L * entryBytes.length); | ||
| 239 | + } | ||
| 240 | + } | ||
| 241 | + }); | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + @Override | ||
| 245 | + public long size() { | ||
| 246 | + assertIsOpen(); | ||
| 247 | + DB db = txMaker.makeTx(); | ||
| 248 | + try { | ||
| 249 | + Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME); | ||
| 250 | + return size.get(); | ||
| 251 | + } finally { | ||
| 252 | + db.close(); | ||
| 253 | + } | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + @Override | ||
| 257 | + public void sync() throws IOException { | ||
| 258 | + assertIsOpen(); | ||
| 259 | + } | ||
| 260 | + | ||
| 261 | + @Override | ||
| 262 | + public void compact(long index, Entry entry) throws IOException { | ||
| 263 | + | ||
| 264 | + assertIsOpen(); | ||
| 265 | + txMaker.execute(new TxBlock() { | ||
| 266 | + @Override | ||
| 267 | + public void tx(DB db) { | ||
| 268 | + BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME); | ||
| 269 | + Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME); | ||
| 270 | + ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index); | ||
| 271 | + long deletedBytes = headMap.keySet().stream().mapToLong(i -> log.remove(i).length).sum(); | ||
| 272 | + size.addAndGet(-1 * deletedBytes); | ||
| 273 | + byte[] entryBytes = serializer.encode(entry); | ||
| 274 | + byte[] existingEntry = log.put(index, entryBytes); | ||
| 275 | + size.addAndGet(entryBytes.length - existingEntry.length); | ||
| 276 | + db.compact(); | ||
| 277 | + } | ||
| 278 | + }); | ||
| 279 | + } | ||
| 280 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | +package org.onlab.onos.store.service.impl; | ||
| 2 | + | ||
| 3 | +import org.onlab.onos.store.service.DatabaseException; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * Exception that indicates a problem with the state machine snapshotting. | ||
| 7 | + */ | ||
| 8 | +@SuppressWarnings("serial") | ||
| 9 | +public class SnapshotException extends DatabaseException { | ||
| 10 | + public SnapshotException(Throwable t) { | ||
| 11 | + super(t); | ||
| 12 | + } | ||
| 13 | +} |
core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpClusterConfigSerializer.java
0 → 100644
| 1 | +package org.onlab.onos.store.service.impl; | ||
| 2 | + | ||
| 3 | +import java.util.Collection; | ||
| 4 | + | ||
| 5 | +import net.kuujo.copycat.cluster.TcpClusterConfig; | ||
| 6 | +import net.kuujo.copycat.cluster.TcpMember; | ||
| 7 | + | ||
| 8 | +import com.esotericsoftware.kryo.Kryo; | ||
| 9 | +import com.esotericsoftware.kryo.Serializer; | ||
| 10 | +import com.esotericsoftware.kryo.io.Input; | ||
| 11 | +import com.esotericsoftware.kryo.io.Output; | ||
| 12 | + | ||
| 13 | +public class TcpClusterConfigSerializer extends Serializer<TcpClusterConfig> { | ||
| 14 | + | ||
| 15 | + @Override | ||
| 16 | + public void write(Kryo kryo, Output output, TcpClusterConfig object) { | ||
| 17 | + kryo.writeClassAndObject(output, object.getLocalMember()); | ||
| 18 | + kryo.writeClassAndObject(output, object.getRemoteMembers()); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + @Override | ||
| 22 | + public TcpClusterConfig read(Kryo kryo, Input input, | ||
| 23 | + Class<TcpClusterConfig> type) { | ||
| 24 | + TcpMember localMember = (TcpMember) kryo.readClassAndObject(input); | ||
| 25 | + @SuppressWarnings("unchecked") | ||
| 26 | + Collection<TcpMember> remoteMembers = (Collection<TcpMember>) kryo.readClassAndObject(input); | ||
| 27 | + return new TcpClusterConfig(localMember, remoteMembers); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | +} |
| 1 | +package org.onlab.onos.store.service.impl; | ||
| 2 | + | ||
| 3 | +import net.kuujo.copycat.cluster.TcpMember; | ||
| 4 | + | ||
| 5 | +import com.esotericsoftware.kryo.Kryo; | ||
| 6 | +import com.esotericsoftware.kryo.Serializer; | ||
| 7 | +import com.esotericsoftware.kryo.io.Input; | ||
| 8 | +import com.esotericsoftware.kryo.io.Output; | ||
| 9 | + | ||
| 10 | +public class TcpMemberSerializer extends Serializer<TcpMember> { | ||
| 11 | + | ||
| 12 | + @Override | ||
| 13 | + public void write(Kryo kryo, Output output, TcpMember object) { | ||
| 14 | + output.writeString(object.host()); | ||
| 15 | + output.writeInt(object.port()); | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + @Override | ||
| 19 | + public TcpMember read(Kryo kryo, Input input, Class<TcpMember> type) { | ||
| 20 | + String host = input.readString(); | ||
| 21 | + int port = input.readInt(); | ||
| 22 | + return new TcpMember(host, port); | ||
| 23 | + } | ||
| 24 | +} |
| 1 | +package org.onlab.onos.store.service.impl; | ||
| 2 | + | ||
| 3 | +import java.io.File; | ||
| 4 | +import java.io.IOException; | ||
| 5 | +import java.nio.file.Files; | ||
| 6 | +import java.util.List; | ||
| 7 | + | ||
| 8 | +import net.kuujo.copycat.internal.log.OperationEntry; | ||
| 9 | +import net.kuujo.copycat.log.Entry; | ||
| 10 | +import net.kuujo.copycat.log.Log; | ||
| 11 | + | ||
| 12 | +import org.junit.After; | ||
| 13 | +import org.junit.Assert; | ||
| 14 | +import org.junit.Before; | ||
| 15 | +import org.junit.Test; | ||
| 16 | +import org.onlab.onos.store.serializers.StoreSerializer; | ||
| 17 | + | ||
| 18 | +import com.google.common.testing.EqualsTester; | ||
| 19 | + | ||
| 20 | +/** | ||
| 21 | + * Test the MapDBLog implementation. | ||
| 22 | + */ | ||
| 23 | +public class MapDBLogTest { | ||
| 24 | + | ||
| 25 | + private static final String DB_FILE_NAME = "mapdbTest"; | ||
| 26 | + private static final StoreSerializer SERIALIZER = ClusterMessagingProtocol.SERIALIZER; | ||
| 27 | + private static final Entry TEST_ENTRY1 = new OperationEntry(1, "test1"); | ||
| 28 | + private static final Entry TEST_ENTRY2 = new OperationEntry(2, "test12"); | ||
| 29 | + private static final Entry TEST_ENTRY3 = new OperationEntry(3, "test123"); | ||
| 30 | + private static final Entry TEST_ENTRY4 = new OperationEntry(4, "test1234"); | ||
| 31 | + | ||
| 32 | + private static final Entry TEST_SNAPSHOT_ENTRY = new OperationEntry(5, "snapshot"); | ||
| 33 | + | ||
| 34 | + private static final long TEST_ENTRY1_SIZE = SERIALIZER.encode(TEST_ENTRY1).length; | ||
| 35 | + private static final long TEST_ENTRY2_SIZE = SERIALIZER.encode(TEST_ENTRY2).length; | ||
| 36 | + private static final long TEST_ENTRY3_SIZE = SERIALIZER.encode(TEST_ENTRY3).length; | ||
| 37 | + private static final long TEST_ENTRY4_SIZE = SERIALIZER.encode(TEST_ENTRY4).length; | ||
| 38 | + | ||
| 39 | + private static final long TEST_SNAPSHOT_ENTRY_SIZE = SERIALIZER.encode(TEST_SNAPSHOT_ENTRY).length; | ||
| 40 | + | ||
| 41 | + @Before | ||
| 42 | + public void setUp() throws Exception { | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @After | ||
| 46 | + public void tearDown() throws Exception { | ||
| 47 | + Files.deleteIfExists(new File(DB_FILE_NAME).toPath()); | ||
| 48 | + Files.deleteIfExists(new File(DB_FILE_NAME + ".t").toPath()); | ||
| 49 | + Files.deleteIfExists(new File(DB_FILE_NAME + ".p").toPath()); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + @Test(expected = IllegalStateException.class) | ||
| 53 | + public void testAssertOpen() { | ||
| 54 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 55 | + log.size(); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + @Test | ||
| 59 | + public void testAppendEntry() throws IOException { | ||
| 60 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 61 | + log.open(); | ||
| 62 | + log.appendEntry(TEST_ENTRY1); | ||
| 63 | + OperationEntry first = log.firstEntry(); | ||
| 64 | + OperationEntry last = log.lastEntry(); | ||
| 65 | + new EqualsTester() | ||
| 66 | + .addEqualityGroup(first, last, TEST_ENTRY1) | ||
| 67 | + .testEquals(); | ||
| 68 | + Assert.assertEquals(TEST_ENTRY1_SIZE, log.size()); | ||
| 69 | + Assert.assertEquals(1, log.firstIndex()); | ||
| 70 | + Assert.assertEquals(1, log.lastIndex()); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + @Test | ||
| 74 | + public void testAppendEntries() throws IOException { | ||
| 75 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 76 | + log.open(); | ||
| 77 | + log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3); | ||
| 78 | + OperationEntry first = log.firstEntry(); | ||
| 79 | + OperationEntry last = log.lastEntry(); | ||
| 80 | + new EqualsTester() | ||
| 81 | + .addEqualityGroup(first, TEST_ENTRY1) | ||
| 82 | + .addEqualityGroup(last, TEST_ENTRY3) | ||
| 83 | + .testEquals(); | ||
| 84 | + Assert.assertEquals(TEST_ENTRY1_SIZE + TEST_ENTRY2_SIZE, TEST_ENTRY3_SIZE, log.size()); | ||
| 85 | + Assert.assertEquals(1, log.firstIndex()); | ||
| 86 | + Assert.assertEquals(3, log.lastIndex()); | ||
| 87 | + Assert.assertTrue(log.containsEntry(1)); | ||
| 88 | + Assert.assertTrue(log.containsEntry(2)); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + @Test | ||
| 92 | + public void testDelete() throws IOException { | ||
| 93 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 94 | + log.open(); | ||
| 95 | + log.appendEntries(TEST_ENTRY1, TEST_ENTRY2); | ||
| 96 | + log.delete(); | ||
| 97 | + Assert.assertEquals(0, log.size()); | ||
| 98 | + Assert.assertTrue(log.isEmpty()); | ||
| 99 | + Assert.assertEquals(0, log.firstIndex()); | ||
| 100 | + Assert.assertNull(log.firstEntry()); | ||
| 101 | + Assert.assertEquals(0, log.lastIndex()); | ||
| 102 | + Assert.assertNull(log.lastEntry()); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + @Test | ||
| 106 | + public void testGetEntries() throws IOException { | ||
| 107 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 108 | + log.open(); | ||
| 109 | + log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4); | ||
| 110 | + Assert.assertEquals( | ||
| 111 | + TEST_ENTRY1_SIZE + | ||
| 112 | + TEST_ENTRY2_SIZE + | ||
| 113 | + TEST_ENTRY3_SIZE + | ||
| 114 | + TEST_ENTRY4_SIZE, log.size()); | ||
| 115 | + | ||
| 116 | + List<Entry> entries = log.getEntries(2, 3); | ||
| 117 | + new EqualsTester() | ||
| 118 | + .addEqualityGroup(log.getEntry(4), TEST_ENTRY4) | ||
| 119 | + .addEqualityGroup(entries.get(0), TEST_ENTRY2) | ||
| 120 | + .addEqualityGroup(entries.get(1), TEST_ENTRY3) | ||
| 121 | + .testEquals(); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + @Test | ||
| 125 | + public void testRemoveAfter() throws IOException { | ||
| 126 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 127 | + log.open(); | ||
| 128 | + log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4); | ||
| 129 | + log.removeAfter(1); | ||
| 130 | + Assert.assertEquals(TEST_ENTRY1_SIZE, log.size()); | ||
| 131 | + new EqualsTester() | ||
| 132 | + .addEqualityGroup(log.firstEntry(), log.lastEntry(), TEST_ENTRY1) | ||
| 133 | + .testEquals(); | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + @Test | ||
| 137 | + public void testAddAfterRemove() throws IOException { | ||
| 138 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 139 | + log.open(); | ||
| 140 | + log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4); | ||
| 141 | + log.removeAfter(1); | ||
| 142 | + log.appendEntry(TEST_ENTRY4); | ||
| 143 | + Assert.assertEquals(TEST_ENTRY1_SIZE + TEST_ENTRY4_SIZE, log.size()); | ||
| 144 | + new EqualsTester() | ||
| 145 | + .addEqualityGroup(log.firstEntry(), TEST_ENTRY1) | ||
| 146 | + .addEqualityGroup(log.lastEntry(), TEST_ENTRY4) | ||
| 147 | + .addEqualityGroup(log.size(), TEST_ENTRY1_SIZE + TEST_ENTRY4_SIZE) | ||
| 148 | + .testEquals(); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + @Test | ||
| 152 | + public void testClose() throws IOException { | ||
| 153 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 154 | + Assert.assertFalse(log.isOpen()); | ||
| 155 | + log.open(); | ||
| 156 | + Assert.assertTrue(log.isOpen()); | ||
| 157 | + log.close(); | ||
| 158 | + Assert.assertFalse(log.isOpen()); | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + @Test | ||
| 162 | + public void testReopen() throws IOException { | ||
| 163 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 164 | + log.open(); | ||
| 165 | + log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4); | ||
| 166 | + log.close(); | ||
| 167 | + log.open(); | ||
| 168 | + | ||
| 169 | + new EqualsTester() | ||
| 170 | + .addEqualityGroup(log.firstEntry(), TEST_ENTRY1) | ||
| 171 | + .addEqualityGroup(log.getEntry(2), TEST_ENTRY2) | ||
| 172 | + .addEqualityGroup(log.lastEntry(), TEST_ENTRY4) | ||
| 173 | + .addEqualityGroup(log.size(), | ||
| 174 | + TEST_ENTRY1_SIZE + | ||
| 175 | + TEST_ENTRY2_SIZE + | ||
| 176 | + TEST_ENTRY3_SIZE + | ||
| 177 | + TEST_ENTRY4_SIZE) | ||
| 178 | + .testEquals(); | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + @Test | ||
| 182 | + public void testCompact() throws IOException { | ||
| 183 | + Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER); | ||
| 184 | + log.open(); | ||
| 185 | + log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4); | ||
| 186 | + log.compact(3, TEST_SNAPSHOT_ENTRY); | ||
| 187 | + new EqualsTester() | ||
| 188 | + .addEqualityGroup(log.firstEntry(), TEST_SNAPSHOT_ENTRY) | ||
| 189 | + .addEqualityGroup(log.lastEntry(), TEST_ENTRY4) | ||
| 190 | + .addEqualityGroup(log.size(), | ||
| 191 | + TEST_SNAPSHOT_ENTRY_SIZE + | ||
| 192 | + TEST_ENTRY4_SIZE) | ||
| 193 | + .testEquals(); | ||
| 194 | + } | ||
| 195 | +} |
| ... | @@ -103,6 +103,20 @@ import com.google.common.collect.ImmutableSet; | ... | @@ -103,6 +103,20 @@ import com.google.common.collect.ImmutableSet; |
| 103 | 103 | ||
| 104 | public final class KryoNamespaces { | 104 | public final class KryoNamespaces { |
| 105 | 105 | ||
| 106 | + public static final KryoNamespace BASIC = KryoNamespace.newBuilder() | ||
| 107 | + .register(ImmutableMap.class, new ImmutableMapSerializer()) | ||
| 108 | + .register(ImmutableList.class, new ImmutableListSerializer()) | ||
| 109 | + .register(ImmutableSet.class, new ImmutableSetSerializer()) | ||
| 110 | + .register( | ||
| 111 | + ArrayList.class, | ||
| 112 | + Arrays.asList().getClass(), | ||
| 113 | + HashMap.class, | ||
| 114 | + HashSet.class, | ||
| 115 | + LinkedList.class, | ||
| 116 | + byte[].class | ||
| 117 | + ) | ||
| 118 | + .build(); | ||
| 119 | + | ||
| 106 | /** | 120 | /** |
| 107 | * KryoNamespace which can serialize ON.lab misc classes. | 121 | * KryoNamespace which can serialize ON.lab misc classes. |
| 108 | */ | 122 | */ |
| ... | @@ -123,19 +137,8 @@ public final class KryoNamespaces { | ... | @@ -123,19 +137,8 @@ public final class KryoNamespaces { |
| 123 | */ | 137 | */ |
| 124 | public static final KryoNamespace API = KryoNamespace.newBuilder() | 138 | public static final KryoNamespace API = KryoNamespace.newBuilder() |
| 125 | .register(MISC) | 139 | .register(MISC) |
| 126 | - .register(ImmutableMap.class, new ImmutableMapSerializer()) | 140 | + .register(BASIC) |
| 127 | - .register(ImmutableList.class, new ImmutableListSerializer()) | ||
| 128 | - .register(ImmutableSet.class, new ImmutableSetSerializer()) | ||
| 129 | .register( | 141 | .register( |
| 130 | - // | ||
| 131 | - ArrayList.class, | ||
| 132 | - Arrays.asList().getClass(), | ||
| 133 | - HashMap.class, | ||
| 134 | - HashSet.class, | ||
| 135 | - LinkedList.class, | ||
| 136 | - byte[].class, | ||
| 137 | - // | ||
| 138 | - // | ||
| 139 | ControllerNode.State.class, | 142 | ControllerNode.State.class, |
| 140 | Device.Type.class, | 143 | Device.Type.class, |
| 141 | Port.Type.class, | 144 | Port.Type.class, | ... | ... |
This diff is collapsed. Click to expand it.
| ... | @@ -15,6 +15,8 @@ | ... | @@ -15,6 +15,8 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onlab.onos.store.trivial.impl; | 16 | package org.onlab.onos.store.trivial.impl; |
| 17 | 17 | ||
| 18 | +import java.util.ArrayList; | ||
| 19 | +import java.util.List; | ||
| 18 | import java.util.Set; | 20 | import java.util.Set; |
| 19 | import java.util.concurrent.atomic.AtomicInteger; | 21 | import java.util.concurrent.atomic.AtomicInteger; |
| 20 | 22 | ||
| ... | @@ -22,6 +24,7 @@ import org.junit.After; | ... | @@ -22,6 +24,7 @@ import org.junit.After; |
| 22 | import org.junit.Before; | 24 | import org.junit.Before; |
| 23 | import org.junit.Test; | 25 | import org.junit.Test; |
| 24 | import org.onlab.onos.cluster.NodeId; | 26 | import org.onlab.onos.cluster.NodeId; |
| 27 | +import org.onlab.onos.mastership.MastershipEvent; | ||
| 25 | import org.onlab.onos.mastership.MastershipTerm; | 28 | import org.onlab.onos.mastership.MastershipTerm; |
| 26 | import org.onlab.onos.net.DeviceId; | 29 | import org.onlab.onos.net.DeviceId; |
| 27 | 30 | ||
| ... | @@ -74,6 +77,7 @@ public class SimpleMastershipStoreTest { | ... | @@ -74,6 +77,7 @@ public class SimpleMastershipStoreTest { |
| 74 | assertEquals("wrong role", MASTER, sms.getRole(N2, DID3)); | 77 | assertEquals("wrong role", MASTER, sms.getRole(N2, DID3)); |
| 75 | 78 | ||
| 76 | //N2 is master but N1 is only in backups set | 79 | //N2 is master but N1 is only in backups set |
| 80 | + put(DID4, N1, false, true); | ||
| 77 | put(DID4, N2, true, false); | 81 | put(DID4, N2, true, false); |
| 78 | assertEquals("wrong role", STANDBY, sms.getRole(N1, DID4)); | 82 | assertEquals("wrong role", STANDBY, sms.getRole(N1, DID4)); |
| 79 | } | 83 | } |
| ... | @@ -127,12 +131,12 @@ public class SimpleMastershipStoreTest { | ... | @@ -127,12 +131,12 @@ public class SimpleMastershipStoreTest { |
| 127 | put(DID1, N1, false, false); | 131 | put(DID1, N1, false, false); |
| 128 | assertEquals("wrong role", MASTER, sms.requestRole(DID1)); | 132 | assertEquals("wrong role", MASTER, sms.requestRole(DID1)); |
| 129 | 133 | ||
| 130 | - //STANDBY without backup - become MASTER | 134 | + //was STANDBY - become MASTER |
| 131 | put(DID2, N1, false, true); | 135 | put(DID2, N1, false, true); |
| 132 | assertEquals("wrong role", MASTER, sms.requestRole(DID2)); | 136 | assertEquals("wrong role", MASTER, sms.requestRole(DID2)); |
| 133 | 137 | ||
| 134 | - //STANDBY with backup - stay STANDBY | 138 | + //other MASTER - stay STANDBY |
| 135 | - put(DID3, N2, false, true); | 139 | + put(DID3, N2, true, false); |
| 136 | assertEquals("wrong role", STANDBY, sms.requestRole(DID3)); | 140 | assertEquals("wrong role", STANDBY, sms.requestRole(DID3)); |
| 137 | 141 | ||
| 138 | //local (N1) is MASTER - stay MASTER | 142 | //local (N1) is MASTER - stay MASTER |
| ... | @@ -145,30 +149,34 @@ public class SimpleMastershipStoreTest { | ... | @@ -145,30 +149,34 @@ public class SimpleMastershipStoreTest { |
| 145 | //NONE - record backup but take no other action | 149 | //NONE - record backup but take no other action |
| 146 | put(DID1, N1, false, false); | 150 | put(DID1, N1, false, false); |
| 147 | sms.setStandby(N1, DID1); | 151 | sms.setStandby(N1, DID1); |
| 148 | - assertTrue("not backed up", sms.backups.contains(N1)); | 152 | + assertTrue("not backed up", sms.backups.get(DID1).contains(N1)); |
| 149 | - sms.termMap.clear(); | 153 | + int prev = sms.termMap.get(DID1).get(); |
| 150 | sms.setStandby(N1, DID1); | 154 | sms.setStandby(N1, DID1); |
| 151 | - assertTrue("term not set", sms.termMap.containsKey(DID1)); | 155 | + assertEquals("term should not change", prev, sms.termMap.get(DID1).get()); |
| 152 | 156 | ||
| 153 | //no backup, MASTER | 157 | //no backup, MASTER |
| 154 | - put(DID1, N1, true, true); | 158 | + put(DID1, N1, true, false); |
| 155 | - assertNull("wrong event", sms.setStandby(N1, DID1)); | 159 | + assertNull("expect no MASTER event", sms.setStandby(N1, DID1).roleInfo().master()); |
| 156 | assertNull("wrong node", sms.masterMap.get(DID1)); | 160 | assertNull("wrong node", sms.masterMap.get(DID1)); |
| 157 | 161 | ||
| 158 | //backup, switch | 162 | //backup, switch |
| 159 | sms.masterMap.clear(); | 163 | sms.masterMap.clear(); |
| 160 | put(DID1, N1, true, true); | 164 | put(DID1, N1, true, true); |
| 165 | + put(DID1, N2, false, true); | ||
| 161 | put(DID2, N2, true, true); | 166 | put(DID2, N2, true, true); |
| 162 | - assertEquals("wrong event", MASTER_CHANGED, sms.setStandby(N1, DID1).type()); | 167 | + MastershipEvent event = sms.setStandby(N1, DID1); |
| 168 | + assertEquals("wrong event", MASTER_CHANGED, event.type()); | ||
| 169 | + assertEquals("wrong master", N2, event.roleInfo().master()); | ||
| 163 | } | 170 | } |
| 164 | 171 | ||
| 165 | //helper to populate master/backup structures | 172 | //helper to populate master/backup structures |
| 166 | - private void put(DeviceId dev, NodeId node, boolean store, boolean backup) { | 173 | + private void put(DeviceId dev, NodeId node, boolean master, boolean backup) { |
| 167 | - if (store) { | 174 | + if (master) { |
| 168 | sms.masterMap.put(dev, node); | 175 | sms.masterMap.put(dev, node); |
| 169 | - } | 176 | + } else if (backup) { |
| 170 | - if (backup) { | 177 | + List<NodeId> stbys = sms.backups.getOrDefault(dev, new ArrayList<>()); |
| 171 | - sms.backups.add(node); | 178 | + stbys.add(node); |
| 179 | + sms.backups.put(dev, stbys); | ||
| 172 | } | 180 | } |
| 173 | sms.termMap.put(dev, new AtomicInteger()); | 181 | sms.termMap.put(dev, new AtomicInteger()); |
| 174 | } | 182 | } | ... | ... |
| ... | @@ -56,6 +56,9 @@ | ... | @@ -56,6 +56,9 @@ |
| 56 | <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle> | 56 | <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle> |
| 57 | 57 | ||
| 58 | <bundle>mvn:org.onlab.onos/onlab-thirdparty/1.0.0-SNAPSHOT</bundle> | 58 | <bundle>mvn:org.onlab.onos/onlab-thirdparty/1.0.0-SNAPSHOT</bundle> |
| 59 | + | ||
| 60 | + <bundle>mvn:org.mapdb/mapdb/1.0.6</bundle> | ||
| 61 | + | ||
| 59 | <!-- FIXME: resolce Chronicle's dependency issue | 62 | <!-- FIXME: resolce Chronicle's dependency issue |
| 60 | <bundle>mvn:net.openhft/lang/6.4.6</bundle> | 63 | <bundle>mvn:net.openhft/lang/6.4.6</bundle> |
| 61 | <bundle>mvn:net.openhft/affinity/2.1.1</bundle> | 64 | <bundle>mvn:net.openhft/affinity/2.1.1</bundle> | ... | ... |
This diff is collapsed. Click to expand it.
| ... | @@ -15,7 +15,7 @@ | ... | @@ -15,7 +15,7 @@ |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | /* | 17 | /* |
| 18 | - ONOS GUI -- Masthead | 18 | + ONOS GUI -- Masthead script |
| 19 | 19 | ||
| 20 | Defines the masthead for the UI. Injects logo and title, as well as providing | 20 | Defines the masthead for the UI. Injects logo and title, as well as providing |
| 21 | the placeholder for a set of radio buttons. | 21 | the placeholder for a set of radio buttons. |
| ... | @@ -53,4 +53,4 @@ | ... | @@ -53,4 +53,4 @@ |
| 53 | class: 'right' | 53 | class: 'right' |
| 54 | }); | 54 | }); |
| 55 | 55 | ||
| 56 | -}(ONOS)); | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 56 | +}(ONOS)); | ... | ... |
| ... | @@ -407,7 +407,8 @@ | ... | @@ -407,7 +407,8 @@ |
| 407 | height: this.height, | 407 | height: this.height, |
| 408 | uid: this.uid, | 408 | uid: this.uid, |
| 409 | setRadio: this.setRadio, | 409 | setRadio: this.setRadio, |
| 410 | - setKeys: this.setKeys | 410 | + setKeys: this.setKeys, |
| 411 | + dataLoadError: this.dataLoadError | ||
| 411 | } | 412 | } |
| 412 | }, | 413 | }, |
| 413 | 414 | ||
| ... | @@ -498,6 +499,16 @@ | ... | @@ -498,6 +499,16 @@ |
| 498 | 499 | ||
| 499 | uid: function (id) { | 500 | uid: function (id) { |
| 500 | return makeUid(this, id); | 501 | return makeUid(this, id); |
| 502 | + }, | ||
| 503 | + | ||
| 504 | + // TODO : implement custom dialogs (don't use alerts) | ||
| 505 | + | ||
| 506 | + dataLoadError: function (err, url) { | ||
| 507 | + var msg = 'Data Load Error\n\n' + | ||
| 508 | + err.status + ' -- ' + err.statusText + '\n\n' + | ||
| 509 | + 'relative-url: "' + url + '"\n\n' + | ||
| 510 | + 'complete-url: "' + err.responseURL + '"'; | ||
| 511 | + alert(msg); | ||
| 501 | } | 512 | } |
| 502 | 513 | ||
| 503 | // TODO: consider schedule, clearTimer, etc. | 514 | // TODO: consider schedule, clearTimer, etc. | ... | ... |
| ... | @@ -25,7 +25,7 @@ | ... | @@ -25,7 +25,7 @@ |
| 25 | 25 | ||
| 26 | // configuration data | 26 | // configuration data |
| 27 | var config = { | 27 | var config = { |
| 28 | - useLiveData: false, | 28 | + useLiveData: true, |
| 29 | debugOn: false, | 29 | debugOn: false, |
| 30 | debug: { | 30 | debug: { |
| 31 | showNodeXY: false, | 31 | showNodeXY: false, |
| ... | @@ -56,12 +56,24 @@ | ... | @@ -56,12 +56,24 @@ |
| 56 | opt: 'img/opt.png' | 56 | opt: 'img/opt.png' |
| 57 | }, | 57 | }, |
| 58 | force: { | 58 | force: { |
| 59 | - marginLR: 20, | 59 | + note: 'node.class or link.class is used to differentiate', |
| 60 | - marginTB: 20, | 60 | + linkDistance: { |
| 61 | + infra: 200, | ||
| 62 | + host: 40 | ||
| 63 | + }, | ||
| 64 | + linkStrength: { | ||
| 65 | + infra: 1.0, | ||
| 66 | + host: 1.0 | ||
| 67 | + }, | ||
| 68 | + charge: { | ||
| 69 | + device: -400, | ||
| 70 | + host: -100 | ||
| 71 | + }, | ||
| 72 | + pad: 20, | ||
| 61 | translate: function() { | 73 | translate: function() { |
| 62 | return 'translate(' + | 74 | return 'translate(' + |
| 63 | - config.force.marginLR + ',' + | 75 | + config.force.pad + ',' + |
| 64 | - config.force.marginTB + ')'; | 76 | + config.force.pad + ')'; |
| 65 | } | 77 | } |
| 66 | } | 78 | } |
| 67 | }; | 79 | }; |
| ... | @@ -94,7 +106,11 @@ | ... | @@ -94,7 +106,11 @@ |
| 94 | // D3 selections | 106 | // D3 selections |
| 95 | var svg, | 107 | var svg, |
| 96 | bgImg, | 108 | bgImg, |
| 97 | - topoG; | 109 | + topoG, |
| 110 | + nodeG, | ||
| 111 | + linkG, | ||
| 112 | + node, | ||
| 113 | + link; | ||
| 98 | 114 | ||
| 99 | // ============================== | 115 | // ============================== |
| 100 | // For Debugging / Development | 116 | // For Debugging / Development |
| ... | @@ -175,23 +191,146 @@ | ... | @@ -175,23 +191,146 @@ |
| 175 | // ============================== | 191 | // ============================== |
| 176 | // Private functions | 192 | // Private functions |
| 177 | 193 | ||
| 178 | - // set the size of the given element to that of the view | 194 | + // set the size of the given element to that of the view (reduced if padded) |
| 179 | - function setSize(el, view) { | 195 | + function setSize(el, view, pad) { |
| 196 | + var padding = pad ? pad * 2 : 0; | ||
| 180 | el.attr({ | 197 | el.attr({ |
| 181 | - width: view.width(), | 198 | + width: view.width() - padding, |
| 182 | - height: view.height() | 199 | + height: view.height() - padding |
| 183 | }); | 200 | }); |
| 184 | } | 201 | } |
| 185 | 202 | ||
| 186 | - | ||
| 187 | function getNetworkData(view) { | 203 | function getNetworkData(view) { |
| 188 | var url = getTopoUrl(); | 204 | var url = getTopoUrl(); |
| 189 | 205 | ||
| 190 | - // TODO ... | 206 | + console.log('Fetching JSON: ' + url); |
| 207 | + d3.json(url, function(err, data) { | ||
| 208 | + if (err) { | ||
| 209 | + view.dataLoadError(err, url); | ||
| 210 | + } else { | ||
| 211 | + network.data = data; | ||
| 212 | + drawNetwork(view); | ||
| 213 | + } | ||
| 214 | + }); | ||
| 215 | + } | ||
| 216 | + | ||
| 217 | + function drawNetwork(view) { | ||
| 218 | + preprocessData(view); | ||
| 219 | + updateLayout(view); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + function preprocessData(view) { | ||
| 223 | + var w = view.width(), | ||
| 224 | + h = view.height(), | ||
| 225 | + hDevice = h * 0.6, | ||
| 226 | + hHost = h * 0.3, | ||
| 227 | + data = network.data, | ||
| 228 | + deviceLayout = computeInitLayout(w, hDevice, data.devices.length), | ||
| 229 | + hostLayout = computeInitLayout(w, hHost, data.hosts.length); | ||
| 230 | + | ||
| 231 | + network.lookup = {}; | ||
| 232 | + network.nodes = []; | ||
| 233 | + network.links = []; | ||
| 234 | + // we created new arrays, so need to set the refs in the force layout | ||
| 235 | + network.force.nodes(network.nodes); | ||
| 236 | + network.force.links(network.links); | ||
| 237 | + | ||
| 238 | + // let's just start with the nodes | ||
| 239 | + | ||
| 240 | + // note that both 'devices' and 'hosts' get mapped into the nodes array | ||
| 241 | + function makeNode(d, cls, layout) { | ||
| 242 | + var node = { | ||
| 243 | + id: d.id, | ||
| 244 | + labels: d.labels, | ||
| 245 | + class: cls, | ||
| 246 | + icon: cls, | ||
| 247 | + type: d.type, | ||
| 248 | + x: layout.x(), | ||
| 249 | + y: layout.y() | ||
| 250 | + }; | ||
| 251 | + network.lookup[d.id] = node; | ||
| 252 | + network.nodes.push(node); | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + // first the devices... | ||
| 256 | + network.data.devices.forEach(function (d) { | ||
| 257 | + makeNode(d, 'device', deviceLayout); | ||
| 258 | + }); | ||
| 259 | + | ||
| 260 | + // then the hosts... | ||
| 261 | + network.data.hosts.forEach(function (d) { | ||
| 262 | + makeNode(d, 'host', hostLayout); | ||
| 263 | + }); | ||
| 264 | + | ||
| 265 | + // TODO: process links | ||
| 266 | + } | ||
| 267 | + | ||
| 268 | + function computeInitLayout(w, h, n) { | ||
| 269 | + var maxdw = 60, | ||
| 270 | + compdw, dw, ox, layout; | ||
| 271 | + | ||
| 272 | + if (n < 2) { | ||
| 273 | + layout = { ox: w/2, dw: 0 } | ||
| 274 | + } else { | ||
| 275 | + compdw = (0.8 * w) / (n - 1); | ||
| 276 | + dw = Math.min(maxdw, compdw); | ||
| 277 | + ox = w/2 - ((n - 1)/2 * dw); | ||
| 278 | + layout = { ox: ox, dw: dw } | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + layout.i = 0; | ||
| 282 | + | ||
| 283 | + layout.x = function () { | ||
| 284 | + var x = layout.ox + layout.i*layout.dw; | ||
| 285 | + layout.i++; | ||
| 286 | + return x; | ||
| 287 | + }; | ||
| 288 | + | ||
| 289 | + layout.y = function () { | ||
| 290 | + return h; | ||
| 291 | + }; | ||
| 292 | + | ||
| 293 | + return layout; | ||
| 294 | + } | ||
| 295 | + | ||
| 296 | + function linkId(d) { | ||
| 297 | + return d.source.id + '~' + d.target.id; | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + function nodeId(d) { | ||
| 301 | + return d.id; | ||
| 302 | + } | ||
| 191 | 303 | ||
| 304 | + function updateLayout(view) { | ||
| 305 | + link = link.data(network.force.links(), linkId); | ||
| 306 | + link.enter().append('line') | ||
| 307 | + .attr('class', 'link'); | ||
| 308 | + link.exit().remove(); | ||
| 309 | + | ||
| 310 | + node = node.data(network.force.nodes(), nodeId); | ||
| 311 | + node.enter().append('circle') | ||
| 312 | + .attr('id', function (d) { return 'nodeId-' + d.id; }) | ||
| 313 | + .attr('class', function (d) { return 'node'; }) | ||
| 314 | + .attr('r', 12); | ||
| 315 | + | ||
| 316 | + network.force.start(); | ||
| 192 | } | 317 | } |
| 193 | 318 | ||
| 194 | 319 | ||
| 320 | + function tick() { | ||
| 321 | + node.attr({ | ||
| 322 | + cx: function(d) { return d.x; }, | ||
| 323 | + cy: function(d) { return d.y; } | ||
| 324 | + }); | ||
| 325 | + | ||
| 326 | + link.attr({ | ||
| 327 | + x1: function (d) { return d.source.x; }, | ||
| 328 | + y1: function (d) { return d.source.y; }, | ||
| 329 | + x2: function (d) { return d.target.x; }, | ||
| 330 | + y2: function (d) { return d.target.y; } | ||
| 331 | + }); | ||
| 332 | + } | ||
| 333 | + | ||
| 195 | // ============================== | 334 | // ============================== |
| 196 | // View life-cycle callbacks | 335 | // View life-cycle callbacks |
| 197 | 336 | ||
| ... | @@ -199,15 +338,15 @@ | ... | @@ -199,15 +338,15 @@ |
| 199 | var w = view.width(), | 338 | var w = view.width(), |
| 200 | h = view.height(), | 339 | h = view.height(), |
| 201 | idBg = view.uid('bg'), | 340 | idBg = view.uid('bg'), |
| 202 | - showBg = config.options.showBackground ? 'visible' : 'hidden'; | 341 | + showBg = config.options.showBackground ? 'visible' : 'hidden', |
| 342 | + fcfg = config.force, | ||
| 343 | + fpad = fcfg.pad, | ||
| 344 | + forceDim = [w - 2*fpad, h - 2*fpad]; | ||
| 203 | 345 | ||
| 204 | // NOTE: view.$div is a D3 selection of the view's div | 346 | // NOTE: view.$div is a D3 selection of the view's div |
| 205 | svg = view.$div.append('svg'); | 347 | svg = view.$div.append('svg'); |
| 206 | setSize(svg, view); | 348 | setSize(svg, view); |
| 207 | 349 | ||
| 208 | - topoG = svg.append('g') | ||
| 209 | - .attr('transform', config.force.translate()); | ||
| 210 | - | ||
| 211 | // load the background image | 350 | // load the background image |
| 212 | bgImg = svg.append('svg:image') | 351 | bgImg = svg.append('svg:image') |
| 213 | .attr({ | 352 | .attr({ |
| ... | @@ -219,6 +358,28 @@ | ... | @@ -219,6 +358,28 @@ |
| 219 | .style({ | 358 | .style({ |
| 220 | visibility: showBg | 359 | visibility: showBg |
| 221 | }); | 360 | }); |
| 361 | + | ||
| 362 | + // group for the topology | ||
| 363 | + topoG = svg.append('g') | ||
| 364 | + .attr('transform', fcfg.translate()); | ||
| 365 | + | ||
| 366 | + // subgroups for links and nodes | ||
| 367 | + linkG = topoG.append('g').attr('id', 'links'); | ||
| 368 | + nodeG = topoG.append('g').attr('id', 'nodes'); | ||
| 369 | + | ||
| 370 | + // selection of nodes and links | ||
| 371 | + link = linkG.selectAll('.link'); | ||
| 372 | + node = nodeG.selectAll('.node'); | ||
| 373 | + | ||
| 374 | + // set up the force layout | ||
| 375 | + network.force = d3.layout.force() | ||
| 376 | + .size(forceDim) | ||
| 377 | + .nodes(network.nodes) | ||
| 378 | + .links(network.links) | ||
| 379 | + .charge(function (d) { return fcfg.charge[d.class]; }) | ||
| 380 | + .linkDistance(function (d) { return fcfg.linkDistance[d.class]; }) | ||
| 381 | + .linkStrength(function (d) { return fcfg.linkStrength[d.class]; }) | ||
| 382 | + .on('tick', tick); | ||
| 222 | } | 383 | } |
| 223 | 384 | ||
| 224 | 385 | ... | ... |
-
Please register or login to post a comment