blob: 0edbc213ed00f2b2b20e905f9dad789c2e8bcf6e [file] [log] [blame]
Yuta HIGUCHI67a527f2014-10-02 22:23:54 -07001package org.onlab.onos.store.device.impl;
2
3import com.google.common.collect.FluentIterable;
4import com.google.common.collect.ImmutableList;
5
6import org.apache.commons.lang3.concurrent.ConcurrentException;
7import org.apache.commons.lang3.concurrent.ConcurrentInitializer;
8import org.apache.felix.scr.annotations.Activate;
9import org.apache.felix.scr.annotations.Component;
10import org.apache.felix.scr.annotations.Deactivate;
11import org.apache.felix.scr.annotations.Reference;
12import org.apache.felix.scr.annotations.ReferenceCardinality;
13import org.apache.felix.scr.annotations.Service;
14import org.onlab.onos.net.Annotations;
15import org.onlab.onos.net.DefaultAnnotations;
16import org.onlab.onos.net.DefaultDevice;
17import org.onlab.onos.net.DefaultPort;
18import org.onlab.onos.net.Device;
19import org.onlab.onos.net.Device.Type;
20import org.onlab.onos.net.DeviceId;
21import org.onlab.onos.net.Port;
22import org.onlab.onos.net.PortNumber;
23import org.onlab.onos.net.SparseAnnotations;
24import org.onlab.onos.net.device.DefaultDeviceDescription;
25import org.onlab.onos.net.device.DefaultPortDescription;
26import org.onlab.onos.net.device.DeviceDescription;
27import org.onlab.onos.net.device.DeviceEvent;
28import org.onlab.onos.net.device.DeviceStore;
29import org.onlab.onos.net.device.DeviceStoreDelegate;
30import org.onlab.onos.net.device.PortDescription;
31import org.onlab.onos.net.provider.ProviderId;
32import org.onlab.onos.store.AbstractStore;
33import org.onlab.onos.store.ClockService;
34import org.onlab.onos.store.Timestamp;
35import org.onlab.onos.store.common.impl.Timestamped;
36import org.slf4j.Logger;
37
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.HashSet;
41import java.util.Iterator;
42import java.util.List;
43import java.util.Map;
44import java.util.Map.Entry;
45import java.util.Objects;
46import java.util.Set;
47import java.util.concurrent.ConcurrentHashMap;
48import java.util.concurrent.ConcurrentMap;
49import java.util.concurrent.atomic.AtomicReference;
50
51import static com.google.common.base.Preconditions.checkArgument;
52import static com.google.common.base.Preconditions.checkNotNull;
53import static com.google.common.base.Predicates.notNull;
54import static org.onlab.onos.net.device.DeviceEvent.Type.*;
55import static org.slf4j.LoggerFactory.getLogger;
56import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
57import static org.onlab.onos.net.DefaultAnnotations.merge;
58import static com.google.common.base.Verify.verify;
59
60// TODO: implement remove event handling and call *Internal
61/**
62 * Manages inventory of infrastructure devices using gossip protocol to distribute
63 * information.
64 */
65@Component(immediate = true)
66@Service
67public class GossipDeviceStore
68 extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
69 implements DeviceStore {
70
71 private final Logger log = getLogger(getClass());
72
73 public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
74
75 // TODO: Check if inner Map can be replaced with plain Map
76 // innerMap is used to lock a Device, thus instance should never be replaced.
77 // collection of Description given from various providers
78 private final ConcurrentMap<DeviceId,
79 ConcurrentMap<ProviderId, DeviceDescriptions>>
80 deviceDescs = new ConcurrentHashMap<>();
81
82 // cache of Device and Ports generated by compositing descriptions from providers
83 private final ConcurrentMap<DeviceId, Device> devices = new ConcurrentHashMap<>();
84 private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>> devicePorts = new ConcurrentHashMap<>();
85
86 // available(=UP) devices
87 private final Set<DeviceId> availableDevices = new HashSet<>();
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected ClockService clockService;
91
92 @Activate
93 public void activate() {
94 log.info("Started");
95 }
96
97 @Deactivate
98 public void deactivate() {
99 deviceDescs.clear();
100 devices.clear();
101 devicePorts.clear();
102 availableDevices.clear();
103 log.info("Stopped");
104 }
105
106 @Override
107 public int getDeviceCount() {
108 return devices.size();
109 }
110
111 @Override
112 public Iterable<Device> getDevices() {
113 return Collections.unmodifiableCollection(devices.values());
114 }
115
116 @Override
117 public Device getDevice(DeviceId deviceId) {
118 return devices.get(deviceId);
119 }
120
121 @Override
122 public synchronized DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
123 DeviceDescription deviceDescription) {
124 Timestamp newTimestamp = clockService.getTimestamp(deviceId);
125 final Timestamped<DeviceDescription> deltaDesc = new Timestamped<>(deviceDescription, newTimestamp);
126 DeviceEvent event = createOrUpdateDeviceInternal(providerId, deviceId, deltaDesc);
127 if (event != null) {
128 // FIXME: broadcast deltaDesc, UP
129 log.debug("broadcast deltaDesc");
130 }
131 return event;
132 }
133
134 private DeviceEvent createOrUpdateDeviceInternal(ProviderId providerId, DeviceId deviceId,
135 Timestamped<DeviceDescription> deltaDesc) {
136
137 // Collection of DeviceDescriptions for a Device
138 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs
139 = createIfAbsentUnchecked(deviceDescs, deviceId,
140 new InitConcurrentHashMap<ProviderId, DeviceDescriptions>());
141
142
143 DeviceDescriptions descs
144 = createIfAbsentUnchecked(providerDescs, providerId,
145 new InitDeviceDescs(deltaDesc));
146
147 // update description
148 synchronized (providerDescs) {
149 // locking per device
150
151 final Device oldDevice = devices.get(deviceId);
152 final Device newDevice;
153
154 if (deltaDesc == descs.getDeviceDesc() ||
155 deltaDesc.isNewer(descs.getDeviceDesc())) {
156 // on new device or valid update
157 descs.putDeviceDesc(deltaDesc);
158 newDevice = composeDevice(deviceId, providerDescs);
159 } else {
160 // outdated event, ignored.
161 return null;
162 }
163 if (oldDevice == null) {
164 // ADD
165 return createDevice(providerId, newDevice);
166 } else {
167 // UPDATE or ignore (no change or stale)
168 return updateDevice(providerId, oldDevice, newDevice);
169 }
170 }
171 }
172
173 // Creates the device and returns the appropriate event if necessary.
174 // Guarded by deviceDescs value (=locking Device)
175 private DeviceEvent createDevice(ProviderId providerId,
176 Device newDevice) {
177
178 // update composed device cache
179 Device oldDevice = devices.putIfAbsent(newDevice.id(), newDevice);
180 verify(oldDevice == null,
181 "Unexpected Device in cache. PID:%s [old=%s, new=%s]",
182 providerId, oldDevice, newDevice);
183
184 if (!providerId.isAncillary()) {
185 availableDevices.add(newDevice.id());
186 }
187
188 return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, newDevice, null);
189 }
190
191 // Updates the device and returns the appropriate event if necessary.
192 // Guarded by deviceDescs value (=locking Device)
193 private DeviceEvent updateDevice(ProviderId providerId,
194 Device oldDevice, Device newDevice) {
195
196 // We allow only certain attributes to trigger update
197 if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
198 !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()) ||
199 !isAnnotationsEqual(oldDevice.annotations(), newDevice.annotations())) {
200
201 boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice);
202 if (!replaced) {
203 verify(replaced,
204 "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]",
205 providerId, oldDevice, devices.get(newDevice.id())
206 , newDevice);
207 }
208 if (!providerId.isAncillary()) {
209 availableDevices.add(newDevice.id());
210 }
211 return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null);
212 }
213
214 // Otherwise merely attempt to change availability if primary provider
215 if (!providerId.isAncillary()) {
216 boolean added = availableDevices.add(newDevice.id());
217 return !added ? null :
218 new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null);
219 }
220 return null;
221 }
222
223 @Override
224 public DeviceEvent markOffline(DeviceId deviceId) {
225 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs
226 = createIfAbsentUnchecked(deviceDescs, deviceId,
227 new InitConcurrentHashMap<ProviderId, DeviceDescriptions>());
228
229 // locking device
230 synchronized (providerDescs) {
231 Device device = devices.get(deviceId);
232 if (device == null) {
233 return null;
234 }
235 boolean removed = availableDevices.remove(deviceId);
236 if (removed) {
237 // TODO: broadcast ... DOWN only?
238 return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
239
240 }
241 return null;
242 }
243 }
244
245 @Override
246 public synchronized List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
247 List<PortDescription> portDescriptions) {
248 Timestamp newTimestamp = clockService.getTimestamp(deviceId);
249
250 List<Timestamped<PortDescription>> deltaDescs = new ArrayList<>(portDescriptions.size());
251 for (PortDescription e : portDescriptions) {
252 deltaDescs.add(new Timestamped<PortDescription>(e, newTimestamp));
253 }
254
255 List<DeviceEvent> events = updatePortsInternal(providerId, deviceId, deltaDescs);
256 if (!events.isEmpty()) {
257 // FIXME: broadcast deltaDesc, UP
258 log.debug("broadcast deltaDesc");
259 }
260 return events;
261
262 }
263
264 private List<DeviceEvent> updatePortsInternal(ProviderId providerId, DeviceId deviceId,
265 List<Timestamped<PortDescription>> deltaDescs) {
266
267 Device device = devices.get(deviceId);
268 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
269
270 ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
271 checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
272
273 DeviceDescriptions descs = descsMap.get(providerId);
274 // every provider must provide DeviceDescription.
275 checkArgument(descs != null,
276 "Device description for Device ID %s from Provider %s was not found",
277 deviceId, providerId);
278
279 List<DeviceEvent> events = new ArrayList<>();
280 synchronized (descsMap) {
281 Map<PortNumber, Port> ports = getPortMap(deviceId);
282
283 // Add new ports
284 Set<PortNumber> processed = new HashSet<>();
285 for (Timestamped<PortDescription> deltaDesc : deltaDescs) {
286 final PortNumber number = deltaDesc.value().portNumber();
287 final Port oldPort = ports.get(number);
288 final Port newPort;
289
290 final Timestamped<PortDescription> existingPortDesc = descs.getPortDesc(number);
291 if (existingPortDesc == null ||
292 deltaDesc == existingPortDesc ||
293 deltaDesc.isNewer(existingPortDesc)) {
294 // on new port or valid update
295 // update description
296 descs.putPortDesc(deltaDesc);
297 newPort = composePort(device, number, descsMap);
298 } else {
299 // outdated event, ignored.
300 continue;
301 }
302
303 events.add(oldPort == null ?
304 createPort(device, newPort, ports) :
305 updatePort(device, oldPort, newPort, ports));
306 processed.add(number);
307 }
308
309 events.addAll(pruneOldPorts(device, ports, processed));
310 }
311 return FluentIterable.from(events).filter(notNull()).toList();
312 }
313
314 // Creates a new port based on the port description adds it to the map and
315 // Returns corresponding event.
316 // Guarded by deviceDescs value (=locking Device)
317 private DeviceEvent createPort(Device device, Port newPort,
318 Map<PortNumber, Port> ports) {
319 ports.put(newPort.number(), newPort);
320 return new DeviceEvent(PORT_ADDED, device, newPort);
321 }
322
323 // Checks if the specified port requires update and if so, it replaces the
324 // existing entry in the map and returns corresponding event.
325 // Guarded by deviceDescs value (=locking Device)
326 private DeviceEvent updatePort(Device device, Port oldPort,
327 Port newPort,
328 Map<PortNumber, Port> ports) {
329 if (oldPort.isEnabled() != newPort.isEnabled() ||
330 !isAnnotationsEqual(oldPort.annotations(), newPort.annotations())) {
331
332 ports.put(oldPort.number(), newPort);
333 return new DeviceEvent(PORT_UPDATED, device, newPort);
334 }
335 return null;
336 }
337
338 // Prunes the specified list of ports based on which ports are in the
339 // processed list and returns list of corresponding events.
340 // Guarded by deviceDescs value (=locking Device)
341 private List<DeviceEvent> pruneOldPorts(Device device,
342 Map<PortNumber, Port> ports,
343 Set<PortNumber> processed) {
344 List<DeviceEvent> events = new ArrayList<>();
345 Iterator<PortNumber> iterator = ports.keySet().iterator();
346 while (iterator.hasNext()) {
347 PortNumber portNumber = iterator.next();
348 if (!processed.contains(portNumber)) {
349 events.add(new DeviceEvent(PORT_REMOVED, device,
350 ports.get(portNumber)));
351 iterator.remove();
352 }
353 }
354 return events;
355 }
356
357 // Gets the map of ports for the specified device; if one does not already
358 // exist, it creates and registers a new one.
359 private ConcurrentMap<PortNumber, Port> getPortMap(DeviceId deviceId) {
360 return createIfAbsentUnchecked(devicePorts, deviceId,
361 new InitConcurrentHashMap<PortNumber, Port>());
362 }
363
364 @Override
365 public synchronized DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
366 PortDescription portDescription) {
367 Timestamp newTimestamp = clockService.getTimestamp(deviceId);
368 final Timestamped<PortDescription> deltaDesc = new Timestamped<>(portDescription, newTimestamp);
369 DeviceEvent event = updatePortStatusInternal(providerId, deviceId, deltaDesc);
370 if (event != null) {
371 // FIXME: broadcast deltaDesc
372 log.debug("broadcast deltaDesc");
373 }
374 return event;
375 }
376
377 private DeviceEvent updatePortStatusInternal(ProviderId providerId, DeviceId deviceId,
378 Timestamped<PortDescription> deltaDesc) {
379
380 Device device = devices.get(deviceId);
381 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
382
383 ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
384 checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
385
386 DeviceDescriptions descs = descsMap.get(providerId);
387 // assuming all providers must to give DeviceDescription
388 checkArgument(descs != null,
389 "Device description for Device ID %s from Provider %s was not found",
390 deviceId, providerId);
391
392 synchronized (descsMap) {
393 ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
394 final PortNumber number = deltaDesc.value().portNumber();
395 final Port oldPort = ports.get(number);
396 final Port newPort;
397
398 final Timestamped<PortDescription> existingPortDesc = descs.getPortDesc(number);
399 if (existingPortDesc == null ||
400 deltaDesc == existingPortDesc ||
401 deltaDesc.isNewer(existingPortDesc)) {
402 // on new port or valid update
403 // update description
404 descs.putPortDesc(deltaDesc);
405 newPort = composePort(device, number, descsMap);
406 } else {
407 // outdated event, ignored.
408 return null;
409 }
410
411 if (oldPort == null) {
412 return createPort(device, newPort, ports);
413 } else {
414 return updatePort(device, oldPort, newPort, ports);
415 }
416 }
417 }
418
419 @Override
420 public List<Port> getPorts(DeviceId deviceId) {
421 Map<PortNumber, Port> ports = devicePorts.get(deviceId);
422 if (ports == null) {
423 return Collections.emptyList();
424 }
425 return ImmutableList.copyOf(ports.values());
426 }
427
428 @Override
429 public Port getPort(DeviceId deviceId, PortNumber portNumber) {
430 Map<PortNumber, Port> ports = devicePorts.get(deviceId);
431 return ports == null ? null : ports.get(portNumber);
432 }
433
434 @Override
435 public boolean isAvailable(DeviceId deviceId) {
436 return availableDevices.contains(deviceId);
437 }
438
439 @Override
440 public DeviceEvent removeDevice(DeviceId deviceId) {
441 synchronized (this) {
442 Device device = devices.remove(deviceId);
443 return device == null ? null :
444 new DeviceEvent(DEVICE_REMOVED, device, null);
445 }
446 }
447
448 private static boolean isAnnotationsEqual(Annotations lhs, Annotations rhs) {
449 if (lhs == rhs) {
450 return true;
451 }
452 if (lhs == null || rhs == null) {
453 return false;
454 }
455
456 if (!lhs.keys().equals(rhs.keys())) {
457 return false;
458 }
459
460 for (String key : lhs.keys()) {
461 if (!lhs.value(key).equals(rhs.value(key))) {
462 return false;
463 }
464 }
465 return true;
466 }
467
468 /**
469 * Returns a Device, merging description given from multiple Providers.
470 *
471 * @param deviceId device identifier
472 * @param providerDescs Collection of Descriptions from multiple providers
473 * @return Device instance
474 */
475 private Device composeDevice(DeviceId deviceId,
476 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
477
478 checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied");
479
480 ProviderId primary = pickPrimaryPID(providerDescs);
481
482 DeviceDescriptions desc = providerDescs.get(primary);
483
484 DeviceDescription base = desc.getDeviceDesc().value();
485 Type type = base.type();
486 String manufacturer = base.manufacturer();
487 String hwVersion = base.hwVersion();
488 String swVersion = base.swVersion();
489 String serialNumber = base.serialNumber();
490 DefaultAnnotations annotations = DefaultAnnotations.builder().build();
491 annotations = merge(annotations, base.annotations());
492
493 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
494 if (e.getKey().equals(primary)) {
495 continue;
496 }
497 // TODO: should keep track of Description timestamp
498 // and only merge conflicting keys when timestamp is newer
499 // Currently assuming there will never be a key conflict between
500 // providers
501
502 // annotation merging. not so efficient, should revisit later
503 annotations = merge(annotations, e.getValue().getDeviceDesc().value().annotations());
504 }
505
506 return new DefaultDevice(primary, deviceId , type, manufacturer,
507 hwVersion, swVersion, serialNumber, annotations);
508 }
509
510 /**
511 * Returns a Port, merging description given from multiple Providers.
512 *
513 * @param device device the port is on
514 * @param number port number
515 * @param providerDescs Collection of Descriptions from multiple providers
516 * @return Port instance
517 */
518 private Port composePort(Device device, PortNumber number,
519 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
520
521 ProviderId primary = pickPrimaryPID(providerDescs);
522 DeviceDescriptions primDescs = providerDescs.get(primary);
523 // if no primary, assume not enabled
524 // TODO: revisit this default port enabled/disabled behavior
525 boolean isEnabled = false;
526 DefaultAnnotations annotations = DefaultAnnotations.builder().build();
527
528 final Timestamped<PortDescription> portDesc = primDescs.getPortDesc(number);
529 if (portDesc != null) {
530 isEnabled = portDesc.value().isEnabled();
531 annotations = merge(annotations, portDesc.value().annotations());
532 }
533
534 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
535 if (e.getKey().equals(primary)) {
536 continue;
537 }
538 // TODO: should keep track of Description timestamp
539 // and only merge conflicting keys when timestamp is newer
540 // Currently assuming there will never be a key conflict between
541 // providers
542
543 // annotation merging. not so efficient, should revisit later
544 final Timestamped<PortDescription> otherPortDesc = e.getValue().getPortDesc(number);
545 if (otherPortDesc != null) {
546 annotations = merge(annotations, otherPortDesc.value().annotations());
547 }
548 }
549
550 return new DefaultPort(device, number, isEnabled, annotations);
551 }
552
553 /**
554 * @return primary ProviderID, or randomly chosen one if none exists
555 */
556 private ProviderId pickPrimaryPID(
557 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
558 ProviderId fallBackPrimary = null;
559 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
560 if (!e.getKey().isAncillary()) {
561 return e.getKey();
562 } else if (fallBackPrimary == null) {
563 // pick randomly as a fallback in case there is no primary
564 fallBackPrimary = e.getKey();
565 }
566 }
567 return fallBackPrimary;
568 }
569
570 private static final class InitConcurrentHashMap<K, V> implements
571 ConcurrentInitializer<ConcurrentMap<K, V>> {
572 @Override
573 public ConcurrentMap<K, V> get() throws ConcurrentException {
574 return new ConcurrentHashMap<>();
575 }
576 }
577
578 public static final class InitDeviceDescs
579 implements ConcurrentInitializer<DeviceDescriptions> {
580
581 private final Timestamped<DeviceDescription> deviceDesc;
582
583 public InitDeviceDescs(Timestamped<DeviceDescription> deviceDesc) {
584 this.deviceDesc = checkNotNull(deviceDesc);
585 }
586 @Override
587 public DeviceDescriptions get() throws ConcurrentException {
588 return new DeviceDescriptions(deviceDesc);
589 }
590 }
591
592
593 /**
594 * Collection of Description of a Device and it's Ports given from a Provider.
595 */
596 public static class DeviceDescriptions {
597
598 private final AtomicReference<Timestamped<DeviceDescription>> deviceDesc;
599 private final ConcurrentMap<PortNumber, Timestamped<PortDescription>> portDescs;
600
601 public DeviceDescriptions(Timestamped<DeviceDescription> desc) {
602 this.deviceDesc = new AtomicReference<>(checkNotNull(desc));
603 this.portDescs = new ConcurrentHashMap<>();
604 }
605
606 public Timestamped<DeviceDescription> getDeviceDesc() {
607 return deviceDesc.get();
608 }
609
610 public Timestamped<PortDescription> getPortDesc(PortNumber number) {
611 return portDescs.get(number);
612 }
613
614 /**
615 * Puts DeviceDescription, merging annotations as necessary.
616 *
617 * @param newDesc new DeviceDescription
618 * @return previous DeviceDescription
619 */
620 public synchronized Timestamped<DeviceDescription> putDeviceDesc(Timestamped<DeviceDescription> newDesc) {
621 Timestamped<DeviceDescription> oldOne = deviceDesc.get();
622 Timestamped<DeviceDescription> newOne = newDesc;
623 if (oldOne != null) {
624 SparseAnnotations merged = merge(oldOne.value().annotations(),
625 newDesc.value().annotations());
626 newOne = new Timestamped<DeviceDescription>(
627 new DefaultDeviceDescription(newDesc.value(), merged),
628 newDesc.timestamp());
629 }
630 return deviceDesc.getAndSet(newOne);
631 }
632
633 /**
634 * Puts PortDescription, merging annotations as necessary.
635 *
636 * @param newDesc new PortDescription
637 * @return previous PortDescription
638 */
639 public synchronized Timestamped<PortDescription> putPortDesc(Timestamped<PortDescription> newDesc) {
640 Timestamped<PortDescription> oldOne = portDescs.get(newDesc.value().portNumber());
641 Timestamped<PortDescription> newOne = newDesc;
642 if (oldOne != null) {
643 SparseAnnotations merged = merge(oldOne.value().annotations(),
644 newDesc.value().annotations());
645 newOne = new Timestamped<PortDescription>(
646 new DefaultPortDescription(newDesc.value(), merged),
647 newDesc.timestamp());
648 }
649 return portDescs.put(newOne.value().portNumber(), newOne);
650 }
651 }
652}