Praseed Balakrishnan

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 +}
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,
......
...@@ -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.
......
...@@ -24,3 +24,6 @@ svg #topo-bg { ...@@ -24,3 +24,6 @@ svg #topo-bg {
24 opacity: 0.5; 24 opacity: 0.5;
25 } 25 }
26 26
27 +svg .node {
28 + fill: #03c;
29 +}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -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
......