Simon Hunt
Committed by Gerrit Code Review

Included connect point port number in definition of UiLinkId.

Added dumpString() to ModelCache / UiTopology.
Added more unit tests for ModelCache.

Change-Id: I842bb418b25cc901bd12bc28c6660c836f7235bc
...@@ -87,6 +87,16 @@ public class UiDevice extends UiNode { ...@@ -87,6 +87,16 @@ public class UiDevice extends UiNode {
87 } 87 }
88 88
89 /** 89 /**
90 + * Returns the identifier of the region to which this device belongs.
91 + * This will be null if the device does not belong to any region.
92 + *
93 + * @return region identity
94 + */
95 + public RegionId regionId() {
96 + return regionId;
97 + }
98 +
99 + /**
90 * Returns the UI region to which this device belongs. 100 * Returns the UI region to which this device belongs.
91 * 101 *
92 * @return the UI region 102 * @return the UI region
......
...@@ -177,4 +177,42 @@ public class UiLink extends UiElement { ...@@ -177,4 +177,42 @@ public class UiLink extends UiElement {
177 edgeLink = elink; 177 edgeLink = elink;
178 edgeDevice = elink.hostLocation().deviceId(); 178 edgeDevice = elink.hostLocation().deviceId();
179 } 179 }
180 +
181 +
182 + /**
183 + * Returns the identity of device A.
184 + *
185 + * @return device A ID
186 + */
187 + public DeviceId deviceA() {
188 + return deviceA;
189 + }
190 +
191 + /**
192 + * Returns the identity of device B.
193 + *
194 + * @return device B ID
195 + */
196 + public DeviceId deviceB() {
197 + return deviceB;
198 + }
199 +
200 + /**
201 + * Returns backing link from A to B.
202 + *
203 + * @return backing link A to B
204 + */
205 + public Link linkAtoB() {
206 + return linkAtoB;
207 + }
208 +
209 + /**
210 + * Returns backing link from B to A.
211 + *
212 + * @return backing link B to A
213 + */
214 + public Link linkBtoA() {
215 + return linkBtoA;
216 + }
217 +
180 } 218 }
......
...@@ -19,6 +19,7 @@ package org.onosproject.ui.model.topo; ...@@ -19,6 +19,7 @@ package org.onosproject.ui.model.topo;
19 import org.onosproject.net.ConnectPoint; 19 import org.onosproject.net.ConnectPoint;
20 import org.onosproject.net.ElementId; 20 import org.onosproject.net.ElementId;
21 import org.onosproject.net.Link; 21 import org.onosproject.net.Link;
22 +import org.onosproject.net.PortNumber;
22 23
23 /** 24 /**
24 * A canonical representation of an identifier for {@link UiLink}s. 25 * A canonical representation of an identifier for {@link UiLink}s.
...@@ -33,10 +34,14 @@ public final class UiLinkId { ...@@ -33,10 +34,14 @@ public final class UiLinkId {
33 B_TO_A 34 B_TO_A
34 } 35 }
35 36
36 - private static final String ID_DELIMITER = "~"; 37 + private static final String CP_DELIMITER = "~";
38 + private static final String ID_PORT_DELIMITER = "/";
37 39
38 private final ElementId idA; 40 private final ElementId idA;
41 + private final PortNumber portA;
39 private final ElementId idB; 42 private final ElementId idB;
43 + private final PortNumber portB;
44 +
40 private final String idStr; 45 private final String idStr;
41 46
42 /** 47 /**
...@@ -46,13 +51,18 @@ public final class UiLinkId { ...@@ -46,13 +51,18 @@ public final class UiLinkId {
46 * underlying link. 51 * underlying link.
47 * 52 *
48 * @param a first element ID 53 * @param a first element ID
54 + * @param pa first element port
49 * @param b second element ID 55 * @param b second element ID
56 + * @param pb second element port
50 */ 57 */
51 - private UiLinkId(ElementId a, ElementId b) { 58 + private UiLinkId(ElementId a, PortNumber pa, ElementId b, PortNumber pb) {
52 idA = a; 59 idA = a;
60 + portA = pa;
53 idB = b; 61 idB = b;
62 + portB = pb;
54 63
55 - idStr = a.toString() + ID_DELIMITER + b.toString(); 64 + idStr = a + ID_PORT_DELIMITER + pa + CP_DELIMITER +
65 + b + ID_PORT_DELIMITER + pb;
56 } 66 }
57 67
58 @Override 68 @Override
...@@ -70,6 +80,15 @@ public final class UiLinkId { ...@@ -70,6 +80,15 @@ public final class UiLinkId {
70 } 80 }
71 81
72 /** 82 /**
83 + * Returns the port of the first element.
84 + *
85 + * @return first element port
86 + */
87 + public PortNumber portA() {
88 + return portA;
89 + }
90 +
91 + /**
73 * Returns the identifier of the second element. 92 * Returns the identifier of the second element.
74 * 93 *
75 * @return second element identity 94 * @return second element identity
...@@ -78,6 +97,15 @@ public final class UiLinkId { ...@@ -78,6 +97,15 @@ public final class UiLinkId {
78 return idB; 97 return idB;
79 } 98 }
80 99
100 + /**
101 + * Returns the port of the second element.
102 + *
103 + * @return second element port
104 + */
105 + public PortNumber portB() {
106 + return portB;
107 + }
108 +
81 @Override 109 @Override
82 public boolean equals(Object o) { 110 public boolean equals(Object o) {
83 if (this == o) { 111 if (this == o) {
...@@ -127,13 +155,10 @@ public final class UiLinkId { ...@@ -127,13 +155,10 @@ public final class UiLinkId {
127 155
128 ElementId srcId = src.elementId(); 156 ElementId srcId = src.elementId();
129 ElementId dstId = dst.elementId(); 157 ElementId dstId = dst.elementId();
130 - if (srcId == null || dstId == null) {
131 - throw new NullPointerException("null element ID in connect point: " + link);
132 - }
133 158
134 // canonicalize 159 // canonicalize
135 int comp = srcId.toString().compareTo(dstId.toString()); 160 int comp = srcId.toString().compareTo(dstId.toString());
136 - return comp <= 0 ? new UiLinkId(srcId, dstId) 161 + return comp <= 0 ? new UiLinkId(srcId, src.port(), dstId, dst.port())
137 - : new UiLinkId(dstId, srcId); 162 + : new UiLinkId(dstId, dst.port(), srcId, src.port());
138 } 163 }
139 } 164 }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
16 16
17 package org.onosproject.ui.model.topo; 17 package org.onosproject.ui.model.topo;
18 18
19 +import com.google.common.collect.ImmutableSet;
19 import org.onosproject.net.DeviceId; 20 import org.onosproject.net.DeviceId;
20 import org.onosproject.net.HostId; 21 import org.onosproject.net.HostId;
21 import org.onosproject.net.region.Region; 22 import org.onosproject.net.region.Region;
...@@ -117,6 +118,15 @@ public class UiRegion extends UiNode { ...@@ -117,6 +118,15 @@ public class UiRegion extends UiNode {
117 } 118 }
118 119
119 /** 120 /**
121 + * Returns the set of device identifiers for this region.
122 + *
123 + * @return device identifiers for this region
124 + */
125 + public Set<DeviceId> deviceIds() {
126 + return ImmutableSet.copyOf(deviceIds);
127 + }
128 +
129 + /**
120 * Returns the devices in this region. 130 * Returns the devices in this region.
121 * 131 *
122 * @return the devices in this region 132 * @return the devices in this region
...@@ -126,6 +136,15 @@ public class UiRegion extends UiNode { ...@@ -126,6 +136,15 @@ public class UiRegion extends UiNode {
126 } 136 }
127 137
128 /** 138 /**
139 + * Returns the set of host identifiers for this region.
140 + *
141 + * @return host identifiers for this region
142 + */
143 + public Set<HostId> hostIds() {
144 + return ImmutableSet.copyOf(hostIds);
145 + }
146 +
147 + /**
129 * Returns the hosts in this region. 148 * Returns the hosts in this region.
130 * 149 *
131 * @return the hosts in this region 150 * @return the hosts in this region
...@@ -135,6 +154,15 @@ public class UiRegion extends UiNode { ...@@ -135,6 +154,15 @@ public class UiRegion extends UiNode {
135 } 154 }
136 155
137 /** 156 /**
157 + * Returns the set of link identifiers for this region.
158 + *
159 + * @return link identifiers for this region
160 + */
161 + public Set<UiLinkId> linkIds() {
162 + return ImmutableSet.copyOf(uiLinkIds);
163 + }
164 +
165 + /**
138 * Returns the links in this region. 166 * Returns the links in this region.
139 * 167 *
140 * @return the links in this region 168 * @return the links in this region
......
...@@ -35,6 +35,10 @@ import static com.google.common.base.MoreObjects.toStringHelper; ...@@ -35,6 +35,10 @@ import static com.google.common.base.MoreObjects.toStringHelper;
35 */ 35 */
36 public class UiTopology extends UiElement { 36 public class UiTopology extends UiElement {
37 37
38 + private static final String INDENT_1 = " ";
39 + private static final String INDENT_2 = " ";
40 + private static final String EOL = String.format("%n");
41 +
38 private static final String E_UNMAPPED = 42 private static final String E_UNMAPPED =
39 "Attempting to retrieve unmapped {}: {}"; 43 "Attempting to retrieve unmapped {}: {}";
40 44
...@@ -133,15 +137,6 @@ public class UiTopology extends UiElement { ...@@ -133,15 +137,6 @@ public class UiTopology extends UiElement {
133 } 137 }
134 138
135 /** 139 /**
136 - * Returns the number of regions configured in the topology.
137 - *
138 - * @return number of regions
139 - */
140 - public int regionCount() {
141 - return regionLookup.size();
142 - }
143 -
144 - /**
145 * Adds the given region to the topology model. 140 * Adds the given region to the topology model.
146 * 141 *
147 * @param uiRegion region to add 142 * @param uiRegion region to add
...@@ -156,7 +151,19 @@ public class UiTopology extends UiElement { ...@@ -156,7 +151,19 @@ public class UiTopology extends UiElement {
156 * @param uiRegion region to remove 151 * @param uiRegion region to remove
157 */ 152 */
158 public void remove(UiRegion uiRegion) { 153 public void remove(UiRegion uiRegion) {
159 - regionLookup.remove(uiRegion.id()); 154 + UiRegion r = regionLookup.remove(uiRegion.id());
155 + if (r != null) {
156 + r.destroy();
157 + }
158 + }
159 +
160 + /**
161 + * Returns the number of regions configured in the topology.
162 + *
163 + * @return number of regions
164 + */
165 + public int regionCount() {
166 + return regionLookup.size();
160 } 167 }
161 168
162 /** 169 /**
...@@ -192,6 +199,15 @@ public class UiTopology extends UiElement { ...@@ -192,6 +199,15 @@ public class UiTopology extends UiElement {
192 } 199 }
193 200
194 /** 201 /**
202 + * Returns the number of devices configured in the topology.
203 + *
204 + * @return number of devices
205 + */
206 + public int deviceCount() {
207 + return deviceLookup.size();
208 + }
209 +
210 + /**
195 * Returns the link with the specified identifier, or null if no such 211 * Returns the link with the specified identifier, or null if no such
196 * link exists. 212 * link exists.
197 * 213 *
...@@ -217,13 +233,22 @@ public class UiTopology extends UiElement { ...@@ -217,13 +233,22 @@ public class UiTopology extends UiElement {
217 * @param uiLink link to remove 233 * @param uiLink link to remove
218 */ 234 */
219 public void remove(UiLink uiLink) { 235 public void remove(UiLink uiLink) {
220 - UiLink link = linkLookup.get(uiLink.id()); 236 + UiLink link = linkLookup.remove(uiLink.id());
221 if (link != null) { 237 if (link != null) {
222 link.destroy(); 238 link.destroy();
223 } 239 }
224 } 240 }
225 241
226 /** 242 /**
243 + * Returns the number of links configured in the topology.
244 + *
245 + * @return number of links
246 + */
247 + public int linkCount() {
248 + return linkLookup.size();
249 + }
250 +
251 + /**
227 * Returns the host with the specified identifier, or null if no such 252 * Returns the host with the specified identifier, or null if no such
228 * host exists. 253 * host exists.
229 * 254 *
...@@ -255,6 +280,16 @@ public class UiTopology extends UiElement { ...@@ -255,6 +280,16 @@ public class UiTopology extends UiElement {
255 } 280 }
256 } 281 }
257 282
283 + /**
284 + * Returns the number of hosts configured in the topology.
285 + *
286 + * @return number of hosts
287 + */
288 + public int hostCount() {
289 + return hostLookup.size();
290 + }
291 +
292 +
258 // == 293 // ==
259 // package private methods for supporting linkage amongst topology entities 294 // package private methods for supporting linkage amongst topology entities
260 // == 295 // ==
...@@ -316,4 +351,42 @@ public class UiTopology extends UiElement { ...@@ -316,4 +351,42 @@ public class UiTopology extends UiElement {
316 return uiLinks; 351 return uiLinks;
317 } 352 }
318 353
354 + /**
355 + * Returns a detailed (multi-line) string showing the contents of the
356 + * topology.
357 + *
358 + * @return detailed string
359 + */
360 + public String dumpString() {
361 + StringBuilder sb = new StringBuilder("Topology:").append(EOL);
362 +
363 + sb.append(INDENT_1).append("Cluster Members").append(EOL);
364 + for (UiClusterMember m : cnodeLookup.values()) {
365 + sb.append(INDENT_2).append(m).append(EOL);
366 + }
367 +
368 + sb.append(INDENT_1).append("Regions").append(EOL);
369 + for (UiRegion r : regionLookup.values()) {
370 + sb.append(INDENT_2).append(r).append(EOL);
371 + }
372 +
373 + sb.append(INDENT_1).append("Devices").append(EOL);
374 + for (UiDevice d : deviceLookup.values()) {
375 + sb.append(INDENT_2).append(d).append(EOL);
376 + }
377 +
378 + sb.append(INDENT_1).append("Hosts").append(EOL);
379 + for (UiHost h : hostLookup.values()) {
380 + sb.append(INDENT_2).append(h).append(EOL);
381 + }
382 +
383 + sb.append(INDENT_1).append("Links").append(EOL);
384 + for (UiLink link : linkLookup.values()) {
385 + sb.append(INDENT_2).append(link).append(EOL);
386 + }
387 + sb.append("------").append(EOL);
388 +
389 + return sb.toString();
390 + }
391 +
319 } 392 }
......
...@@ -26,6 +26,7 @@ import org.onosproject.net.provider.ProviderId; ...@@ -26,6 +26,7 @@ import org.onosproject.net.provider.ProviderId;
26 import org.onosproject.ui.model.AbstractUiModelTest; 26 import org.onosproject.ui.model.AbstractUiModelTest;
27 27
28 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertEquals;
29 +import static org.junit.Assert.assertNotEquals;
29 import static org.onosproject.net.DeviceId.deviceId; 30 import static org.onosproject.net.DeviceId.deviceId;
30 import static org.onosproject.net.PortNumber.portNumber; 31 import static org.onosproject.net.PortNumber.portNumber;
31 32
...@@ -34,28 +35,34 @@ import static org.onosproject.net.PortNumber.portNumber; ...@@ -34,28 +35,34 @@ import static org.onosproject.net.PortNumber.portNumber;
34 */ 35 */
35 public class UiLinkIdTest extends AbstractUiModelTest { 36 public class UiLinkIdTest extends AbstractUiModelTest {
36 37
37 -
38 - private static final ProviderId PROVIDER_ID = ProviderId.NONE;
39 -
40 private static final DeviceId DEV_X = deviceId("device-X"); 38 private static final DeviceId DEV_X = deviceId("device-X");
41 private static final DeviceId DEV_Y = deviceId("device-Y"); 39 private static final DeviceId DEV_Y = deviceId("device-Y");
42 private static final PortNumber P1 = portNumber(1); 40 private static final PortNumber P1 = portNumber(1);
43 private static final PortNumber P2 = portNumber(2); 41 private static final PortNumber P2 = portNumber(2);
42 + private static final PortNumber P3 = portNumber(3);
43 +
44 + private static final ConnectPoint CP_X1 = new ConnectPoint(DEV_X, P1);
45 + private static final ConnectPoint CP_Y2 = new ConnectPoint(DEV_Y, P2);
46 + private static final ConnectPoint CP_Y3 = new ConnectPoint(DEV_Y, P3);
44 47
45 - private static final ConnectPoint CP_X = new ConnectPoint(DEV_X, P1); 48 + private static final Link LINK_X1_TO_Y2 = DefaultLink.builder()
46 - private static final ConnectPoint CP_Y = new ConnectPoint(DEV_Y, P2); 49 + .providerId(ProviderId.NONE)
50 + .src(CP_X1)
51 + .dst(CP_Y2)
52 + .type(Link.Type.DIRECT)
53 + .build();
47 54
48 - private static final Link LINK_X_TO_Y = DefaultLink.builder() 55 + private static final Link LINK_Y2_TO_X1 = DefaultLink.builder()
49 .providerId(ProviderId.NONE) 56 .providerId(ProviderId.NONE)
50 - .src(CP_X) 57 + .src(CP_Y2)
51 - .dst(CP_Y) 58 + .dst(CP_X1)
52 .type(Link.Type.DIRECT) 59 .type(Link.Type.DIRECT)
53 .build(); 60 .build();
54 61
55 - private static final Link LINK_Y_TO_X = DefaultLink.builder() 62 + private static final Link LINK_X1_TO_Y3 = DefaultLink.builder()
56 .providerId(ProviderId.NONE) 63 .providerId(ProviderId.NONE)
57 - .src(CP_Y) 64 + .src(CP_X1)
58 - .dst(CP_X) 65 + .dst(CP_Y3)
59 .type(Link.Type.DIRECT) 66 .type(Link.Type.DIRECT)
60 .build(); 67 .build();
61 68
...@@ -63,10 +70,20 @@ public class UiLinkIdTest extends AbstractUiModelTest { ...@@ -63,10 +70,20 @@ public class UiLinkIdTest extends AbstractUiModelTest {
63 @Test 70 @Test
64 public void canonical() { 71 public void canonical() {
65 title("canonical"); 72 title("canonical");
66 - UiLinkId one = UiLinkId.uiLinkId(LINK_X_TO_Y); 73 + UiLinkId one = UiLinkId.uiLinkId(LINK_X1_TO_Y2);
67 - UiLinkId two = UiLinkId.uiLinkId(LINK_Y_TO_X); 74 + UiLinkId two = UiLinkId.uiLinkId(LINK_Y2_TO_X1);
68 print("link one: %s", one); 75 print("link one: %s", one);
69 print("link two: %s", two); 76 print("link two: %s", two);
70 assertEquals("not equiv", one, two); 77 assertEquals("not equiv", one, two);
71 } 78 }
79 +
80 + @Test
81 + public void sameDevsDiffPorts() {
82 + title("sameDevsDiffPorts");
83 + UiLinkId one = UiLinkId.uiLinkId(LINK_X1_TO_Y2);
84 + UiLinkId other = UiLinkId.uiLinkId(LINK_X1_TO_Y3);
85 + print("link one: %s", one);
86 + print("link other: %s", other);
87 + assertNotEquals("equiv?", one, other);
88 + }
72 } 89 }
......
...@@ -197,6 +197,11 @@ class ModelCache { ...@@ -197,6 +197,11 @@ class ModelCache {
197 postEvent(REGION_ADDED_OR_UPDATED, uiRegion); 197 postEvent(REGION_ADDED_OR_UPDATED, uiRegion);
198 } 198 }
199 199
200 + // package private for unit test access
201 + UiRegion accessRegion(RegionId id) {
202 + return uiTopology.findRegion(id);
203 + }
204 +
200 // invoked from UiSharedTopologyModel region listener 205 // invoked from UiSharedTopologyModel region listener
201 void removeRegion(Region region) { 206 void removeRegion(Region region) {
202 RegionId id = region.id(); 207 RegionId id = region.id();
...@@ -241,6 +246,11 @@ class ModelCache { ...@@ -241,6 +246,11 @@ class ModelCache {
241 postEvent(DEVICE_ADDED_OR_UPDATED, uiDevice); 246 postEvent(DEVICE_ADDED_OR_UPDATED, uiDevice);
242 } 247 }
243 248
249 + // package private for unit test access
250 + UiDevice accessDevice(DeviceId id) {
251 + return uiTopology.findDevice(id);
252 + }
253 +
244 // invoked from UiSharedTopologyModel device listener 254 // invoked from UiSharedTopologyModel device listener
245 void removeDevice(Device device) { 255 void removeDevice(Device device) {
246 DeviceId id = device.id(); 256 DeviceId id = device.id();
...@@ -290,6 +300,11 @@ class ModelCache { ...@@ -290,6 +300,11 @@ class ModelCache {
290 postEvent(LINK_ADDED_OR_UPDATED, uiLink); 300 postEvent(LINK_ADDED_OR_UPDATED, uiLink);
291 } 301 }
292 302
303 + // package private for unit test access
304 + UiLink accessLink(UiLinkId id) {
305 + return uiTopology.findLink(id);
306 + }
307 +
293 // invoked from UiSharedTopologyModel link listener 308 // invoked from UiSharedTopologyModel link listener
294 void removeLink(Link link) { 309 void removeLink(Link link) {
295 UiLinkId id = uiLinkId(link); 310 UiLinkId id = uiLinkId(link);
...@@ -310,36 +325,55 @@ class ModelCache { ...@@ -310,36 +325,55 @@ class ModelCache {
310 325
311 // === HOSTS 326 // === HOSTS
312 327
328 + private EdgeLink synthesizeLink(Host h) {
329 + return createEdgeLink(h, true);
330 + }
331 +
313 private UiHost addNewHost(Host h) { 332 private UiHost addNewHost(Host h) {
314 UiHost host = new UiHost(uiTopology, h); 333 UiHost host = new UiHost(uiTopology, h);
315 uiTopology.add(host); 334 uiTopology.add(host);
316 335
317 - UiLink edgeLink = addNewEdgeLink(host); 336 + EdgeLink elink = synthesizeLink(h);
318 - host.setEdgeLinkId(edgeLink.id()); 337 + UiLinkId elinkId = uiLinkId(elink);
338 + host.setEdgeLinkId(elinkId);
339 +
340 + // add synthesized edge link to the topology
341 + UiLink edgeLink = addNewLink(elinkId);
342 + edgeLink.attachEdgeLink(elink);
319 343
320 return host; 344 return host;
321 } 345 }
322 346
323 - private void removeOldEdgeLink(UiHost uiHost) { 347 + private void insertNewUiLink(UiLinkId id, EdgeLink e) {
324 - UiLink old = uiTopology.findLink(uiHost.edgeLinkId()); 348 + UiLink newEdgeLink = addNewLink(id);
325 - if (old != null) { 349 + newEdgeLink.attachEdgeLink(e);
326 - uiTopology.remove(old);
327 - }
328 - }
329 350
330 - private UiLink addNewEdgeLink(UiHost uiHost) {
331 - EdgeLink elink = createEdgeLink(uiHost.backingHost(), true);
332 - UiLinkId elinkId = UiLinkId.uiLinkId(elink);
333 - UiLink uiLink = addNewLink(elinkId);
334 - uiLink.attachEdgeLink(elink);
335 - return uiLink;
336 } 351 }
337 352
338 private void updateHost(UiHost uiHost, Host h) { 353 private void updateHost(UiHost uiHost, Host h) {
339 - removeOldEdgeLink(uiHost); 354 + UiLink existing = uiTopology.findLink(uiHost.edgeLinkId());
355 +
356 + EdgeLink currentElink = synthesizeLink(h);
357 + UiLinkId currentElinkId = uiLinkId(currentElink);
358 +
359 + if (existing != null) {
360 + if (!currentElinkId.equals(existing.id())) {
361 + // edge link has changed
362 + insertNewUiLink(currentElinkId, currentElink);
363 + uiHost.setEdgeLinkId(currentElinkId);
364 +
365 + uiTopology.remove(existing);
366 + }
367 +
368 + } else {
369 + // no previously existing edge link
370 + insertNewUiLink(currentElinkId, currentElink);
371 + uiHost.setEdgeLinkId(currentElinkId);
372 +
373 + }
374 +
340 HostLocation hloc = h.location(); 375 HostLocation hloc = h.location();
341 uiHost.setLocation(hloc.deviceId(), hloc.port()); 376 uiHost.setLocation(hloc.deviceId(), hloc.port());
342 - addNewEdgeLink(uiHost);
343 } 377 }
344 378
345 private void loadHosts() { 379 private void loadHosts() {
...@@ -364,9 +398,17 @@ class ModelCache { ...@@ -364,9 +398,17 @@ class ModelCache {
364 // invoked from UiSharedTopologyModel host listener 398 // invoked from UiSharedTopologyModel host listener
365 void moveHost(Host host, Host prevHost) { 399 void moveHost(Host host, Host prevHost) {
366 UiHost uiHost = uiTopology.findHost(prevHost.id()); 400 UiHost uiHost = uiTopology.findHost(prevHost.id());
367 - updateHost(uiHost, host); 401 + if (uiHost != null) {
402 + updateHost(uiHost, host);
403 + postEvent(HOST_MOVED, uiHost);
404 + } else {
405 + log.warn(E_NO_ELEMENT, "host", prevHost.id());
406 + }
407 + }
368 408
369 - postEvent(HOST_MOVED, uiHost); 409 + // package private for unit test access
410 + UiHost accessHost(HostId id) {
411 + return uiTopology.findHost(id);
370 } 412 }
371 413
372 // invoked from UiSharedTopologyModel host listener 414 // invoked from UiSharedTopologyModel host listener
...@@ -374,8 +416,9 @@ class ModelCache { ...@@ -374,8 +416,9 @@ class ModelCache {
374 HostId id = host.id(); 416 HostId id = host.id();
375 UiHost uiHost = uiTopology.findHost(id); 417 UiHost uiHost = uiTopology.findHost(id);
376 if (uiHost != null) { 418 if (uiHost != null) {
419 + UiLink edgeLink = uiTopology.findLink(uiHost.edgeLinkId());
420 + uiTopology.remove(edgeLink);
377 uiTopology.remove(uiHost); 421 uiTopology.remove(uiHost);
378 - removeOldEdgeLink(uiHost);
379 postEvent(HOST_REMOVED, uiHost); 422 postEvent(HOST_REMOVED, uiHost);
380 } else { 423 } else {
381 log.warn(E_NO_ELEMENT, "host", id); 424 log.warn(E_NO_ELEMENT, "host", id);
...@@ -386,6 +429,15 @@ class ModelCache { ...@@ -386,6 +429,15 @@ class ModelCache {
386 // === CACHE STATISTICS 429 // === CACHE STATISTICS
387 430
388 /** 431 /**
432 + * Returns a detailed (multi-line) string showing the contents of the cache.
433 + *
434 + * @return detailed string
435 + */
436 + public String dumpString() {
437 + return uiTopology.dumpString();
438 + }
439 +
440 + /**
389 * Returns the number of members in the cluster. 441 * Returns the number of members in the cluster.
390 * 442 *
391 * @return number of cluster members 443 * @return number of cluster members
...@@ -402,4 +454,31 @@ class ModelCache { ...@@ -402,4 +454,31 @@ class ModelCache {
402 public int regionCount() { 454 public int regionCount() {
403 return uiTopology.regionCount(); 455 return uiTopology.regionCount();
404 } 456 }
457 +
458 + /**
459 + * Returns the number of devices in the topology.
460 + *
461 + * @return number of devices
462 + */
463 + public int deviceCount() {
464 + return uiTopology.deviceCount();
465 + }
466 +
467 + /**
468 + * Returns the number of links in the topology.
469 + *
470 + * @return number of links
471 + */
472 + public int linkCount() {
473 + return uiTopology.linkCount();
474 + }
475 +
476 + /**
477 + * Returns the number of hosts in the topology.
478 + *
479 + * @return number of hosts
480 + */
481 + public int hostCount() {
482 + return uiTopology.hostCount();
483 + }
405 } 484 }
......
...@@ -136,15 +136,15 @@ abstract class AbstractTopoModelTest extends AbstractUiImplTest { ...@@ -136,15 +136,15 @@ abstract class AbstractTopoModelTest extends AbstractUiImplTest {
136 protected static final Set<Region> REGION_SET = 136 protected static final Set<Region> REGION_SET =
137 ImmutableSet.of(REGION_1, REGION_2, REGION_3); 137 ImmutableSet.of(REGION_1, REGION_2, REGION_3);
138 138
139 - protected static final String D1 = "D1"; 139 + protected static final String D1 = "d1";
140 - protected static final String D2 = "D2"; 140 + protected static final String D2 = "d2";
141 - protected static final String D3 = "D3"; 141 + protected static final String D3 = "d3";
142 - protected static final String D4 = "D4"; 142 + protected static final String D4 = "d4";
143 - protected static final String D5 = "D5"; 143 + protected static final String D5 = "d5";
144 - protected static final String D6 = "D6"; 144 + protected static final String D6 = "d6";
145 - protected static final String D7 = "D7"; 145 + protected static final String D7 = "d7";
146 - protected static final String D8 = "D8"; 146 + protected static final String D8 = "d8";
147 - protected static final String D9 = "D9"; 147 + protected static final String D9 = "d9";
148 148
149 protected static final String MFR = "Mfr"; 149 protected static final String MFR = "Mfr";
150 protected static final String HW = "h/w"; 150 protected static final String HW = "h/w";
...@@ -303,7 +303,6 @@ abstract class AbstractTopoModelTest extends AbstractUiImplTest { ...@@ -303,7 +303,6 @@ abstract class AbstractTopoModelTest extends AbstractUiImplTest {
303 private static final HostService MOCK_HOST = new MockHostService(); 303 private static final HostService MOCK_HOST = new MockHostService();
304 304
305 305
306 -
307 private static class MockClusterService extends ClusterServiceAdapter { 306 private static class MockClusterService extends ClusterServiceAdapter {
308 private final Map<NodeId, ControllerNode> nodes = new HashMap<>(); 307 private final Map<NodeId, ControllerNode> nodes = new HashMap<>();
309 private final Map<NodeId, ControllerNode.State> states = new HashMap<>(); 308 private final Map<NodeId, ControllerNode.State> states = new HashMap<>();
...@@ -470,38 +469,46 @@ abstract class AbstractTopoModelTest extends AbstractUiImplTest { ...@@ -470,38 +469,46 @@ abstract class AbstractTopoModelTest extends AbstractUiImplTest {
470 469
471 } 470 }
472 471
472 + /**
473 + * Synthesizes a pair of unidirectional links between two devices. The
474 + * string array should be of the form:
475 + * <pre>
476 + * { "device-A-id", "device-A-port", "device-B-id", "device-B-port" }
477 + * </pre>
478 + *
479 + * @param linkPairData device ids and ports
480 + * @return pair of synthesized links
481 + */
482 + protected static List<Link> makeLinkPair(String[] linkPairData) {
483 + DeviceId devA = deviceId(linkPairData[0]);
484 + PortNumber portA = portNumber(Long.valueOf(linkPairData[1]));
485 + DeviceId devB = deviceId(linkPairData[2]);
486 + PortNumber portB = portNumber(Long.valueOf(linkPairData[3]));
487 +
488 + Link linkA = DefaultLink.builder()
489 + .providerId(ProviderId.NONE)
490 + .type(Link.Type.DIRECT)
491 + .src(new ConnectPoint(devA, portA))
492 + .dst(new ConnectPoint(devB, portB))
493 + .build();
494 +
495 + Link linkB = DefaultLink.builder()
496 + .providerId(ProviderId.NONE)
497 + .type(Link.Type.DIRECT)
498 + .src(new ConnectPoint(devB, portB))
499 + .dst(new ConnectPoint(devA, portA))
500 + .build();
501 +
502 + return ImmutableList.of(linkA, linkB);
503 + }
473 504
474 private static class MockLinkService extends LinkServiceAdapter { 505 private static class MockLinkService extends LinkServiceAdapter {
475 private final Set<Link> links = new HashSet<>(); 506 private final Set<Link> links = new HashSet<>();
476 507
477 MockLinkService() { 508 MockLinkService() {
478 for (String[] linkPair : LINK_CONNECT_DATA) { 509 for (String[] linkPair : LINK_CONNECT_DATA) {
479 - links.addAll(makeLinks(linkPair)); 510 + links.addAll(makeLinkPair(linkPair));
480 } 511 }
481 -
482 - }
483 -
484 - private Set<Link> makeLinks(String[] linkPair) {
485 - DeviceId devA = deviceId(linkPair[0]);
486 - PortNumber portA = portNumber(Long.valueOf(linkPair[1]));
487 - DeviceId devB = deviceId(linkPair[2]);
488 - PortNumber portB = portNumber(Long.valueOf(linkPair[3]));
489 -
490 - Link linkA = DefaultLink.builder()
491 - .providerId(ProviderId.NONE)
492 - .type(Link.Type.DIRECT)
493 - .src(new ConnectPoint(devA, portA))
494 - .dst(new ConnectPoint(devB, portB))
495 - .build();
496 -
497 - Link linkB = DefaultLink.builder()
498 - .providerId(ProviderId.NONE)
499 - .type(Link.Type.DIRECT)
500 - .src(new ConnectPoint(devB, portB))
501 - .dst(new ConnectPoint(devA, portA))
502 - .build();
503 -
504 - return ImmutableSet.of(linkA, linkB);
505 } 512 }
506 513
507 @Override 514 @Override
......
...@@ -20,16 +20,27 @@ import org.junit.Before; ...@@ -20,16 +20,27 @@ import org.junit.Before;
20 import org.junit.Test; 20 import org.junit.Test;
21 import org.onosproject.event.Event; 21 import org.onosproject.event.Event;
22 import org.onosproject.event.EventDispatcher; 22 import org.onosproject.event.EventDispatcher;
23 +import org.onosproject.net.Device;
23 import org.onosproject.net.DeviceId; 24 import org.onosproject.net.DeviceId;
25 +import org.onosproject.net.Link;
26 +import org.onosproject.net.region.Region;
24 import org.onosproject.ui.impl.topo.model.UiModelEvent.Type; 27 import org.onosproject.ui.impl.topo.model.UiModelEvent.Type;
25 import org.onosproject.ui.model.topo.UiClusterMember; 28 import org.onosproject.ui.model.topo.UiClusterMember;
29 +import org.onosproject.ui.model.topo.UiDevice;
26 import org.onosproject.ui.model.topo.UiElement; 30 import org.onosproject.ui.model.topo.UiElement;
31 +import org.onosproject.ui.model.topo.UiLink;
32 +import org.onosproject.ui.model.topo.UiLinkId;
33 +import org.onosproject.ui.model.topo.UiRegion;
34 +
35 +import java.util.Collection;
36 +import java.util.Iterator;
27 37
28 import static org.junit.Assert.assertEquals; 38 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertFalse; 39 import static org.junit.Assert.assertFalse;
30 import static org.junit.Assert.assertNotNull; 40 import static org.junit.Assert.assertNotNull;
31 import static org.junit.Assert.assertTrue; 41 import static org.junit.Assert.assertTrue;
32 import static org.onosproject.cluster.NodeId.nodeId; 42 import static org.onosproject.cluster.NodeId.nodeId;
43 +import static org.onosproject.ui.model.topo.UiLinkId.uiLinkId;
33 44
34 /** 45 /**
35 * Unit tests for {@link ModelCache}. 46 * Unit tests for {@link ModelCache}.
...@@ -64,6 +75,12 @@ public class ModelCacheTest extends AbstractTopoModelTest { ...@@ -64,6 +75,12 @@ public class ModelCacheTest extends AbstractTopoModelTest {
64 75
65 private ModelCache cache; 76 private ModelCache cache;
66 77
78 + private void assertContains(String msg, Collection<?> coll, Object... things) {
79 + for (Object o : things) {
80 + assertTrue(msg, coll.contains(o));
81 + }
82 + }
83 +
67 @Before 84 @Before
68 public void setUp() { 85 public void setUp() {
69 cache = new ModelCache(MOCK_SERVICES, dispatcher); 86 cache = new ModelCache(MOCK_SERVICES, dispatcher);
...@@ -98,6 +115,20 @@ public class ModelCacheTest extends AbstractTopoModelTest { ...@@ -98,6 +115,20 @@ public class ModelCacheTest extends AbstractTopoModelTest {
98 } 115 }
99 116
100 @Test 117 @Test
118 + public void nonExistentClusterMember() {
119 + title("nonExistentClusterMember");
120 + cache.addOrUpdateClusterMember(CNODE_1);
121 + print(cache);
122 + assertEquals("unex # members", 1, cache.clusterMemberCount());
123 + dispatcher.assertEventCount(1);
124 + dispatcher.assertLast(Type.CLUSTER_MEMBER_ADDED_OR_UPDATED, C1);
125 +
126 + cache.removeClusterMember(CNODE_2);
127 + assertEquals("unex # members", 1, cache.clusterMemberCount());
128 + dispatcher.assertEventCount(1);
129 + }
130 +
131 + @Test
101 public void createThreeNodeCluster() { 132 public void createThreeNodeCluster() {
102 title("createThreeNodeCluster"); 133 title("createThreeNodeCluster");
103 cache.addOrUpdateClusterMember(CNODE_1); 134 cache.addOrUpdateClusterMember(CNODE_1);
...@@ -146,23 +177,145 @@ public class ModelCacheTest extends AbstractTopoModelTest { ...@@ -146,23 +177,145 @@ public class ModelCacheTest extends AbstractTopoModelTest {
146 public void addNodeAndDevices() { 177 public void addNodeAndDevices() {
147 title("addNodeAndDevices"); 178 title("addNodeAndDevices");
148 cache.addOrUpdateClusterMember(CNODE_1); 179 cache.addOrUpdateClusterMember(CNODE_1);
180 + dispatcher.assertLast(Type.CLUSTER_MEMBER_ADDED_OR_UPDATED, C1);
149 cache.addOrUpdateDevice(DEV_1); 181 cache.addOrUpdateDevice(DEV_1);
182 + dispatcher.assertLast(Type.DEVICE_ADDED_OR_UPDATED, D1);
150 cache.addOrUpdateDevice(DEV_2); 183 cache.addOrUpdateDevice(DEV_2);
184 + dispatcher.assertLast(Type.DEVICE_ADDED_OR_UPDATED, D2);
151 cache.addOrUpdateDevice(DEV_3); 185 cache.addOrUpdateDevice(DEV_3);
186 + dispatcher.assertLast(Type.DEVICE_ADDED_OR_UPDATED, D3);
187 + dispatcher.assertEventCount(4);
152 print(cache); 188 print(cache);
189 +
190 + assertEquals("unex # nodes", 1, cache.clusterMemberCount());
191 + assertEquals("unex # devices", 3, cache.deviceCount());
192 + cache.removeDevice(DEV_4);
193 + assertEquals("unex # devices", 3, cache.deviceCount());
194 + dispatcher.assertEventCount(4);
195 +
196 + cache.removeDevice(DEV_2);
197 + dispatcher.assertLast(Type.DEVICE_REMOVED, D2);
198 + dispatcher.assertEventCount(5);
199 +
200 + // check out details of device
201 + UiDevice dev = cache.accessDevice(DEVID_1);
202 + assertEquals("wrong id", D1, dev.idAsString());
203 + assertEquals("wrong region", R1, dev.regionId().toString());
204 + Device d = dev.backingDevice();
205 + assertEquals("wrong serial", SERIAL, d.serialNumber());
153 } 206 }
154 207
155 @Test 208 @Test
156 public void addRegions() { 209 public void addRegions() {
157 title("addRegions"); 210 title("addRegions");
158 cache.addOrUpdateRegion(REGION_1); 211 cache.addOrUpdateRegion(REGION_1);
212 + dispatcher.assertLast(Type.REGION_ADDED_OR_UPDATED, R1);
213 + dispatcher.assertEventCount(1);
214 + assertEquals("unex # regions", 1, cache.regionCount());
215 +
216 + cache.addOrUpdateRegion(REGION_2);
217 + dispatcher.assertLast(Type.REGION_ADDED_OR_UPDATED, R2);
218 + dispatcher.assertEventCount(2);
219 + assertEquals("unex # regions", 2, cache.regionCount());
220 +
159 print(cache); 221 print(cache);
222 +
223 + cache.removeRegion(REGION_3);
224 + dispatcher.assertEventCount(2);
225 + assertEquals("unex # regions", 2, cache.regionCount());
226 +
227 + cache.removeRegion(REGION_1);
228 + dispatcher.assertLast(Type.REGION_REMOVED, R1);
229 + dispatcher.assertEventCount(3);
230 + assertEquals("unex # regions", 1, cache.regionCount());
231 +
232 + print(cache);
233 +
234 + UiRegion region = cache.accessRegion(REGION_2.id());
235 + assertEquals("wrong id", REGION_2.id(), region.id());
236 + assertEquals("unex # device IDs", 3, region.deviceIds().size());
237 + assertContains("missing ID", region.deviceIds(), DEVID_4, DEVID_5, DEVID_6);
238 + Region r = region.backingRegion();
239 + print(r);
240 + assertEquals("wrong region name", "Region-R2", r.name());
241 + }
242 +
243 + private static final String[] LINKS_2_7 = {D2, "27", D7, "72"};
244 +
245 + @Test
246 + public void addLinks() {
247 + title("addLinks");
248 +
249 + Iterator<Link> iter = makeLinkPair(LINKS_2_7).iterator();
250 + Link link1 = iter.next();
251 + Link link2 = iter.next();
252 + print(link1);
253 + print(link2);
254 +
255 + UiLinkId idA2B = uiLinkId(link1);
256 + UiLinkId idB2A = uiLinkId(link2);
257 + // remember, link IDs are canonicalized
258 + assertEquals("not same link ID", idA2B, idB2A);
259 +
260 + // we've established that the ID is the same for both
261 + UiLinkId linkId = idA2B;
262 +
263 + cache.addOrUpdateLink(link1);
264 + dispatcher.assertLast(Type.LINK_ADDED_OR_UPDATED, linkId.toString());
265 + dispatcher.assertEventCount(1);
266 + assertEquals("unex # links", 1, cache.linkCount());
267 +
268 + UiLink link = cache.accessLink(linkId);
269 + assertEquals("dev A not d2", DEVID_2, link.deviceA());
270 + assertEquals("dev B not d7", DEVID_7, link.deviceB());
271 + assertEquals("wrong backing link A-B", link1, link.linkAtoB());
272 + assertEquals("backing link B-A?", null, link.linkBtoA());
273 +
274 + cache.addOrUpdateLink(link2);
275 + dispatcher.assertLast(Type.LINK_ADDED_OR_UPDATED, linkId.toString());
276 + dispatcher.assertEventCount(2);
277 + // NOTE: yes! expect 1 UiLink
278 + assertEquals("unex # links", 1, cache.linkCount());
279 +
280 + link = cache.accessLink(linkId);
281 + assertEquals("dev A not d2", DEVID_2, link.deviceA());
282 + assertEquals("dev B not d7", DEVID_7, link.deviceB());
283 + assertEquals("wrong backing link A-B", link1, link.linkAtoB());
284 + assertEquals("wrong backing link B-A", link2, link.linkBtoA());
285 +
286 + // now remove links one at a time
287 + cache.removeLink(link1);
288 + // NOTE: yes! ADD_OR_UPDATE, since the link was updated
289 + dispatcher.assertLast(Type.LINK_ADDED_OR_UPDATED, linkId.toString());
290 + dispatcher.assertEventCount(3);
291 + // NOTE: yes! expect 1 UiLink (still)
292 + assertEquals("unex # links", 1, cache.linkCount());
293 +
294 + link = cache.accessLink(linkId);
295 + assertEquals("dev A not d2", DEVID_2, link.deviceA());
296 + assertEquals("dev B not d7", DEVID_7, link.deviceB());
297 + assertEquals("backing link A-B?", null, link.linkAtoB());
298 + assertEquals("wrong backing link B-A", link2, link.linkBtoA());
299 +
300 + // remove final link
301 + cache.removeLink(link2);
302 + dispatcher.assertLast(Type.LINK_REMOVED, linkId.toString());
303 + dispatcher.assertEventCount(4);
304 + // NOTE: finally link should be removed from cache
305 + assertEquals("unex # links", 0, cache.linkCount());
160 } 306 }
161 307
162 @Test 308 @Test
163 public void load() { 309 public void load() {
164 title("load"); 310 title("load");
165 cache.load(); 311 cache.load();
166 - print(cache); 312 + print(cache.dumpString());
313 +
314 + // See mock service bundle for expected values (AbstractTopoModelTest)
315 + assertEquals("unex # cnodes", 3, cache.clusterMemberCount());
316 + assertEquals("unex # regions", 3, cache.regionCount());
317 + assertEquals("unex # devices", 9, cache.deviceCount());
318 + assertEquals("unex # hosts", 18, cache.hostCount());
319 + assertEquals("unex # hosts", 26, cache.linkCount());
167 } 320 }
168 } 321 }
......