blob: 996dfa73b731106d978cafe4a61d4972938419fe [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;
Yuta HIGUCHI39ede6a2014-10-03 15:23:33 -070014import org.onlab.onos.net.AnnotationsUtil;
Yuta HIGUCHI67a527f2014-10-02 22:23:54 -070015import 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()) ||
Yuta HIGUCHI39ede6a2014-10-03 15:23:33 -0700199 !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations())) {
Yuta HIGUCHI67a527f2014-10-02 22:23:54 -0700200
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() ||
Yuta HIGUCHI39ede6a2014-10-03 15:23:33 -0700330 !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
Yuta HIGUCHI67a527f2014-10-02 22:23:54 -0700331
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) {
Yuta HIGUCHI39ede6a2014-10-03 15:23:33 -0700441 Device device = devices.remove(deviceId);
442 // FIXME: should we be removing deviceDescs also?
443 return device == null ? null :
444 new DeviceEvent(DEVICE_REMOVED, device, null);
Yuta HIGUCHI67a527f2014-10-02 22:23:54 -0700445 }
446
447 /**
448 * Returns a Device, merging description given from multiple Providers.
449 *
450 * @param deviceId device identifier
451 * @param providerDescs Collection of Descriptions from multiple providers
452 * @return Device instance
453 */
454 private Device composeDevice(DeviceId deviceId,
455 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
456
457 checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied");
458
459 ProviderId primary = pickPrimaryPID(providerDescs);
460
461 DeviceDescriptions desc = providerDescs.get(primary);
462
463 DeviceDescription base = desc.getDeviceDesc().value();
464 Type type = base.type();
465 String manufacturer = base.manufacturer();
466 String hwVersion = base.hwVersion();
467 String swVersion = base.swVersion();
468 String serialNumber = base.serialNumber();
469 DefaultAnnotations annotations = DefaultAnnotations.builder().build();
470 annotations = merge(annotations, base.annotations());
471
472 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
473 if (e.getKey().equals(primary)) {
474 continue;
475 }
476 // TODO: should keep track of Description timestamp
477 // and only merge conflicting keys when timestamp is newer
478 // Currently assuming there will never be a key conflict between
479 // providers
480
481 // annotation merging. not so efficient, should revisit later
482 annotations = merge(annotations, e.getValue().getDeviceDesc().value().annotations());
483 }
484
485 return new DefaultDevice(primary, deviceId , type, manufacturer,
486 hwVersion, swVersion, serialNumber, annotations);
487 }
488
489 /**
490 * Returns a Port, merging description given from multiple Providers.
491 *
492 * @param device device the port is on
493 * @param number port number
494 * @param providerDescs Collection of Descriptions from multiple providers
495 * @return Port instance
496 */
497 private Port composePort(Device device, PortNumber number,
498 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
499
500 ProviderId primary = pickPrimaryPID(providerDescs);
501 DeviceDescriptions primDescs = providerDescs.get(primary);
502 // if no primary, assume not enabled
503 // TODO: revisit this default port enabled/disabled behavior
504 boolean isEnabled = false;
505 DefaultAnnotations annotations = DefaultAnnotations.builder().build();
506
507 final Timestamped<PortDescription> portDesc = primDescs.getPortDesc(number);
508 if (portDesc != null) {
509 isEnabled = portDesc.value().isEnabled();
510 annotations = merge(annotations, portDesc.value().annotations());
511 }
512
513 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
514 if (e.getKey().equals(primary)) {
515 continue;
516 }
517 // TODO: should keep track of Description timestamp
518 // and only merge conflicting keys when timestamp is newer
519 // Currently assuming there will never be a key conflict between
520 // providers
521
522 // annotation merging. not so efficient, should revisit later
523 final Timestamped<PortDescription> otherPortDesc = e.getValue().getPortDesc(number);
524 if (otherPortDesc != null) {
525 annotations = merge(annotations, otherPortDesc.value().annotations());
526 }
527 }
528
529 return new DefaultPort(device, number, isEnabled, annotations);
530 }
531
532 /**
533 * @return primary ProviderID, or randomly chosen one if none exists
534 */
535 private ProviderId pickPrimaryPID(
536 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
537 ProviderId fallBackPrimary = null;
538 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
539 if (!e.getKey().isAncillary()) {
540 return e.getKey();
541 } else if (fallBackPrimary == null) {
542 // pick randomly as a fallback in case there is no primary
543 fallBackPrimary = e.getKey();
544 }
545 }
546 return fallBackPrimary;
547 }
548
549 private static final class InitConcurrentHashMap<K, V> implements
550 ConcurrentInitializer<ConcurrentMap<K, V>> {
551 @Override
552 public ConcurrentMap<K, V> get() throws ConcurrentException {
553 return new ConcurrentHashMap<>();
554 }
555 }
556
557 public static final class InitDeviceDescs
558 implements ConcurrentInitializer<DeviceDescriptions> {
559
560 private final Timestamped<DeviceDescription> deviceDesc;
561
562 public InitDeviceDescs(Timestamped<DeviceDescription> deviceDesc) {
563 this.deviceDesc = checkNotNull(deviceDesc);
564 }
565 @Override
566 public DeviceDescriptions get() throws ConcurrentException {
567 return new DeviceDescriptions(deviceDesc);
568 }
569 }
570
571
572 /**
573 * Collection of Description of a Device and it's Ports given from a Provider.
574 */
575 public static class DeviceDescriptions {
576
577 private final AtomicReference<Timestamped<DeviceDescription>> deviceDesc;
578 private final ConcurrentMap<PortNumber, Timestamped<PortDescription>> portDescs;
579
580 public DeviceDescriptions(Timestamped<DeviceDescription> desc) {
581 this.deviceDesc = new AtomicReference<>(checkNotNull(desc));
582 this.portDescs = new ConcurrentHashMap<>();
583 }
584
585 public Timestamped<DeviceDescription> getDeviceDesc() {
586 return deviceDesc.get();
587 }
588
589 public Timestamped<PortDescription> getPortDesc(PortNumber number) {
590 return portDescs.get(number);
591 }
592
593 /**
594 * Puts DeviceDescription, merging annotations as necessary.
595 *
596 * @param newDesc new DeviceDescription
597 * @return previous DeviceDescription
598 */
599 public synchronized Timestamped<DeviceDescription> putDeviceDesc(Timestamped<DeviceDescription> newDesc) {
600 Timestamped<DeviceDescription> oldOne = deviceDesc.get();
601 Timestamped<DeviceDescription> newOne = newDesc;
602 if (oldOne != null) {
603 SparseAnnotations merged = merge(oldOne.value().annotations(),
604 newDesc.value().annotations());
605 newOne = new Timestamped<DeviceDescription>(
606 new DefaultDeviceDescription(newDesc.value(), merged),
607 newDesc.timestamp());
608 }
609 return deviceDesc.getAndSet(newOne);
610 }
611
612 /**
613 * Puts PortDescription, merging annotations as necessary.
614 *
615 * @param newDesc new PortDescription
616 * @return previous PortDescription
617 */
618 public synchronized Timestamped<PortDescription> putPortDesc(Timestamped<PortDescription> newDesc) {
619 Timestamped<PortDescription> oldOne = portDescs.get(newDesc.value().portNumber());
620 Timestamped<PortDescription> newOne = newDesc;
621 if (oldOne != null) {
622 SparseAnnotations merged = merge(oldOne.value().annotations(),
623 newDesc.value().annotations());
624 newOne = new Timestamped<PortDescription>(
625 new DefaultPortDescription(newDesc.value(), merged),
626 newDesc.timestamp());
627 }
628 return portDescs.put(newOne.value().portNumber(), newOne);
629 }
630 }
631}