blob: bc0a055c661bbb48078b875f94952f14022bdb9b [file] [log] [blame]
tomea961ff2014-10-01 12:45:15 -07001package org.onlab.onos.store.trivial.impl;
tome5ec3fd2014-09-04 15:18:06 -07002
Yuta HIGUCHIf5479702014-09-25 00:09:24 -07003import com.google.common.collect.FluentIterable;
tom46a220d2014-09-05 08:25:56 -07004import com.google.common.collect.ImmutableList;
Yuta HIGUCHIf5479702014-09-25 00:09:24 -07005
Yuta HIGUCHI8e493792014-10-01 19:36:32 -07006import org.apache.commons.lang3.concurrent.ConcurrentException;
7import org.apache.commons.lang3.concurrent.ConcurrentInitializer;
tom41a2c5f2014-09-19 09:20:35 -07008import 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.Service;
tome5ec3fd2014-09-04 15:18:06 -070012import org.onlab.onos.net.DefaultDevice;
tom29df6f42014-09-05 08:14:14 -070013import org.onlab.onos.net.DefaultPort;
tome5ec3fd2014-09-04 15:18:06 -070014import org.onlab.onos.net.Device;
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070015import org.onlab.onos.net.Device.Type;
tome5ec3fd2014-09-04 15:18:06 -070016import org.onlab.onos.net.DeviceId;
tome5ec3fd2014-09-04 15:18:06 -070017import org.onlab.onos.net.Port;
18import org.onlab.onos.net.PortNumber;
19import org.onlab.onos.net.device.DeviceDescription;
20import org.onlab.onos.net.device.DeviceEvent;
tom41a2c5f2014-09-19 09:20:35 -070021import org.onlab.onos.net.device.DeviceStore;
tomf80c9722014-09-24 14:49:18 -070022import org.onlab.onos.net.device.DeviceStoreDelegate;
tome5ec3fd2014-09-04 15:18:06 -070023import org.onlab.onos.net.device.PortDescription;
24import org.onlab.onos.net.provider.ProviderId;
tomf80c9722014-09-24 14:49:18 -070025import org.onlab.onos.store.AbstractStore;
tom41a2c5f2014-09-19 09:20:35 -070026import org.slf4j.Logger;
tome5ec3fd2014-09-04 15:18:06 -070027
28import java.util.ArrayList;
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070029import java.util.Collection;
tome5ec3fd2014-09-04 15:18:06 -070030import java.util.Collections;
tome5ec3fd2014-09-04 15:18:06 -070031import java.util.HashSet;
tom29df6f42014-09-05 08:14:14 -070032import java.util.Iterator;
tome5ec3fd2014-09-04 15:18:06 -070033import java.util.List;
34import java.util.Map;
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070035import java.util.Map.Entry;
tome5ec3fd2014-09-04 15:18:06 -070036import java.util.Objects;
37import java.util.Set;
38import java.util.concurrent.ConcurrentHashMap;
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070039import java.util.concurrent.ConcurrentMap;
40import java.util.concurrent.atomic.AtomicReference;
tome5ec3fd2014-09-04 15:18:06 -070041
42import static com.google.common.base.Preconditions.checkArgument;
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070043import static com.google.common.base.Preconditions.checkNotNull;
Yuta HIGUCHIf5479702014-09-25 00:09:24 -070044import static com.google.common.base.Predicates.notNull;
tom29df6f42014-09-05 08:14:14 -070045import static org.onlab.onos.net.device.DeviceEvent.Type.*;
tom41a2c5f2014-09-19 09:20:35 -070046import static org.slf4j.LoggerFactory.getLogger;
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070047import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
tome5ec3fd2014-09-04 15:18:06 -070048
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070049// TODO: synchronization should be done in more fine-grained manner.
tome5ec3fd2014-09-04 15:18:06 -070050/**
tome4729872014-09-23 00:37:37 -070051 * Manages inventory of infrastructure devices using trivial in-memory
tomcbff9392014-09-10 00:45:23 -070052 * structures implementation.
tome5ec3fd2014-09-04 15:18:06 -070053 */
tom41a2c5f2014-09-19 09:20:35 -070054@Component(immediate = true)
55@Service
tomf80c9722014-09-24 14:49:18 -070056public class SimpleDeviceStore
57 extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
58 implements DeviceStore {
tom41a2c5f2014-09-19 09:20:35 -070059
60 private final Logger log = getLogger(getClass());
tome5ec3fd2014-09-04 15:18:06 -070061
tom29df6f42014-09-05 08:14:14 -070062 public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
63
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070064 // collection of Description given from various providers
65 private final ConcurrentMap<DeviceId,
66 ConcurrentMap<ProviderId, DeviceDescriptions>>
67 deviceDescs = new ConcurrentHashMap<>();
68
69 // cache of Device and Ports generated by compositing descriptions from providers
70 private final ConcurrentMap<DeviceId, Device> devices = new ConcurrentHashMap<>();
71 private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>> devicePorts = new ConcurrentHashMap<>();
72
73 // available(=UP) devices
tom249829a2014-09-04 15:28:04 -070074 private final Set<DeviceId> availableDevices = new HashSet<>();
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070075
tome5ec3fd2014-09-04 15:18:06 -070076
tom41a2c5f2014-09-19 09:20:35 -070077 @Activate
78 public void activate() {
79 log.info("Started");
80 }
81
82 @Deactivate
83 public void deactivate() {
84 log.info("Stopped");
85 }
tom5bcc9462014-09-19 10:11:31 -070086
tom41a2c5f2014-09-19 09:20:35 -070087 @Override
88 public int getDeviceCount() {
tomad2d2092014-09-06 23:24:20 -070089 return devices.size();
90 }
91
tom41a2c5f2014-09-19 09:20:35 -070092 @Override
93 public Iterable<Device> getDevices() {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -070094 return Collections.unmodifiableCollection(devices.values());
tome5ec3fd2014-09-04 15:18:06 -070095 }
96
tom41a2c5f2014-09-19 09:20:35 -070097 @Override
98 public Device getDevice(DeviceId deviceId) {
tome5ec3fd2014-09-04 15:18:06 -070099 return devices.get(deviceId);
100 }
101
tom41a2c5f2014-09-19 09:20:35 -0700102 @Override
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700103 public synchronized DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
tome5ec3fd2014-09-04 15:18:06 -0700104 DeviceDescription deviceDescription) {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700105 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs
106 = createIfAbsentUnchecked(deviceDescs, deviceId,
107 new InitConcurrentHashMap<ProviderId, DeviceDescriptions>());
108
109 Device oldDevice = devices.get(deviceId);
110
111 DeviceDescriptions descs
112 = createIfAbsentUnchecked(providerDescs, providerId,
113 new InitDeviceDescs(deviceDescription));
114
115 descs.putDeviceDesc(deviceDescription);
116
117 Device newDevice = composeDevice(deviceId, providerDescs);
118
119 if (oldDevice == null) {
120 // ADD
121 return createDevice(providerId, newDevice);
122 } else {
123 // UPDATE or ignore (no change or stale)
124 return updateDevice(providerId, oldDevice, newDevice);
tome5ec3fd2014-09-04 15:18:06 -0700125 }
tome5ec3fd2014-09-04 15:18:06 -0700126 }
127
128 // Creates the device and returns the appropriate event if necessary.
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700129 private DeviceEvent createDevice(ProviderId providerId, Device newDevice) {
130
131 // update composed device cache
tome5ec3fd2014-09-04 15:18:06 -0700132 synchronized (this) {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700133 devices.putIfAbsent(newDevice.id(), newDevice);
134 if (!providerId.isAncillary()) {
135 availableDevices.add(newDevice.id());
136 }
tome5ec3fd2014-09-04 15:18:06 -0700137 }
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700138
139 return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, newDevice, null);
tome5ec3fd2014-09-04 15:18:06 -0700140 }
141
142 // Updates the device and returns the appropriate event if necessary.
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700143 private DeviceEvent updateDevice(ProviderId providerId, Device oldDevice, Device newDevice) {
144
tome5ec3fd2014-09-04 15:18:06 -0700145 // We allow only certain attributes to trigger update
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700146 if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
147 !Objects.equals(oldDevice.swVersion(), newDevice.swVersion())) {
148
tome5ec3fd2014-09-04 15:18:06 -0700149 synchronized (this) {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700150 devices.replace(newDevice.id(), oldDevice, newDevice);
151 if (!providerId.isAncillary()) {
152 availableDevices.add(newDevice.id());
153 }
tome5ec3fd2014-09-04 15:18:06 -0700154 }
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700155 return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null);
tome5ec3fd2014-09-04 15:18:06 -0700156 }
157
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700158 // Otherwise merely attempt to change availability if primary provider
159 if (!providerId.isAncillary()) {
160 synchronized (this) {
161 boolean added = availableDevices.add(newDevice.id());
tom29df6f42014-09-05 08:14:14 -0700162 return !added ? null :
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700163 new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null);
164 }
tome5ec3fd2014-09-04 15:18:06 -0700165 }
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700166 return null;
tome5ec3fd2014-09-04 15:18:06 -0700167 }
168
tom41a2c5f2014-09-19 09:20:35 -0700169 @Override
170 public DeviceEvent markOffline(DeviceId deviceId) {
tome5ec3fd2014-09-04 15:18:06 -0700171 synchronized (this) {
172 Device device = devices.get(deviceId);
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700173 boolean removed = (device != null) && availableDevices.remove(deviceId);
tom29df6f42014-09-05 08:14:14 -0700174 return !removed ? null :
175 new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
tome5ec3fd2014-09-04 15:18:06 -0700176 }
177 }
178
tom41a2c5f2014-09-19 09:20:35 -0700179 @Override
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700180 public synchronized List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
tome5ec3fd2014-09-04 15:18:06 -0700181 List<PortDescription> portDescriptions) {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700182
183 // TODO: implement multi-provider
184 Device device = devices.get(deviceId);
185 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
186
187 ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
188 checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
189
190 DeviceDescriptions descs = descsMap.get(providerId);
191 checkArgument(descs != null,
192 "Device description for Device ID %s from Provider %s was not found",
193 deviceId, providerId);
194
195
tom29df6f42014-09-05 08:14:14 -0700196 List<DeviceEvent> events = new ArrayList<>();
197 synchronized (this) {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700198 ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
tom29df6f42014-09-05 08:14:14 -0700199
200 // Add new ports
201 Set<PortNumber> processed = new HashSet<>();
202 for (PortDescription portDescription : portDescriptions) {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700203 PortNumber number = portDescription.portNumber();
204 Port oldPort = ports.get(number);
205 // update description
206 descs.putPortDesc(number, portDescription);
207 Port newPort = composePort(device, number, descsMap);
208
209 events.add(oldPort == null ?
210 createPort(device, newPort, ports) :
211 updatePort(device, oldPort, newPort, ports));
tom29df6f42014-09-05 08:14:14 -0700212 processed.add(portDescription.portNumber());
213 }
214
215 events.addAll(pruneOldPorts(device, ports, processed));
216 }
Yuta HIGUCHIf5479702014-09-25 00:09:24 -0700217 return FluentIterable.from(events).filter(notNull()).toList();
tom29df6f42014-09-05 08:14:14 -0700218 }
219
220 // Creates a new port based on the port description adds it to the map and
221 // Returns corresponding event.
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700222 private DeviceEvent createPort(Device device, Port newPort,
223 ConcurrentMap<PortNumber, Port> ports) {
224 ports.put(newPort.number(), newPort);
225 return new DeviceEvent(PORT_ADDED, device, newPort);
tom29df6f42014-09-05 08:14:14 -0700226 }
227
228 // CHecks if the specified port requires update and if so, it replaces the
229 // existing entry in the map and returns corresponding event.
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700230 private DeviceEvent updatePort(Device device, Port oldPort,
231 Port newPort,
232 ConcurrentMap<PortNumber, Port> ports) {
233 if (oldPort.isEnabled() != newPort.isEnabled()) {
234 ports.put(oldPort.number(), newPort);
235 return new DeviceEvent(PORT_UPDATED, device, newPort);
tom29df6f42014-09-05 08:14:14 -0700236 }
237 return null;
238 }
239
240 // Prunes the specified list of ports based on which ports are in the
241 // processed list and returns list of corresponding events.
242 private List<DeviceEvent> pruneOldPorts(Device device,
243 Map<PortNumber, Port> ports,
244 Set<PortNumber> processed) {
245 List<DeviceEvent> events = new ArrayList<>();
246 Iterator<PortNumber> iterator = ports.keySet().iterator();
247 while (iterator.hasNext()) {
248 PortNumber portNumber = iterator.next();
tom24c55cd2014-09-06 10:47:25 -0700249 if (!processed.contains(portNumber)) {
tom29df6f42014-09-05 08:14:14 -0700250 events.add(new DeviceEvent(PORT_REMOVED, device,
251 ports.get(portNumber)));
252 iterator.remove();
253 }
254 }
255 return events;
256 }
257
258 // Gets the map of ports for the specified device; if one does not already
259 // exist, it creates and registers a new one.
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700260 private ConcurrentMap<PortNumber, Port> getPortMap(DeviceId deviceId) {
261 return createIfAbsentUnchecked(devicePorts, deviceId,
262 new InitConcurrentHashMap<PortNumber, Port>());
tome5ec3fd2014-09-04 15:18:06 -0700263 }
264
tom41a2c5f2014-09-19 09:20:35 -0700265 @Override
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700266 public synchronized DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
tome5ec3fd2014-09-04 15:18:06 -0700267 PortDescription portDescription) {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700268 Device device = devices.get(deviceId);
269 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
270
271 ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
272 checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
273
274 DeviceDescriptions descs = descsMap.get(providerId);
275 checkArgument(descs != null,
276 "Device description for Device ID %s from Provider %s was not found",
277 deviceId, providerId);
278
279 // TODO: implement multi-provider
tom46a220d2014-09-05 08:25:56 -0700280 synchronized (this) {
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700281 ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
282 final PortNumber number = portDescription.portNumber();
283 Port oldPort = ports.get(number);
284 // update description
285 descs.putPortDesc(number, portDescription);
286 Port newPort = composePort(device, number, descsMap);
287 if (oldPort == null) {
288 return createPort(device, newPort, ports);
289 } else {
290 return updatePort(device, oldPort, newPort, ports);
291 }
tom46a220d2014-09-05 08:25:56 -0700292 }
tome5ec3fd2014-09-04 15:18:06 -0700293 }
294
tom41a2c5f2014-09-19 09:20:35 -0700295 @Override
296 public List<Port> getPorts(DeviceId deviceId) {
tom46a220d2014-09-05 08:25:56 -0700297 Map<PortNumber, Port> ports = devicePorts.get(deviceId);
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700298 if (ports == null) {
299 return Collections.emptyList();
300 }
301 return ImmutableList.copyOf(ports.values());
tome5ec3fd2014-09-04 15:18:06 -0700302 }
303
tom41a2c5f2014-09-19 09:20:35 -0700304 @Override
305 public Port getPort(DeviceId deviceId, PortNumber portNumber) {
tom46a220d2014-09-05 08:25:56 -0700306 Map<PortNumber, Port> ports = devicePorts.get(deviceId);
307 return ports == null ? null : ports.get(portNumber);
tome5ec3fd2014-09-04 15:18:06 -0700308 }
309
tom41a2c5f2014-09-19 09:20:35 -0700310 @Override
311 public boolean isAvailable(DeviceId deviceId) {
tomff7eb7c2014-09-08 12:49:03 -0700312 return availableDevices.contains(deviceId);
313 }
314
tom41a2c5f2014-09-19 09:20:35 -0700315 @Override
tom41a2c5f2014-09-19 09:20:35 -0700316 public DeviceEvent removeDevice(DeviceId deviceId) {
tome5ec3fd2014-09-04 15:18:06 -0700317 synchronized (this) {
tome5ec3fd2014-09-04 15:18:06 -0700318 Device device = devices.remove(deviceId);
tom29df6f42014-09-05 08:14:14 -0700319 return device == null ? null :
320 new DeviceEvent(DEVICE_REMOVED, device, null);
tome5ec3fd2014-09-04 15:18:06 -0700321 }
322 }
Yuta HIGUCHI8e493792014-10-01 19:36:32 -0700323
324 /**
325 * Returns a Device, merging description given from multiple Providers.
326 *
327 * @param deviceId device identifier
328 * @param providerDescs Collection of Descriptions from multiple providers
329 * @return Device instance
330 */
331 private Device composeDevice(DeviceId deviceId,
332 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
333
334 checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied");
335
336 ProviderId primary = pickPrimaryPID(providerDescs);
337
338 DeviceDescriptions desc = providerDescs.get(primary);
339 Type type = desc.getDeviceDesc().type();
340 String manufacturer = desc.getDeviceDesc().manufacturer();
341 String hwVersion = desc.getDeviceDesc().hwVersion();
342 String swVersion = desc.getDeviceDesc().swVersion();
343 String serialNumber = desc.getDeviceDesc().serialNumber();
344
345 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
346 if (e.getKey().equals(primary)) {
347 continue;
348 }
349 // FIXME: implement attribute merging once we have K-V attributes
350 }
351
352 return new DefaultDevice(primary, deviceId , type, manufacturer, hwVersion, swVersion, serialNumber);
353 }
354
355 // probably want composePorts
356 private Port composePort(Device device, PortNumber number,
357 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
358
359 ProviderId primary = pickPrimaryPID(providerDescs);
360 DeviceDescriptions primDescs = providerDescs.get(primary);
361 final PortDescription portDesc = primDescs.getPortDesc(number);
362 boolean isEnabled;
363 if (portDesc != null) {
364 isEnabled = portDesc.isEnabled();
365 } else {
366 // if no primary, assume not enabled
367 // TODO: revisit this port enabled/disabled behavior
368 isEnabled = false;
369 }
370
371 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
372 if (e.getKey().equals(primary)) {
373 continue;
374 }
375 // FIXME: implement attribute merging once we have K-V attributes
376 }
377
378 return new DefaultPort(device, number, isEnabled);
379 }
380
381 /**
382 * @return primary ProviderID, or randomly chosen one if none exists
383 */
384 private ProviderId pickPrimaryPID(
385 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
386 ProviderId fallBackPrimary = null;
387 for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
388 if (!e.getKey().isAncillary()) {
389 return e.getKey();
390 } else if (fallBackPrimary == null) {
391 // pick randomly as a fallback in case there is no primary
392 fallBackPrimary = e.getKey();
393 }
394 }
395 return fallBackPrimary;
396 }
397
398 // TODO: can be made generic
399 private static final class InitConcurrentHashMap<K, V> implements
400 ConcurrentInitializer<ConcurrentMap<K, V>> {
401 @Override
402 public ConcurrentMap<K, V> get() throws ConcurrentException {
403 return new ConcurrentHashMap<>();
404 }
405 }
406
407 public static final class InitDeviceDescs
408 implements ConcurrentInitializer<DeviceDescriptions> {
409 private final DeviceDescription deviceDesc;
410 public InitDeviceDescs(DeviceDescription deviceDesc) {
411 this.deviceDesc = checkNotNull(deviceDesc);
412 }
413 @Override
414 public DeviceDescriptions get() throws ConcurrentException {
415 return new DeviceDescriptions(deviceDesc);
416 }
417 }
418
419
420 /**
421 * Collection of Description of a Device and it's Ports given from a Provider.
422 */
423 private static class DeviceDescriptions {
424 // private final DeviceId id;
425 // private final ProviderId pid;
426
427 private final AtomicReference<DeviceDescription> deviceDesc;
428 private final ConcurrentMap<PortNumber, PortDescription> portDescs;
429
430 public DeviceDescriptions(DeviceDescription desc) {
431 this.deviceDesc = new AtomicReference<>(desc);
432 this.portDescs = new ConcurrentHashMap<>();
433 }
434
435 public DeviceDescription getDeviceDesc() {
436 return deviceDesc.get();
437 }
438
439 public PortDescription getPortDesc(PortNumber number) {
440 return portDescs.get(number);
441 }
442
443 public Collection<PortDescription> getPortDescs() {
444 return Collections.unmodifiableCollection(portDescs.values());
445 }
446
447 public DeviceDescription putDeviceDesc(DeviceDescription newDesc) {
448 return deviceDesc.getAndSet(newDesc);
449 }
450
451 public PortDescription putPortDesc(PortNumber number, PortDescription newDesc) {
452 return portDescs.put(number, newDesc);
453 }
454 }
tome5ec3fd2014-09-04 15:18:06 -0700455}