Yuta HIGUCHI

implemented annotation merging on SimpleDeviceStore.

- Added annotation support to PortsDescriptions

Change-Id: I157e4fb93b8f387b405722b8d004501d993decda
1 package org.onlab.onos.net; 1 package org.onlab.onos.net;
2 2
3 +import java.util.Collections;
3 import java.util.HashMap; 4 import java.util.HashMap;
4 import java.util.Map; 5 import java.util.Map;
5 import java.util.Objects; 6 import java.util.Objects;
...@@ -71,9 +72,33 @@ public final class DefaultAnnotations implements SparseAnnotations { ...@@ -71,9 +72,33 @@ public final class DefaultAnnotations implements SparseAnnotations {
71 return new DefaultAnnotations(merged); 72 return new DefaultAnnotations(merged);
72 } 73 }
73 74
75 + /**
76 + * Convert Annotations to DefaultAnnotations if needed and merges.
77 + *
78 + * @see #merge(DefaultAnnotations, SparseAnnotations)
79 + *
80 + * @param annotations base annotations
81 + * @param sparseAnnotations additional sparse annotations
82 + * @return combined annotations or the original base annotations if there
83 + * are not additional annotations
84 + */
85 + public static DefaultAnnotations merge(Annotations annotations,
86 + SparseAnnotations sparseAnnotations) {
87 + if (annotations instanceof DefaultAnnotations) {
88 + return merge((DefaultAnnotations) annotations, sparseAnnotations);
89 + }
90 +
91 + DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
92 + for (String key : annotations.keys()) {
93 + builder.set(key, annotations.value(key));
94 + }
95 + return merge(builder.build(), sparseAnnotations);
96 + }
97 +
74 @Override 98 @Override
75 public Set<String> keys() { 99 public Set<String> keys() {
76 - return map.keySet(); 100 + // TODO: unmodifiable to be removed after switching to ImmutableMap;
101 + return Collections.unmodifiableSet(map.keySet());
77 } 102 }
78 103
79 @Override 104 @Override
......
...@@ -45,6 +45,18 @@ public class DefaultDeviceDescription extends AbstractDescription ...@@ -45,6 +45,18 @@ public class DefaultDeviceDescription extends AbstractDescription
45 this.serialNumber = serialNumber; 45 this.serialNumber = serialNumber;
46 } 46 }
47 47
48 + /**
49 + * Creates a device description using the supplied information.
50 + * @param base DeviceDescription to basic information
51 + * @param annotations Annotations to use.
52 + */
53 + public DefaultDeviceDescription(DeviceDescription base,
54 + SparseAnnotations... annotations) {
55 + this(base.deviceURI(), base.type(), base.manufacturer(),
56 + base.hwVersion(), base.swVersion(), base.serialNumber(),
57 + annotations);
58 + }
59 +
48 @Override 60 @Override
49 public URI deviceURI() { 61 public URI deviceURI() {
50 return uri; 62 return uri;
......
1 package org.onlab.onos.net.device; 1 package org.onlab.onos.net.device;
2 2
3 +import org.onlab.onos.net.AbstractDescription;
3 import org.onlab.onos.net.PortNumber; 4 import org.onlab.onos.net.PortNumber;
5 +import org.onlab.onos.net.SparseAnnotations;
4 6
5 /** 7 /**
6 * Default implementation of immutable port description. 8 * Default implementation of immutable port description.
7 */ 9 */
8 -public class DefaultPortDescription implements PortDescription { 10 +public class DefaultPortDescription extends AbstractDescription
11 + implements PortDescription {
9 12
10 private final PortNumber number; 13 private final PortNumber number;
11 private final boolean isEnabled; 14 private final boolean isEnabled;
12 15
13 - public DefaultPortDescription(PortNumber number, boolean isEnabled) { 16 + /**
17 + * Creates a port description using the supplied information.
18 + *
19 + * @param number port number
20 + * @param isEnabled port enabled state
21 + * @param annotations optional key/value annotations map
22 + */
23 + public DefaultPortDescription(PortNumber number, boolean isEnabled,
24 + SparseAnnotations... annotations) {
25 + super(annotations);
14 this.number = number; 26 this.number = number;
15 this.isEnabled = isEnabled; 27 this.isEnabled = isEnabled;
16 } 28 }
17 29
30 + /**
31 + * Creates a port description using the supplied information.
32 + *
33 + * @param base PortDescription to get basic information from
34 + * @param annotations optional key/value annotations map
35 + */
36 + public DefaultPortDescription(PortDescription base,
37 + SparseAnnotations annotations) {
38 + this(base.portNumber(), base.isEnabled(), annotations);
39 + }
40 +
18 @Override 41 @Override
19 public PortNumber portNumber() { 42 public PortNumber portNumber() {
20 return number; 43 return number;
......
1 package org.onlab.onos.net.device; 1 package org.onlab.onos.net.device;
2 2
3 +import org.onlab.onos.net.Description;
3 import org.onlab.onos.net.PortNumber; 4 import org.onlab.onos.net.PortNumber;
4 5
5 /** 6 /**
6 * Information about a port. 7 * Information about a port.
7 */ 8 */
8 -public interface PortDescription { 9 +public interface PortDescription extends Description {
9 10
10 // TODO: possibly relocate this to a common ground so that this can also used by host tracking if required 11 // TODO: possibly relocate this to a common ground so that this can also used by host tracking if required
11 12
......
...@@ -9,6 +9,8 @@ import org.apache.felix.scr.annotations.Activate; ...@@ -9,6 +9,8 @@ import org.apache.felix.scr.annotations.Activate;
9 import org.apache.felix.scr.annotations.Component; 9 import org.apache.felix.scr.annotations.Component;
10 import org.apache.felix.scr.annotations.Deactivate; 10 import org.apache.felix.scr.annotations.Deactivate;
11 import org.apache.felix.scr.annotations.Service; 11 import org.apache.felix.scr.annotations.Service;
12 +import org.onlab.onos.net.Annotations;
13 +import org.onlab.onos.net.DefaultAnnotations;
12 import org.onlab.onos.net.DefaultDevice; 14 import org.onlab.onos.net.DefaultDevice;
13 import org.onlab.onos.net.DefaultPort; 15 import org.onlab.onos.net.DefaultPort;
14 import org.onlab.onos.net.Device; 16 import org.onlab.onos.net.Device;
...@@ -16,6 +18,9 @@ import org.onlab.onos.net.Device.Type; ...@@ -16,6 +18,9 @@ import org.onlab.onos.net.Device.Type;
16 import org.onlab.onos.net.DeviceId; 18 import org.onlab.onos.net.DeviceId;
17 import org.onlab.onos.net.Port; 19 import org.onlab.onos.net.Port;
18 import org.onlab.onos.net.PortNumber; 20 import org.onlab.onos.net.PortNumber;
21 +import org.onlab.onos.net.SparseAnnotations;
22 +import org.onlab.onos.net.device.DefaultDeviceDescription;
23 +import org.onlab.onos.net.device.DefaultPortDescription;
19 import org.onlab.onos.net.device.DeviceDescription; 24 import org.onlab.onos.net.device.DeviceDescription;
20 import org.onlab.onos.net.device.DeviceEvent; 25 import org.onlab.onos.net.device.DeviceEvent;
21 import org.onlab.onos.net.device.DeviceStore; 26 import org.onlab.onos.net.device.DeviceStore;
...@@ -45,6 +50,7 @@ import static com.google.common.base.Predicates.notNull; ...@@ -45,6 +50,7 @@ import static com.google.common.base.Predicates.notNull;
45 import static org.onlab.onos.net.device.DeviceEvent.Type.*; 50 import static org.onlab.onos.net.device.DeviceEvent.Type.*;
46 import static org.slf4j.LoggerFactory.getLogger; 51 import static org.slf4j.LoggerFactory.getLogger;
47 import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked; 52 import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
53 +import static org.onlab.onos.net.DefaultAnnotations.merge;
48 54
49 // TODO: synchronization should be done in more fine-grained manner. 55 // TODO: synchronization should be done in more fine-grained manner.
50 /** 56 /**
...@@ -112,8 +118,8 @@ public class SimpleDeviceStore ...@@ -112,8 +118,8 @@ public class SimpleDeviceStore
112 = createIfAbsentUnchecked(providerDescs, providerId, 118 = createIfAbsentUnchecked(providerDescs, providerId,
113 new InitDeviceDescs(deviceDescription)); 119 new InitDeviceDescs(deviceDescription));
114 120
121 + // update description
115 descs.putDeviceDesc(deviceDescription); 122 descs.putDeviceDesc(deviceDescription);
116 -
117 Device newDevice = composeDevice(deviceId, providerDescs); 123 Device newDevice = composeDevice(deviceId, providerDescs);
118 124
119 if (oldDevice == null) { 125 if (oldDevice == null) {
...@@ -144,7 +150,8 @@ public class SimpleDeviceStore ...@@ -144,7 +150,8 @@ public class SimpleDeviceStore
144 150
145 // We allow only certain attributes to trigger update 151 // We allow only certain attributes to trigger update
146 if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) || 152 if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
147 - !Objects.equals(oldDevice.swVersion(), newDevice.swVersion())) { 153 + !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()) ||
154 + !isAnnotationsEqual(oldDevice.annotations(), newDevice.annotations())) {
148 155
149 synchronized (this) { 156 synchronized (this) {
150 devices.replace(newDevice.id(), oldDevice, newDevice); 157 devices.replace(newDevice.id(), oldDevice, newDevice);
...@@ -203,7 +210,7 @@ public class SimpleDeviceStore ...@@ -203,7 +210,7 @@ public class SimpleDeviceStore
203 PortNumber number = portDescription.portNumber(); 210 PortNumber number = portDescription.portNumber();
204 Port oldPort = ports.get(number); 211 Port oldPort = ports.get(number);
205 // update description 212 // update description
206 - descs.putPortDesc(number, portDescription); 213 + descs.putPortDesc(portDescription);
207 Port newPort = composePort(device, number, descsMap); 214 Port newPort = composePort(device, number, descsMap);
208 215
209 events.add(oldPort == null ? 216 events.add(oldPort == null ?
...@@ -225,12 +232,14 @@ public class SimpleDeviceStore ...@@ -225,12 +232,14 @@ public class SimpleDeviceStore
225 return new DeviceEvent(PORT_ADDED, device, newPort); 232 return new DeviceEvent(PORT_ADDED, device, newPort);
226 } 233 }
227 234
228 - // CHecks if the specified port requires update and if so, it replaces the 235 + // Checks if the specified port requires update and if so, it replaces the
229 // existing entry in the map and returns corresponding event. 236 // existing entry in the map and returns corresponding event.
230 private DeviceEvent updatePort(Device device, Port oldPort, 237 private DeviceEvent updatePort(Device device, Port oldPort,
231 Port newPort, 238 Port newPort,
232 ConcurrentMap<PortNumber, Port> ports) { 239 ConcurrentMap<PortNumber, Port> ports) {
233 - if (oldPort.isEnabled() != newPort.isEnabled()) { 240 + if (oldPort.isEnabled() != newPort.isEnabled() ||
241 + !isAnnotationsEqual(oldPort.annotations(), newPort.annotations())) {
242 +
234 ports.put(oldPort.number(), newPort); 243 ports.put(oldPort.number(), newPort);
235 return new DeviceEvent(PORT_UPDATED, device, newPort); 244 return new DeviceEvent(PORT_UPDATED, device, newPort);
236 } 245 }
...@@ -272,17 +281,17 @@ public class SimpleDeviceStore ...@@ -272,17 +281,17 @@ public class SimpleDeviceStore
272 checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId); 281 checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
273 282
274 DeviceDescriptions descs = descsMap.get(providerId); 283 DeviceDescriptions descs = descsMap.get(providerId);
284 + // assuming all providers must to give DeviceDescription
275 checkArgument(descs != null, 285 checkArgument(descs != null,
276 "Device description for Device ID %s from Provider %s was not found", 286 "Device description for Device ID %s from Provider %s was not found",
277 deviceId, providerId); 287 deviceId, providerId);
278 288
279 - // TODO: implement multi-provider
280 synchronized (this) { 289 synchronized (this) {
281 ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId); 290 ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
282 final PortNumber number = portDescription.portNumber(); 291 final PortNumber number = portDescription.portNumber();
283 Port oldPort = ports.get(number); 292 Port oldPort = ports.get(number);
284 // update description 293 // update description
285 - descs.putPortDesc(number, portDescription); 294 + descs.putPortDesc(portDescription);
286 Port newPort = composePort(device, number, descsMap); 295 Port newPort = composePort(device, number, descsMap);
287 if (oldPort == null) { 296 if (oldPort == null) {
288 return createPort(device, newPort, ports); 297 return createPort(device, newPort, ports);
...@@ -321,6 +330,26 @@ public class SimpleDeviceStore ...@@ -321,6 +330,26 @@ public class SimpleDeviceStore
321 } 330 }
322 } 331 }
323 332
333 + private static boolean isAnnotationsEqual(Annotations lhs, Annotations rhs) {
334 + if (lhs == rhs) {
335 + return true;
336 + }
337 + if (lhs == null || rhs == null) {
338 + return false;
339 + }
340 +
341 + if (!lhs.keys().equals(rhs.keys())) {
342 + return false;
343 + }
344 +
345 + for (String key : lhs.keys()) {
346 + if (!lhs.value(key).equals(rhs.value(key))) {
347 + return false;
348 + }
349 + }
350 + return true;
351 + }
352 +
324 /** 353 /**
325 * Returns a Device, merging description given from multiple Providers. 354 * Returns a Device, merging description given from multiple Providers.
326 * 355 *
...@@ -336,46 +365,67 @@ public class SimpleDeviceStore ...@@ -336,46 +365,67 @@ public class SimpleDeviceStore
336 ProviderId primary = pickPrimaryPID(providerDescs); 365 ProviderId primary = pickPrimaryPID(providerDescs);
337 366
338 DeviceDescriptions desc = providerDescs.get(primary); 367 DeviceDescriptions desc = providerDescs.get(primary);
368 +
369 + // base
339 Type type = desc.getDeviceDesc().type(); 370 Type type = desc.getDeviceDesc().type();
340 String manufacturer = desc.getDeviceDesc().manufacturer(); 371 String manufacturer = desc.getDeviceDesc().manufacturer();
341 String hwVersion = desc.getDeviceDesc().hwVersion(); 372 String hwVersion = desc.getDeviceDesc().hwVersion();
342 String swVersion = desc.getDeviceDesc().swVersion(); 373 String swVersion = desc.getDeviceDesc().swVersion();
343 String serialNumber = desc.getDeviceDesc().serialNumber(); 374 String serialNumber = desc.getDeviceDesc().serialNumber();
375 + DefaultAnnotations annotations = DefaultAnnotations.builder().build();
376 + annotations = merge(annotations, desc.getDeviceDesc().annotations());
344 377
345 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) { 378 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
346 if (e.getKey().equals(primary)) { 379 if (e.getKey().equals(primary)) {
347 continue; 380 continue;
348 } 381 }
349 - // FIXME: implement attribute merging once we have K-V attributes 382 + // TODO: should keep track of Description timestamp
383 + // and only merge conflicting keys when timestamp is newer
384 + // Currently assuming there will never be a key conflict between
385 + // providers
386 +
387 + // annotation merging. not so efficient, should revisit later
388 + annotations = merge(annotations, e.getValue().getDeviceDesc().annotations());
350 } 389 }
351 390
352 - return new DefaultDevice(primary, deviceId , type, manufacturer, hwVersion, swVersion, serialNumber); 391 + return new DefaultDevice(primary, deviceId , type, manufacturer,
392 + hwVersion, swVersion, serialNumber, annotations);
353 } 393 }
354 394
355 - // probably want composePorts 395 + // probably want composePort"s" also
356 private Port composePort(Device device, PortNumber number, 396 private Port composePort(Device device, PortNumber number,
357 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) { 397 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
358 398
359 ProviderId primary = pickPrimaryPID(providerDescs); 399 ProviderId primary = pickPrimaryPID(providerDescs);
360 DeviceDescriptions primDescs = providerDescs.get(primary); 400 DeviceDescriptions primDescs = providerDescs.get(primary);
401 + // if no primary, assume not enabled
402 + // TODO: revisit this default port enabled/disabled behavior
403 + boolean isEnabled = false;
404 + DefaultAnnotations annotations = DefaultAnnotations.builder().build();
405 +
361 final PortDescription portDesc = primDescs.getPortDesc(number); 406 final PortDescription portDesc = primDescs.getPortDesc(number);
362 - boolean isEnabled;
363 if (portDesc != null) { 407 if (portDesc != null) {
364 isEnabled = portDesc.isEnabled(); 408 isEnabled = portDesc.isEnabled();
365 - } else { 409 + annotations = merge(annotations, portDesc.annotations());
366 - // if no primary, assume not enabled
367 - // TODO: revisit this port enabled/disabled behavior
368 - isEnabled = false;
369 } 410 }
370 411
371 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) { 412 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
372 if (e.getKey().equals(primary)) { 413 if (e.getKey().equals(primary)) {
373 continue; 414 continue;
374 } 415 }
375 - // FIXME: implement attribute merging once we have K-V attributes 416 + // TODO: should keep track of Description timestamp
417 + // and only merge conflicting keys when timestamp is newer
418 + // Currently assuming there will never be a key conflict between
419 + // providers
420 +
421 + // annotation merging. not so efficient, should revisit later
422 + final PortDescription otherPortDesc = e.getValue().getPortDesc(number);
423 + if (otherPortDesc != null) {
424 + annotations = merge(annotations, otherPortDesc.annotations());
425 + }
376 } 426 }
377 427
378 - return new DefaultPort(device, number, isEnabled); 428 + return new DefaultPort(device, number, isEnabled, annotations);
379 } 429 }
380 430
381 /** 431 /**
...@@ -428,7 +478,7 @@ public class SimpleDeviceStore ...@@ -428,7 +478,7 @@ public class SimpleDeviceStore
428 private final ConcurrentMap<PortNumber, PortDescription> portDescs; 478 private final ConcurrentMap<PortNumber, PortDescription> portDescs;
429 479
430 public DeviceDescriptions(DeviceDescription desc) { 480 public DeviceDescriptions(DeviceDescription desc) {
431 - this.deviceDesc = new AtomicReference<>(desc); 481 + this.deviceDesc = new AtomicReference<>(checkNotNull(desc));
432 this.portDescs = new ConcurrentHashMap<>(); 482 this.portDescs = new ConcurrentHashMap<>();
433 } 483 }
434 484
...@@ -444,12 +494,38 @@ public class SimpleDeviceStore ...@@ -444,12 +494,38 @@ public class SimpleDeviceStore
444 return Collections.unmodifiableCollection(portDescs.values()); 494 return Collections.unmodifiableCollection(portDescs.values());
445 } 495 }
446 496
447 - public DeviceDescription putDeviceDesc(DeviceDescription newDesc) { 497 + /**
448 - return deviceDesc.getAndSet(newDesc); 498 + * Puts DeviceDescription, merging annotations as necessary.
499 + *
500 + * @param newDesc new DeviceDescription
501 + * @return previous DeviceDescription
502 + */
503 + public synchronized DeviceDescription putDeviceDesc(DeviceDescription newDesc) {
504 + DeviceDescription oldOne = deviceDesc.get();
505 + DeviceDescription newOne = newDesc;
506 + if (oldOne != null) {
507 + SparseAnnotations merged = merge(oldOne.annotations(),
508 + newDesc.annotations());
509 + newOne = new DefaultDeviceDescription(newOne, merged);
510 + }
511 + return deviceDesc.getAndSet(newOne);
449 } 512 }
450 513
451 - public PortDescription putPortDesc(PortNumber number, PortDescription newDesc) { 514 + /**
452 - return portDescs.put(number, newDesc); 515 + * Puts PortDescription, merging annotations as necessary.
516 + *
517 + * @param newDesc new PortDescription
518 + * @return previous PortDescription
519 + */
520 + public synchronized PortDescription putPortDesc(PortDescription newDesc) {
521 + PortDescription oldOne = portDescs.get(newDesc.portNumber());
522 + PortDescription newOne = newDesc;
523 + if (oldOne != null) {
524 + SparseAnnotations merged = merge(oldOne.annotations(),
525 + newDesc.annotations());
526 + newOne = new DefaultPortDescription(newOne, merged);
527 + }
528 + return portDescs.put(newOne.portNumber(), newOne);
453 } 529 }
454 } 530 }
455 } 531 }
......
...@@ -22,10 +22,13 @@ import org.junit.Before; ...@@ -22,10 +22,13 @@ import org.junit.Before;
22 import org.junit.BeforeClass; 22 import org.junit.BeforeClass;
23 import org.junit.Ignore; 23 import org.junit.Ignore;
24 import org.junit.Test; 24 import org.junit.Test;
25 +import org.onlab.onos.net.Annotations;
26 +import org.onlab.onos.net.DefaultAnnotations;
25 import org.onlab.onos.net.Device; 27 import org.onlab.onos.net.Device;
26 import org.onlab.onos.net.DeviceId; 28 import org.onlab.onos.net.DeviceId;
27 import org.onlab.onos.net.Port; 29 import org.onlab.onos.net.Port;
28 import org.onlab.onos.net.PortNumber; 30 import org.onlab.onos.net.PortNumber;
31 +import org.onlab.onos.net.SparseAnnotations;
29 import org.onlab.onos.net.device.DefaultDeviceDescription; 32 import org.onlab.onos.net.device.DefaultDeviceDescription;
30 import org.onlab.onos.net.device.DefaultPortDescription; 33 import org.onlab.onos.net.device.DefaultPortDescription;
31 import org.onlab.onos.net.device.DeviceDescription; 34 import org.onlab.onos.net.device.DeviceDescription;
...@@ -57,6 +60,23 @@ public class SimpleDeviceStoreTest { ...@@ -57,6 +60,23 @@ public class SimpleDeviceStoreTest {
57 private static final PortNumber P2 = PortNumber.portNumber(2); 60 private static final PortNumber P2 = PortNumber.portNumber(2);
58 private static final PortNumber P3 = PortNumber.portNumber(3); 61 private static final PortNumber P3 = PortNumber.portNumber(3);
59 62
63 + private static final SparseAnnotations A1 = DefaultAnnotations.builder()
64 + .set("A1", "a1")
65 + .set("B1", "b1")
66 + .build();
67 + private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
68 + .remove("A1")
69 + .set("B3", "b3")
70 + .build();
71 + private static final SparseAnnotations A2 = DefaultAnnotations.builder()
72 + .set("A2", "a2")
73 + .set("B2", "b2")
74 + .build();
75 + private static final SparseAnnotations A2_2 = DefaultAnnotations.builder()
76 + .remove("A2")
77 + .set("B4", "b4")
78 + .build();
79 +
60 private SimpleDeviceStore simpleDeviceStore; 80 private SimpleDeviceStore simpleDeviceStore;
61 private DeviceStore deviceStore; 81 private DeviceStore deviceStore;
62 82
...@@ -106,6 +126,24 @@ public class SimpleDeviceStoreTest { ...@@ -106,6 +126,24 @@ public class SimpleDeviceStoreTest {
106 assertEquals(SN, device.serialNumber()); 126 assertEquals(SN, device.serialNumber());
107 } 127 }
108 128
129 + /**
130 + * Verifies that Annotations created by merging {@code annotations} is
131 + * equal to actual Annotations.
132 + *
133 + * @param actual Annotations to check
134 + * @param annotations
135 + */
136 + private static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
137 + DefaultAnnotations expected = DefaultAnnotations.builder().build();
138 + for (SparseAnnotations a : annotations) {
139 + expected = DefaultAnnotations.merge(expected, a);
140 + }
141 + assertEquals(expected.keys(), actual.keys());
142 + for (String key : expected.keys()) {
143 + assertEquals(expected.value(key), actual.value(key));
144 + }
145 + }
146 +
109 @Test 147 @Test
110 public final void testGetDeviceCount() { 148 public final void testGetDeviceCount() {
111 assertEquals("initialy empty", 0, deviceStore.getDeviceCount()); 149 assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
...@@ -171,26 +209,41 @@ public class SimpleDeviceStoreTest { ...@@ -171,26 +209,41 @@ public class SimpleDeviceStoreTest {
171 public final void testCreateOrUpdateDeviceAncillary() { 209 public final void testCreateOrUpdateDeviceAncillary() {
172 DeviceDescription description = 210 DeviceDescription description =
173 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, 211 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
174 - HW, SW1, SN); 212 + HW, SW1, SN, A2);
175 DeviceEvent event = deviceStore.createOrUpdateDevice(PIDA, DID1, description); 213 DeviceEvent event = deviceStore.createOrUpdateDevice(PIDA, DID1, description);
176 assertEquals(DEVICE_ADDED, event.type()); 214 assertEquals(DEVICE_ADDED, event.type());
177 assertDevice(DID1, SW1, event.subject()); 215 assertDevice(DID1, SW1, event.subject());
178 assertEquals(PIDA, event.subject().providerId()); 216 assertEquals(PIDA, event.subject().providerId());
217 + assertAnnotationsEquals(event.subject().annotations(), A2);
179 assertFalse("Ancillary will not bring device up", deviceStore.isAvailable(DID1)); 218 assertFalse("Ancillary will not bring device up", deviceStore.isAvailable(DID1));
180 219
181 DeviceDescription description2 = 220 DeviceDescription description2 =
182 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR, 221 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
183 - HW, SW2, SN); 222 + HW, SW2, SN, A1);
184 DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2); 223 DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
185 assertEquals(DEVICE_UPDATED, event2.type()); 224 assertEquals(DEVICE_UPDATED, event2.type());
186 assertDevice(DID1, SW2, event2.subject()); 225 assertDevice(DID1, SW2, event2.subject());
187 assertEquals(PID, event2.subject().providerId()); 226 assertEquals(PID, event2.subject().providerId());
227 + assertAnnotationsEquals(event2.subject().annotations(), A1, A2);
188 assertTrue(deviceStore.isAvailable(DID1)); 228 assertTrue(deviceStore.isAvailable(DID1));
189 229
190 assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2)); 230 assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
191 231
192 // For now, Ancillary is ignored once primary appears 232 // For now, Ancillary is ignored once primary appears
193 assertNull("No change expected", deviceStore.createOrUpdateDevice(PIDA, DID1, description)); 233 assertNull("No change expected", deviceStore.createOrUpdateDevice(PIDA, DID1, description));
234 +
235 + // But, Ancillary annotations will be in effect
236 + DeviceDescription description3 =
237 + new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
238 + HW, SW1, SN, A2_2);
239 + DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3);
240 + assertEquals(DEVICE_UPDATED, event3.type());
241 + // basic information will be the one from Primary
242 + assertDevice(DID1, SW2, event3.subject());
243 + assertEquals(PID, event3.subject().providerId());
244 + // but annotation from Ancillary will be merged
245 + assertAnnotationsEquals(event3.subject().annotations(), A1, A2, A2_2);
246 + assertTrue(deviceStore.isAvailable(DID1));
194 } 247 }
195 248
196 249
...@@ -299,27 +352,40 @@ public class SimpleDeviceStoreTest { ...@@ -299,27 +352,40 @@ public class SimpleDeviceStoreTest {
299 putDeviceAncillary(DID1, SW1); 352 putDeviceAncillary(DID1, SW1);
300 putDevice(DID1, SW1); 353 putDevice(DID1, SW1);
301 List<PortDescription> pds = Arrays.<PortDescription>asList( 354 List<PortDescription> pds = Arrays.<PortDescription>asList(
302 - new DefaultPortDescription(P1, true) 355 + new DefaultPortDescription(P1, true, A1)
303 ); 356 );
304 deviceStore.updatePorts(PID, DID1, pds); 357 deviceStore.updatePorts(PID, DID1, pds);
305 358
306 DeviceEvent event = deviceStore.updatePortStatus(PID, DID1, 359 DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
307 - new DefaultPortDescription(P1, false)); 360 + new DefaultPortDescription(P1, false, A1_2));
308 assertEquals(PORT_UPDATED, event.type()); 361 assertEquals(PORT_UPDATED, event.type());
309 assertDevice(DID1, SW1, event.subject()); 362 assertDevice(DID1, SW1, event.subject());
310 assertEquals(P1, event.port().number()); 363 assertEquals(P1, event.port().number());
364 + assertAnnotationsEquals(event.port().annotations(), A1, A1_2);
311 assertFalse("Port is disabled", event.port().isEnabled()); 365 assertFalse("Port is disabled", event.port().isEnabled());
312 366
313 DeviceEvent event2 = deviceStore.updatePortStatus(PIDA, DID1, 367 DeviceEvent event2 = deviceStore.updatePortStatus(PIDA, DID1,
314 new DefaultPortDescription(P1, true)); 368 new DefaultPortDescription(P1, true));
315 assertNull("Ancillary is ignored if primary exists", event2); 369 assertNull("Ancillary is ignored if primary exists", event2);
316 370
371 + // but, Ancillary annotation update will be notified
317 DeviceEvent event3 = deviceStore.updatePortStatus(PIDA, DID1, 372 DeviceEvent event3 = deviceStore.updatePortStatus(PIDA, DID1,
318 - new DefaultPortDescription(P2, true)); 373 + new DefaultPortDescription(P1, true, A2));
319 - assertEquals(PORT_ADDED, event3.type()); 374 + assertEquals(PORT_UPDATED, event3.type());
320 assertDevice(DID1, SW1, event3.subject()); 375 assertDevice(DID1, SW1, event3.subject());
321 - assertEquals(P2, event3.port().number()); 376 + assertEquals(P1, event3.port().number());
322 - assertFalse("Port is disabled if not given from provider", event3.port().isEnabled()); 377 + assertAnnotationsEquals(event3.port().annotations(), A1, A1_2, A2);
378 + assertFalse("Port is disabled", event3.port().isEnabled());
379 +
380 + // port only reported from Ancillary will be notified as down
381 + DeviceEvent event4 = deviceStore.updatePortStatus(PIDA, DID1,
382 + new DefaultPortDescription(P2, true));
383 + assertEquals(PORT_ADDED, event4.type());
384 + assertDevice(DID1, SW1, event4.subject());
385 + assertEquals(P2, event4.port().number());
386 + assertAnnotationsEquals(event4.port().annotations());
387 + assertFalse("Port is disabled if not given from primary provider",
388 + event4.port().isEnabled());
323 } 389 }
324 390
325 @Test 391 @Test
......