Simon Hunt
Committed by Gerrit Code Review

ONOS-4326: Focusing on add/remove cluster member. (WIP).

If reviewing this, please refer to http://tinyurl.com/onos-ui-topo-model

Change-Id: Ic6568074ac768ec828f9103e92caab5e9a06ade6
1 +/*
2 + * Copyright 2016-present 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 +
17 +package org.onosproject.ui.model;
18 +
19 +import org.onosproject.cluster.ClusterService;
20 +import org.onosproject.mastership.MastershipService;
21 +import org.onosproject.net.device.DeviceService;
22 +import org.onosproject.net.flow.FlowRuleService;
23 +import org.onosproject.net.host.HostService;
24 +import org.onosproject.net.intent.IntentService;
25 +import org.onosproject.net.link.LinkService;
26 +import org.onosproject.net.region.RegionService;
27 +
28 +/**
29 + * A bundle of services to pass to elements that might need a reference to them.
30 + */
31 +public interface ServiceBundle {
32 + /**
33 + * Reference to a cluster service implementation.
34 + *
35 + * @return cluster service
36 + */
37 + ClusterService cluster();
38 +
39 + /**
40 + * Reference to a mastership service implementation.
41 + *
42 + * @return mastership service
43 + */
44 + MastershipService mastership();
45 +
46 + /**
47 + * Reference to a region service implementation.
48 + *
49 + * @return region service
50 + */
51 + RegionService region();
52 +
53 + /**
54 + * Reference to a device service implementation.
55 + *
56 + * @return device service
57 + */
58 + DeviceService device();
59 +
60 + /**
61 + * Reference to a link service implementation.
62 + *
63 + * @return link service
64 + */
65 + LinkService link();
66 +
67 + /**
68 + * Reference to a host service implementation.
69 + *
70 + * @return host service
71 + */
72 + HostService host();
73 +
74 + /**
75 + * Reference to a intent service implementation.
76 + *
77 + * @return intent service
78 + */
79 + IntentService intent();
80 +
81 + /**
82 + * Reference to a flow service implementation.
83 + *
84 + * @return flow service
85 + */
86 + FlowRuleService flow();
87 +}
...@@ -28,12 +28,14 @@ import java.util.Map; ...@@ -28,12 +28,14 @@ import java.util.Map;
28 */ 28 */
29 class UiCluster extends UiElement { 29 class UiCluster extends UiElement {
30 30
31 + private static final String DEFAULT_CLUSTER_ID = "CLUSTER-0";
32 +
31 private final List<UiClusterMember> members = new ArrayList<>(); 33 private final List<UiClusterMember> members = new ArrayList<>();
32 private final Map<NodeId, UiClusterMember> lookup = new HashMap<>(); 34 private final Map<NodeId, UiClusterMember> lookup = new HashMap<>();
33 35
34 @Override 36 @Override
35 public String toString() { 37 public String toString() {
36 - return String.valueOf(members.size()) + "-member cluster"; 38 + return String.valueOf(size()) + "-member cluster";
37 } 39 }
38 40
39 /** 41 /**
...@@ -65,6 +67,16 @@ class UiCluster extends UiElement { ...@@ -65,6 +67,16 @@ class UiCluster extends UiElement {
65 } 67 }
66 68
67 /** 69 /**
70 + * Removes the given member from the cluster.
71 + *
72 + * @param member member to remove
73 + */
74 + public void remove(UiClusterMember member) {
75 + members.remove(member);
76 + lookup.remove(member.id());
77 + }
78 +
79 + /**
68 * Returns the number of members in the cluster. 80 * Returns the number of members in the cluster.
69 * 81 *
70 * @return number of members 82 * @return number of members
...@@ -72,4 +84,9 @@ class UiCluster extends UiElement { ...@@ -72,4 +84,9 @@ class UiCluster extends UiElement {
72 public int size() { 84 public int size() {
73 return members.size(); 85 return members.size();
74 } 86 }
87 +
88 + @Override
89 + public String idAsString() {
90 + return DEFAULT_CLUSTER_ID;
91 + }
75 } 92 }
......
...@@ -16,9 +16,12 @@ ...@@ -16,9 +16,12 @@
16 16
17 package org.onosproject.ui.model.topo; 17 package org.onosproject.ui.model.topo;
18 18
19 +import org.onlab.packet.IpAddress;
19 import org.onosproject.cluster.ControllerNode; 20 import org.onosproject.cluster.ControllerNode;
20 import org.onosproject.cluster.NodeId; 21 import org.onosproject.cluster.NodeId;
21 22
23 +import static org.onosproject.cluster.ControllerNode.State.INACTIVE;
24 +
22 /** 25 /**
23 * Represents an individual member of the cluster (ONOS instance). 26 * Represents an individual member of the cluster (ONOS instance).
24 */ 27 */
...@@ -26,6 +29,9 @@ public class UiClusterMember extends UiElement { ...@@ -26,6 +29,9 @@ public class UiClusterMember extends UiElement {
26 29
27 private final ControllerNode cnode; 30 private final ControllerNode cnode;
28 31
32 + private int deviceCount = 0;
33 + private ControllerNode.State state = INACTIVE;
34 +
29 /** 35 /**
30 * Constructs a cluster member, with a reference to the specified 36 * Constructs a cluster member, with a reference to the specified
31 * controller node instance. 37 * controller node instance.
...@@ -36,13 +42,33 @@ public class UiClusterMember extends UiElement { ...@@ -36,13 +42,33 @@ public class UiClusterMember extends UiElement {
36 this.cnode = cnode; 42 this.cnode = cnode;
37 } 43 }
38 44
45 + @Override
46 + public String toString() {
47 + return "UiClusterMember{" + cnode +
48 + ", online=" + isOnline() +
49 + ", ready=" + isReady() +
50 + ", #devices=" + deviceCount +
51 + "}";
52 + }
53 +
54 +
39 /** 55 /**
40 - * Updates the information about this cluster member. 56 + * Sets the state of this cluster member.
41 * 57 *
42 - * @param cnode underlying controller node 58 + * @param state the state
43 */ 59 */
44 - public void update(ControllerNode cnode) { 60 + public void setState(ControllerNode.State state) {
45 - // TODO: update our information cache appropriately 61 + this.state = state;
62 + }
63 +
64 +
65 + /**
66 + * Sets the number of devices for which this cluster member is master.
67 + *
68 + * @param deviceCount number of devices
69 + */
70 + public void setDeviceCount(int deviceCount) {
71 + this.deviceCount = deviceCount;
46 } 72 }
47 73
48 /** 74 /**
...@@ -53,4 +79,45 @@ public class UiClusterMember extends UiElement { ...@@ -53,4 +79,45 @@ public class UiClusterMember extends UiElement {
53 public NodeId id() { 79 public NodeId id() {
54 return cnode.id(); 80 return cnode.id();
55 } 81 }
82 +
83 + /**
84 + * Returns the IP address of the cluster member.
85 + *
86 + * @return the IP address
87 + */
88 + public IpAddress ip() {
89 + return cnode.ip();
90 + }
91 +
92 + /**
93 + * Returns true if this cluster member is online (active).
94 + *
95 + * @return true if online, false otherwise
96 + */
97 + public boolean isOnline() {
98 + return state.isActive();
99 + }
100 +
101 + /**
102 + * Returns true if this cluster member is considered ready.
103 + *
104 + * @return true if ready, false otherwise
105 + */
106 + public boolean isReady() {
107 + return state.isReady();
108 + }
109 +
110 + /**
111 + * Returns the number of devices for which this cluster member is master.
112 + *
113 + * @return number of devices for which this member is master
114 + */
115 + public int deviceCount() {
116 + return deviceCount;
117 + }
118 +
119 + @Override
120 + public String idAsString() {
121 + return id().toString();
122 + }
56 } 123 }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
17 package org.onosproject.ui.model.topo; 17 package org.onosproject.ui.model.topo;
18 18
19 import org.onosproject.net.Device; 19 import org.onosproject.net.Device;
20 +import org.onosproject.net.DeviceId;
20 21
21 /** 22 /**
22 * Represents a device. 23 * Represents a device.
...@@ -29,4 +30,18 @@ public class UiDevice extends UiNode { ...@@ -29,4 +30,18 @@ public class UiDevice extends UiNode {
29 protected void destroy() { 30 protected void destroy() {
30 device = null; 31 device = null;
31 } 32 }
33 +
34 + /**
35 + * Returns the identity of the device.
36 + *
37 + * @return device ID
38 + */
39 + public DeviceId id() {
40 + return device.id();
41 + }
42 +
43 + @Override
44 + public String idAsString() {
45 + return id().toString();
46 + }
32 } 47 }
......
...@@ -19,7 +19,7 @@ package org.onosproject.ui.model.topo; ...@@ -19,7 +19,7 @@ package org.onosproject.ui.model.topo;
19 /** 19 /**
20 * Abstract base class of all elements in the UI topology model. 20 * Abstract base class of all elements in the UI topology model.
21 */ 21 */
22 -public class UiElement { 22 +public abstract class UiElement {
23 23
24 /** 24 /**
25 * Removes all external references, and prepares the instance for 25 * Removes all external references, and prepares the instance for
...@@ -28,4 +28,11 @@ public class UiElement { ...@@ -28,4 +28,11 @@ public class UiElement {
28 protected void destroy() { 28 protected void destroy() {
29 // does nothing 29 // does nothing
30 } 30 }
31 +
32 + /**
33 + * Returns a string representation of the element identifier.
34 + *
35 + * @return the element unique identifier
36 + */
37 + public abstract String idAsString();
31 } 38 }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
17 package org.onosproject.ui.model.topo; 17 package org.onosproject.ui.model.topo;
18 18
19 import org.onosproject.net.Host; 19 import org.onosproject.net.Host;
20 +import org.onosproject.net.HostId;
20 21
21 /** 22 /**
22 * Represents an end-station host. 23 * Represents an end-station host.
...@@ -29,4 +30,18 @@ public class UiHost extends UiNode { ...@@ -29,4 +30,18 @@ public class UiHost extends UiNode {
29 protected void destroy() { 30 protected void destroy() {
30 host = null; 31 host = null;
31 } 32 }
33 +
34 + /**
35 + * Returns the identity of the host.
36 + *
37 + * @return host ID
38 + */
39 + public HostId id() {
40 + return host.id();
41 + }
42 +
43 + @Override
44 + public String idAsString() {
45 + return id().toString();
46 + }
32 } 47 }
......
...@@ -54,4 +54,10 @@ public class UiLink extends UiElement { ...@@ -54,4 +54,10 @@ public class UiLink extends UiElement {
54 children = null; 54 children = null;
55 } 55 }
56 } 56 }
57 +
58 + @Override
59 + public String idAsString() {
60 + // TODO
61 + return null;
62 + }
57 } 63 }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
17 package org.onosproject.ui.model.topo; 17 package org.onosproject.ui.model.topo;
18 18
19 import org.onosproject.net.region.Region; 19 import org.onosproject.net.region.Region;
20 +import org.onosproject.net.region.RegionId;
20 21
21 import java.util.Set; 22 import java.util.Set;
22 import java.util.TreeSet; 23 import java.util.TreeSet;
...@@ -46,4 +47,17 @@ public class UiRegion extends UiNode { ...@@ -46,4 +47,17 @@ public class UiRegion extends UiNode {
46 region = null; 47 region = null;
47 } 48 }
48 49
50 + /**
51 + * Returns the identity of the region.
52 + *
53 + * @return region ID
54 + */
55 + public RegionId id() {
56 + return region.id();
57 + }
58 +
59 + @Override
60 + public String idAsString() {
61 + return id().toString();
62 + }
49 } 63 }
......
...@@ -28,6 +28,8 @@ import java.util.TreeSet; ...@@ -28,6 +28,8 @@ import java.util.TreeSet;
28 */ 28 */
29 public class UiTopology extends UiElement { 29 public class UiTopology extends UiElement {
30 30
31 + private static final String DEFAULT_TOPOLOGY_ID = "TOPOLOGY-0";
32 +
31 private static final Logger log = LoggerFactory.getLogger(UiTopology.class); 33 private static final Logger log = LoggerFactory.getLogger(UiTopology.class);
32 34
33 private final UiCluster uiCluster = new UiCluster(); 35 private final UiCluster uiCluster = new UiCluster();
...@@ -69,6 +71,15 @@ public class UiTopology extends UiElement { ...@@ -69,6 +71,15 @@ public class UiTopology extends UiElement {
69 } 71 }
70 72
71 /** 73 /**
74 + * Removes the given cluster member from the topology model.
75 + *
76 + * @param member cluster member to remove
77 + */
78 + public void remove(UiClusterMember member) {
79 + uiCluster.remove(member);
80 + }
81 +
82 + /**
72 * Returns the number of members in the cluster. 83 * Returns the number of members in the cluster.
73 * 84 *
74 * @return number of cluster members 85 * @return number of cluster members
...@@ -85,4 +96,9 @@ public class UiTopology extends UiElement { ...@@ -85,4 +96,9 @@ public class UiTopology extends UiElement {
85 public int regionCount() { 96 public int regionCount() {
86 return uiRegions.size(); 97 return uiRegions.size();
87 } 98 }
99 +
100 + @Override
101 + public String idAsString() {
102 + return DEFAULT_TOPOLOGY_ID;
103 + }
88 } 104 }
......
1 +/*
2 + * Copyright 2016-present 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 +
17 +package org.onosproject.ui;
18 +
19 +/**
20 + * Abstract base class for UI tests.
21 + */
22 +public abstract class AbstractUiTest {
23 +
24 + /**
25 + * System agnostic end-of-line character.
26 + */
27 + protected static final String EOL = String.format("%n");
28 +
29 + /**
30 + * Prints the given string to stdout.
31 + *
32 + * @param s string to print
33 + */
34 + protected static void print(String s) {
35 + System.out.println(s);
36 + }
37 +
38 + /**
39 + * Prints the toString() of the given object to stdout.
40 + *
41 + * @param o object to print
42 + */
43 + protected static void print(Object o) {
44 + if (o == null) {
45 + print("<null>");
46 + } else {
47 + print(o.toString());
48 + }
49 + }
50 +
51 + /**
52 + * Prints the formatted string to stdout.
53 + *
54 + * @param fmt format string
55 + * @param params parameters
56 + * @see String#format(String, Object...)
57 + */
58 + protected static void print(String fmt, Object... params) {
59 + print(String.format(fmt, params));
60 + }
61 +
62 + /**
63 + * Prints a title, to delimit individual unit test output.
64 + *
65 + * @param s a title for the test
66 + */
67 + protected static void title(String s) {
68 + print(EOL + "=== %s ===", s);
69 + }
70 +}
1 +/*
2 + * Copyright 2016-present 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 +
17 +package org.onosproject.ui.model;
18 +
19 +import org.onosproject.cluster.ClusterService;
20 +import org.onosproject.cluster.ClusterServiceAdapter;
21 +import org.onosproject.cluster.ControllerNode;
22 +import org.onosproject.cluster.NodeId;
23 +import org.onosproject.mastership.MastershipService;
24 +import org.onosproject.net.device.DeviceService;
25 +import org.onosproject.net.flow.FlowRuleService;
26 +import org.onosproject.net.host.HostService;
27 +import org.onosproject.net.intent.IntentService;
28 +import org.onosproject.net.link.LinkService;
29 +import org.onosproject.net.region.RegionService;
30 +import org.onosproject.ui.AbstractUiTest;
31 +
32 +/**
33 + * Base class for UI model unit tests.
34 + */
35 +public class AbstractUiModelTest extends AbstractUiTest {
36 +
37 + /**
38 + * Returns canned results.
39 + * At some future point, we may make this "programmable", so that
40 + * it returns certain values based on element IDs etc.
41 + */
42 + protected static final ServiceBundle MOCK_SERVICES =
43 + new ServiceBundle() {
44 + @Override
45 + public ClusterService cluster() {
46 + return MOCK_CLUSTER;
47 + }
48 +
49 + @Override
50 + public MastershipService mastership() {
51 + return null;
52 + }
53 +
54 + @Override
55 + public RegionService region() {
56 + return null;
57 + }
58 +
59 + @Override
60 + public DeviceService device() {
61 + return null;
62 + }
63 +
64 + @Override
65 + public LinkService link() {
66 + return null;
67 + }
68 +
69 + @Override
70 + public HostService host() {
71 + return null;
72 + }
73 +
74 + @Override
75 + public IntentService intent() {
76 + return null;
77 + }
78 +
79 + @Override
80 + public FlowRuleService flow() {
81 + return null;
82 + }
83 + };
84 +
85 + private static final ClusterService MOCK_CLUSTER = new MockClusterService();
86 +
87 +
88 + private static class MockClusterService extends ClusterServiceAdapter {
89 + @Override
90 + public ControllerNode.State getState(NodeId nodeId) {
91 + // For now, a hardcoded state of ACTIVE (but not READY)
92 + // irrespective of the node ID.
93 + return ControllerNode.State.ACTIVE;
94 + }
95 + }
96 +
97 +}
1 +/*
2 + * Copyright 2016-present 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 +
17 +package org.onosproject.ui.model.topo;
18 +
19 +import org.junit.Test;
20 +import org.onlab.packet.IpAddress;
21 +import org.onosproject.cluster.ControllerNode;
22 +import org.onosproject.cluster.DefaultControllerNode;
23 +import org.onosproject.cluster.NodeId;
24 +import org.onosproject.ui.model.AbstractUiModelTest;
25 +
26 +import static org.junit.Assert.assertEquals;
27 +
28 +/**
29 + * Unit tests for {@link UiClusterMember}.
30 + */
31 +public class UiClusterMemberTest extends AbstractUiModelTest {
32 +
33 + private static final NodeId NODE_ID = NodeId.nodeId("Node-1");
34 + private static final IpAddress NODE_IP = IpAddress.valueOf("1.2.3.4");
35 +
36 + private static final ControllerNode CNODE_1 =
37 + new DefaultControllerNode(NODE_ID, NODE_IP);
38 +
39 + private UiClusterMember member;
40 +
41 + @Test
42 + public void basic() {
43 + title("basic");
44 + member = new UiClusterMember(CNODE_1);
45 + print(member);
46 +
47 + assertEquals("wrong id", NODE_ID, member.id());
48 + assertEquals("wrong IP", NODE_IP, member.ip());
49 + assertEquals("unex. online", false, member.isOnline());
50 + assertEquals("unex. ready", false, member.isReady());
51 + assertEquals("unex. device count", 0, member.deviceCount());
52 + }
53 +}
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
17 package org.onosproject.ui.impl.topo.model; 17 package org.onosproject.ui.impl.topo.model;
18 18
19 import org.onosproject.cluster.ControllerNode; 19 import org.onosproject.cluster.ControllerNode;
20 +import org.onosproject.cluster.NodeId;
20 import org.onosproject.cluster.RoleInfo; 21 import org.onosproject.cluster.RoleInfo;
21 import org.onosproject.event.EventDispatcher; 22 import org.onosproject.event.EventDispatcher;
22 import org.onosproject.net.Device; 23 import org.onosproject.net.Device;
...@@ -24,11 +25,16 @@ import org.onosproject.net.DeviceId; ...@@ -24,11 +25,16 @@ import org.onosproject.net.DeviceId;
24 import org.onosproject.net.Host; 25 import org.onosproject.net.Host;
25 import org.onosproject.net.Link; 26 import org.onosproject.net.Link;
26 import org.onosproject.net.region.Region; 27 import org.onosproject.net.region.Region;
28 +import org.onosproject.ui.model.ServiceBundle;
27 import org.onosproject.ui.model.topo.UiClusterMember; 29 import org.onosproject.ui.model.topo.UiClusterMember;
28 import org.onosproject.ui.model.topo.UiDevice; 30 import org.onosproject.ui.model.topo.UiDevice;
29 import org.onosproject.ui.model.topo.UiTopology; 31 import org.onosproject.ui.model.topo.UiTopology;
32 +import org.slf4j.Logger;
33 +import org.slf4j.LoggerFactory;
30 34
31 -import static org.onosproject.ui.impl.topo.model.UiModelEvent.Type.DEVICE_ADDED; 35 +import static org.onosproject.ui.impl.topo.model.UiModelEvent.Type.CLUSTER_MEMBER_ADDED_OR_UPDATED;
36 +import static org.onosproject.ui.impl.topo.model.UiModelEvent.Type.CLUSTER_MEMBER_REMOVED;
37 +import static org.onosproject.ui.impl.topo.model.UiModelEvent.Type.DEVICE_ADDED_OR_UPDATED;
32 import static org.onosproject.ui.impl.topo.model.UiModelEvent.Type.DEVICE_REMOVED; 38 import static org.onosproject.ui.impl.topo.model.UiModelEvent.Type.DEVICE_REMOVED;
33 39
34 /** 40 /**
...@@ -36,10 +42,14 @@ import static org.onosproject.ui.impl.topo.model.UiModelEvent.Type.DEVICE_REMOVE ...@@ -36,10 +42,14 @@ import static org.onosproject.ui.impl.topo.model.UiModelEvent.Type.DEVICE_REMOVE
36 */ 42 */
37 class ModelCache { 43 class ModelCache {
38 44
45 + private static final Logger log = LoggerFactory.getLogger(ModelCache.class);
46 +
47 + private final ServiceBundle services;
39 private final EventDispatcher dispatcher; 48 private final EventDispatcher dispatcher;
40 private final UiTopology uiTopology = new UiTopology(); 49 private final UiTopology uiTopology = new UiTopology();
41 50
42 - ModelCache(EventDispatcher eventDispatcher) { 51 + ModelCache(ServiceBundle services, EventDispatcher eventDispatcher) {
52 + this.services = services;
43 this.dispatcher = eventDispatcher; 53 this.dispatcher = eventDispatcher;
44 } 54 }
45 55
...@@ -59,6 +69,7 @@ class ModelCache { ...@@ -59,6 +69,7 @@ class ModelCache {
59 * Create our internal model of the global topology. 69 * Create our internal model of the global topology.
60 */ 70 */
61 void load() { 71 void load() {
72 + // TODO - implement loading of initial state
62 // loadClusterMembers(); 73 // loadClusterMembers();
63 // loadRegions(); 74 // loadRegions();
64 // loadDevices(); 75 // loadDevices();
...@@ -74,20 +85,36 @@ class ModelCache { ...@@ -74,20 +85,36 @@ class ModelCache {
74 * @param cnode controller node to be added/updated 85 * @param cnode controller node to be added/updated
75 */ 86 */
76 void addOrUpdateClusterMember(ControllerNode cnode) { 87 void addOrUpdateClusterMember(ControllerNode cnode) {
77 - UiClusterMember member = uiTopology.findClusterMember(cnode.id()); 88 + NodeId id = cnode.id();
78 - if (member != null) { 89 + UiClusterMember member = uiTopology.findClusterMember(id);
79 - member.update(cnode); 90 + if (member == null) {
80 - } else {
81 member = new UiClusterMember(cnode); 91 member = new UiClusterMember(cnode);
82 uiTopology.add(member); 92 uiTopology.add(member);
83 } 93 }
84 94
85 - // TODO: post event 95 + // inject computed data about the cluster node, into the model object
96 + ControllerNode.State state = services.cluster().getState(id);
97 + member.setState(state);
98 + member.setDeviceCount(services.mastership().getDevicesOf(id).size());
99 + // NOTE: UI-attached is session-based data, not global
100 +
101 + dispatcher.post(new UiModelEvent(CLUSTER_MEMBER_ADDED_OR_UPDATED, member));
86 } 102 }
87 103
104 + /**
105 + * Removes from the model the specified controller node.
106 + *
107 + * @param cnode controller node to be removed
108 + */
88 void removeClusterMember(ControllerNode cnode) { 109 void removeClusterMember(ControllerNode cnode) {
89 - // TODO: find cluster member assoc. with parameter; remove from model 110 + NodeId id = cnode.id();
90 - // TODO: post event 111 + UiClusterMember member = uiTopology.findClusterMember(id);
112 + if (member != null) {
113 + uiTopology.remove(member);
114 + dispatcher.post(new UiModelEvent(CLUSTER_MEMBER_REMOVED, member));
115 + } else {
116 + log.warn("Tried to remove non-member cluster node {}", id);
117 + }
91 } 118 }
92 119
93 void updateMasterships(DeviceId deviceId, RoleInfo roleInfo) { 120 void updateMasterships(DeviceId deviceId, RoleInfo roleInfo) {
...@@ -111,7 +138,7 @@ class ModelCache { ...@@ -111,7 +138,7 @@ class ModelCache {
111 UiDevice uiDevice = new UiDevice(); 138 UiDevice uiDevice = new UiDevice();
112 139
113 // TODO: post the (correct) event 140 // TODO: post the (correct) event
114 - dispatcher.post(new UiModelEvent(DEVICE_ADDED, uiDevice)); 141 + dispatcher.post(new UiModelEvent(DEVICE_ADDED_OR_UPDATED, uiDevice));
115 } 142 }
116 143
117 void removeDevice(Device device) { 144 void removeDevice(Device device) {
......
...@@ -29,8 +29,12 @@ public class UiModelEvent extends AbstractEvent<UiModelEvent.Type, UiElement> { ...@@ -29,8 +29,12 @@ public class UiModelEvent extends AbstractEvent<UiModelEvent.Type, UiElement> {
29 } 29 }
30 30
31 enum Type { 31 enum Type {
32 - DEVICE_ADDED, 32 + CLUSTER_MEMBER_ADDED_OR_UPDATED,
33 + CLUSTER_MEMBER_REMOVED,
34 +
35 + DEVICE_ADDED_OR_UPDATED,
33 DEVICE_REMOVED, 36 DEVICE_REMOVED,
37 +
34 // TODO... 38 // TODO...
35 } 39 }
36 } 40 }
......
...@@ -59,6 +59,7 @@ import org.onosproject.net.region.RegionService; ...@@ -59,6 +59,7 @@ import org.onosproject.net.region.RegionService;
59 import org.onosproject.net.statistic.StatisticService; 59 import org.onosproject.net.statistic.StatisticService;
60 import org.onosproject.net.topology.TopologyService; 60 import org.onosproject.net.topology.TopologyService;
61 import org.onosproject.ui.impl.topo.UiTopoSession; 61 import org.onosproject.ui.impl.topo.UiTopoSession;
62 +import org.onosproject.ui.model.ServiceBundle;
62 import org.slf4j.Logger; 63 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory; 64 import org.slf4j.LoggerFactory;
64 65
...@@ -122,7 +123,7 @@ public final class UiSharedTopologyModel ...@@ -122,7 +123,7 @@ public final class UiSharedTopologyModel
122 123
123 @Activate 124 @Activate
124 protected void activate() { 125 protected void activate() {
125 - cache = new ModelCache(eventDispatcher); 126 + cache = new ModelCache(new DefaultServiceBundle(), eventDispatcher);
126 127
127 eventDispatcher.addSink(UiModelEvent.class, listenerRegistry); 128 eventDispatcher.addSink(UiModelEvent.class, listenerRegistry);
128 129
...@@ -180,6 +181,52 @@ public final class UiSharedTopologyModel ...@@ -180,6 +181,52 @@ public final class UiSharedTopologyModel
180 removeListener(session); 181 removeListener(session);
181 } 182 }
182 183
184 + /**
185 + * Default implementation of service bundle to return references to our
186 + * dynamically injected services.
187 + */
188 + private class DefaultServiceBundle implements ServiceBundle {
189 + @Override
190 + public ClusterService cluster() {
191 + return clusterService;
192 + }
193 +
194 + @Override
195 + public MastershipService mastership() {
196 + return mastershipService;
197 + }
198 +
199 + @Override
200 + public RegionService region() {
201 + return regionService;
202 + }
203 +
204 + @Override
205 + public DeviceService device() {
206 + return deviceService;
207 + }
208 +
209 + @Override
210 + public LinkService link() {
211 + return linkService;
212 + }
213 +
214 + @Override
215 + public HostService host() {
216 + return hostService;
217 + }
218 +
219 + @Override
220 + public IntentService intent() {
221 + return intentService;
222 + }
223 +
224 + @Override
225 + public FlowRuleService flow() {
226 + return flowService;
227 + }
228 + }
229 +
183 230
184 private class InternalClusterListener implements ClusterEventListener { 231 private class InternalClusterListener implements ClusterEventListener {
185 @Override 232 @Override
......
...@@ -14,12 +14,12 @@ ...@@ -14,12 +14,12 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -package org.onosproject.ui.impl.topo.model; 17 +package org.onosproject.ui.impl;
18 18
19 /** 19 /**
20 - * Base class for model test classes. 20 + * Base class for unit tests.
21 */ 21 */
22 -public abstract class AbstractModelTest { 22 +public class AbstractUiImplTest {
23 23
24 /** 24 /**
25 * System agnostic end-of-line character. 25 * System agnostic end-of-line character.
...@@ -41,8 +41,12 @@ public abstract class AbstractModelTest { ...@@ -41,8 +41,12 @@ public abstract class AbstractModelTest {
41 * @param o object to print 41 * @param o object to print
42 */ 42 */
43 protected void print(Object o) { 43 protected void print(Object o) {
44 + if (o == null) {
45 + print("<null>");
46 + } else {
44 print(o.toString()); 47 print(o.toString());
45 } 48 }
49 + }
46 50
47 /** 51 /**
48 * Prints the formatted string to stdout. 52 * Prints the formatted string to stdout.
...@@ -55,4 +59,12 @@ public abstract class AbstractModelTest { ...@@ -55,4 +59,12 @@ public abstract class AbstractModelTest {
55 print(String.format(fmt, params)); 59 print(String.format(fmt, params));
56 } 60 }
57 61
62 + /**
63 + * Prints a title, to delimit individual unit test output.
64 + *
65 + * @param s a title for the test
66 + */
67 + protected void title(String s) {
68 + print(EOL + "=== %s ===", s);
69 + }
58 } 70 }
......
1 +/*
2 + * Copyright 2016-present 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 +
17 +package org.onosproject.ui.impl.topo.model;
18 +
19 +import com.google.common.collect.ImmutableSet;
20 +import org.onosproject.cluster.ClusterService;
21 +import org.onosproject.cluster.ClusterServiceAdapter;
22 +import org.onosproject.cluster.ControllerNode;
23 +import org.onosproject.cluster.NodeId;
24 +import org.onosproject.mastership.MastershipService;
25 +import org.onosproject.mastership.MastershipServiceAdapter;
26 +import org.onosproject.net.DeviceId;
27 +import org.onosproject.net.device.DeviceService;
28 +import org.onosproject.net.flow.FlowRuleService;
29 +import org.onosproject.net.host.HostService;
30 +import org.onosproject.net.intent.IntentService;
31 +import org.onosproject.net.link.LinkService;
32 +import org.onosproject.net.region.RegionService;
33 +import org.onosproject.ui.impl.AbstractUiImplTest;
34 +import org.onosproject.ui.model.ServiceBundle;
35 +
36 +import java.util.HashMap;
37 +import java.util.Map;
38 +import java.util.Set;
39 +
40 +import static org.onosproject.net.DeviceId.deviceId;
41 +
42 +/**
43 + * Base class for model test classes.
44 + */
45 +abstract class AbstractTopoModelTest extends AbstractUiImplTest {
46 +
47 + /**
48 + * Returns canned results.
49 + * At some future point, we may make this "programmable", so that
50 + * it returns certain values based on element IDs etc.
51 + */
52 + protected static final ServiceBundle MOCK_SERVICES =
53 + new ServiceBundle() {
54 + @Override
55 + public ClusterService cluster() {
56 + return MOCK_CLUSTER;
57 + }
58 +
59 + @Override
60 + public MastershipService mastership() {
61 + return MOCK_MASTER;
62 + }
63 +
64 + @Override
65 + public RegionService region() {
66 + return null;
67 + }
68 +
69 + @Override
70 + public DeviceService device() {
71 + return null;
72 + }
73 +
74 + @Override
75 + public LinkService link() {
76 + return null;
77 + }
78 +
79 + @Override
80 + public HostService host() {
81 + return null;
82 + }
83 +
84 + @Override
85 + public IntentService intent() {
86 + return null;
87 + }
88 +
89 + @Override
90 + public FlowRuleService flow() {
91 + return null;
92 + }
93 + };
94 +
95 + private static final ClusterService MOCK_CLUSTER = new MockClusterService();
96 + private static final MastershipService MOCK_MASTER = new MockMasterService();
97 + // TODO: fill out as necessary
98 +
99 + /*
100 + Our mock environment:
101 +
102 + Three controllers: C1, C2, C3
103 +
104 + Nine devices: D1 .. D9
105 +
106 + D4 ---+ +--- D7
107 + | |
108 + D5 --- D1 --- D2 --- D3 --- D8
109 + | |
110 + D6 ---+ +--- D9
111 +
112 + Twelve hosts (two per D4 ... D9) H41, H42, H51, H52, ...
113 +
114 + Regions:
115 + R1 : D1, D2, D3
116 + R2 : D4, D5, D6
117 + R3 : D7, D8, D9
118 +
119 + Mastership:
120 + C1 : D1, D2, D3
121 + C2 : D4, D5, D6
122 + C3 : D7, D8, D9
123 + */
124 +
125 +
126 + private static class MockClusterService extends ClusterServiceAdapter {
127 + private final Map<NodeId, ControllerNode.State> states = new HashMap<>();
128 +
129 +
130 + @Override
131 + public ControllerNode.State getState(NodeId nodeId) {
132 + // For now, a hardcoded state of ACTIVE (but not READY)
133 + // irrespective of the node ID.
134 + return ControllerNode.State.ACTIVE;
135 + }
136 + }
137 +
138 + protected static final DeviceId D1_ID = deviceId("D1");
139 + protected static final DeviceId D2_ID = deviceId("D2");
140 + protected static final DeviceId D3_ID = deviceId("D3");
141 + protected static final DeviceId D4_ID = deviceId("D4");
142 + protected static final DeviceId D5_ID = deviceId("D5");
143 + protected static final DeviceId D6_ID = deviceId("D6");
144 + protected static final DeviceId D7_ID = deviceId("D7");
145 + protected static final DeviceId D8_ID = deviceId("D8");
146 + protected static final DeviceId D9_ID = deviceId("D9");
147 +
148 + private static class MockMasterService extends MastershipServiceAdapter {
149 + @Override
150 + public Set<DeviceId> getDevicesOf(NodeId nodeId) {
151 + // For now, a hard coded set of two device IDs
152 + // irrespective of the node ID.
153 + return ImmutableSet.of(D1_ID, D2_ID);
154 + }
155 + }
156 +
157 +}
...@@ -16,28 +16,113 @@ ...@@ -16,28 +16,113 @@
16 16
17 package org.onosproject.ui.impl.topo.model; 17 package org.onosproject.ui.impl.topo.model;
18 18
19 +import org.junit.Before;
19 import org.junit.Test; 20 import org.junit.Test;
21 +import org.onlab.packet.IpAddress;
22 +import org.onosproject.cluster.ControllerNode;
23 +import org.onosproject.cluster.DefaultControllerNode;
24 +import org.onosproject.event.Event;
20 import org.onosproject.event.EventDispatcher; 25 import org.onosproject.event.EventDispatcher;
26 +import org.onosproject.ui.impl.topo.model.UiModelEvent.Type;
27 +import org.onosproject.ui.model.topo.UiElement;
21 28
22 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertEquals;
30 +import static org.junit.Assert.assertNotNull;
31 +import static org.onosproject.cluster.NodeId.nodeId;
23 32
24 /** 33 /**
25 * Unit tests for {@link ModelCache}. 34 * Unit tests for {@link ModelCache}.
26 */ 35 */
27 -public class ModelCacheTest extends AbstractModelTest { 36 +public class ModelCacheTest extends AbstractTopoModelTest {
28 37
29 - private static final EventDispatcher DISPATCHER = event -> { 38 + private class TestEvDisp implements EventDispatcher {
30 - // Do we care? 39 +
31 - }; 40 + private Event<Type, UiElement> lastEvent = null;
41 + private int eventCount = 0;
42 +
43 + @Override
44 + public void post(Event event) {
45 + lastEvent = event;
46 + eventCount++;
47 +// print("Event dispatched: %s", event);
48 + }
49 +
50 + private void assertEventCount(int exp) {
51 + assertEquals("unex event count", exp, eventCount);
52 + }
53 +
54 + private void assertLast(Type expEventType, String expId) {
55 + assertNotNull("no last event", lastEvent);
56 + assertEquals("unex event type", expEventType, lastEvent.type());
57 + assertEquals("unex element ID", expId, lastEvent.subject().idAsString());
58 + }
59 + }
60 +
61 + private static IpAddress ip(String s) {
62 + return IpAddress.valueOf(s);
63 + }
64 +
65 + private static ControllerNode cnode(String id, String ip) {
66 + return new DefaultControllerNode(nodeId(id), ip(ip));
67 + }
68 +
69 + private static final String C1 = "C1";
70 + private static final String C2 = "C2";
71 + private static final String C3 = "C3";
72 +
73 + private static final ControllerNode NODE_1 = cnode(C1, "10.0.0.1");
74 + private static final ControllerNode NODE_2 = cnode(C2, "10.0.0.2");
75 + private static final ControllerNode NODE_3 = cnode(C3, "10.0.0.3");
76 +
77 +
78 + private final TestEvDisp dispatcher = new TestEvDisp();
32 79
33 private ModelCache cache; 80 private ModelCache cache;
34 81
82 + @Before
83 + public void setUp() {
84 + cache = new ModelCache(MOCK_SERVICES, dispatcher);
85 + }
86 +
35 @Test 87 @Test
36 public void basic() { 88 public void basic() {
37 - cache = new ModelCache(DISPATCHER); 89 + title("basic");
38 print(cache); 90 print(cache);
39 assertEquals("unex # members", 0, cache.clusterMemberCount()); 91 assertEquals("unex # members", 0, cache.clusterMemberCount());
40 assertEquals("unex # regions", 0, cache.regionCount()); 92 assertEquals("unex # regions", 0, cache.regionCount());
41 } 93 }
42 94
95 + @Test
96 + public void addAndRemoveClusterMember() {
97 + title("addAndRemoveClusterMember");
98 + print(cache);
99 + assertEquals("unex # members", 0, cache.clusterMemberCount());
100 + dispatcher.assertEventCount(0);
101 +
102 + cache.addOrUpdateClusterMember(NODE_1);
103 + print(cache);
104 + assertEquals("unex # members", 1, cache.clusterMemberCount());
105 + dispatcher.assertEventCount(1);
106 + dispatcher.assertLast(Type.CLUSTER_MEMBER_ADDED_OR_UPDATED, C1);
107 +
108 + cache.removeClusterMember(NODE_1);
109 + print(cache);
110 + assertEquals("unex # members", 0, cache.clusterMemberCount());
111 + dispatcher.assertEventCount(2);
112 + dispatcher.assertLast(Type.CLUSTER_MEMBER_REMOVED, C1);
113 + }
114 +
115 + @Test
116 + public void createThreeNodeCluster() {
117 + title("createThreeNodeCluster");
118 + cache.addOrUpdateClusterMember(NODE_1);
119 + dispatcher.assertLast(Type.CLUSTER_MEMBER_ADDED_OR_UPDATED, C1);
120 + cache.addOrUpdateClusterMember(NODE_2);
121 + dispatcher.assertLast(Type.CLUSTER_MEMBER_ADDED_OR_UPDATED, C2);
122 + cache.addOrUpdateClusterMember(NODE_3);
123 + dispatcher.assertLast(Type.CLUSTER_MEMBER_ADDED_OR_UPDATED, C3);
124 + dispatcher.assertEventCount(3);
125 + print(cache);
126 + }
127 +
43 } 128 }
......