blob: bd5f2fd901e2e6394eef8a80b1999eb7f018aa9d [file] [log] [blame]
Madan Jampani61056bc2014-09-27 09:07:26 -07001package org.onlab.onos.store.device.impl;
2
3import static com.google.common.base.Predicates.notNull;
Yuta HIGUCHI2e963892014-09-27 13:00:39 -07004import static com.google.common.base.Preconditions.checkState;
Madan Jampani61056bc2014-09-27 09:07:26 -07005
Madan Jampani61056bc2014-09-27 09:07:26 -07006import com.google.common.collect.FluentIterable;
7import com.google.common.collect.ImmutableSet;
8import com.google.common.collect.ImmutableSet.Builder;
9
10import org.apache.felix.scr.annotations.Activate;
11import org.apache.felix.scr.annotations.Component;
12import org.apache.felix.scr.annotations.Deactivate;
13import org.apache.felix.scr.annotations.Reference;
14import org.apache.felix.scr.annotations.ReferenceCardinality;
15import org.apache.felix.scr.annotations.Service;
16import org.onlab.onos.net.DefaultDevice;
17import org.onlab.onos.net.DefaultPort;
18import org.onlab.onos.net.Device;
19import org.onlab.onos.net.DeviceId;
20import org.onlab.onos.net.Port;
21import org.onlab.onos.net.PortNumber;
22import org.onlab.onos.net.device.DeviceDescription;
23import org.onlab.onos.net.device.DeviceEvent;
24import org.onlab.onos.net.device.DeviceStore;
25import org.onlab.onos.net.device.DeviceStoreDelegate;
26import org.onlab.onos.net.device.PortDescription;
27import org.onlab.onos.net.provider.ProviderId;
Yuta HIGUCHI2e963892014-09-27 13:00:39 -070028import org.onlab.onos.store.AbstractStore;
29import org.onlab.onos.store.ClockService;
Madan Jampani61056bc2014-09-27 09:07:26 -070030import org.onlab.onos.store.Timestamp;
Madan Jampani61056bc2014-09-27 09:07:26 -070031import org.slf4j.Logger;
32
33import java.util.ArrayList;
34import java.util.Collections;
35import java.util.HashMap;
36import java.util.HashSet;
37import java.util.Iterator;
38import java.util.List;
39import java.util.Map;
40import java.util.Objects;
41import java.util.Set;
42import java.util.concurrent.ConcurrentHashMap;
43
44import static com.google.common.base.Preconditions.checkArgument;
45import static org.onlab.onos.net.device.DeviceEvent.Type.*;
46import static org.slf4j.LoggerFactory.getLogger;
47
48/**
49 * Manages inventory of infrastructure devices using a protocol that takes into consideration
50 * the order in which device events occur.
51 */
52@Component(immediate = true)
53@Service
54public class OnosDistributedDeviceStore
Yuta HIGUCHI2e963892014-09-27 13:00:39 -070055 extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
Madan Jampani61056bc2014-09-27 09:07:26 -070056 implements DeviceStore {
57
58 private final Logger log = getLogger(getClass());
59
60 public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
61
62 private ConcurrentHashMap<DeviceId, VersionedValue<Device>> devices;
63 private ConcurrentHashMap<DeviceId, Map<PortNumber, VersionedValue<Port>>> devicePorts;
Yuta HIGUCHI2e963892014-09-27 13:00:39 -070064
Madan Jampani61056bc2014-09-27 09:07:26 -070065 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
66 protected ClockService clockService;
67
Madan Jampani61056bc2014-09-27 09:07:26 -070068 @Activate
69 public void activate() {
Madan Jampani61056bc2014-09-27 09:07:26 -070070
71 devices = new ConcurrentHashMap<>();
72 devicePorts = new ConcurrentHashMap<>();
73
74 log.info("Started");
75 }
Yuta HIGUCHI2e963892014-09-27 13:00:39 -070076
Madan Jampani61056bc2014-09-27 09:07:26 -070077 @Deactivate
78 public void deactivate() {
79 log.info("Stopped");
80 }
81
82 @Override
83 public int getDeviceCount() {
84 return devices.size();
85 }
86
87 @Override
88 public Iterable<Device> getDevices() {
Madan Jampani61056bc2014-09-27 09:07:26 -070089 Builder<Device> builder = ImmutableSet.builder();
Madan Jampani4209def2014-09-29 13:54:57 -070090 synchronized (this) {
91 for (VersionedValue<Device> device : devices.values()) {
92 builder.add(device.entity());
93 }
94 return builder.build();
Madan Jampani61056bc2014-09-27 09:07:26 -070095 }
Madan Jampani61056bc2014-09-27 09:07:26 -070096 }
97
98 @Override
99 public Device getDevice(DeviceId deviceId) {
Madan Jampani4209def2014-09-29 13:54:57 -0700100 VersionedValue<Device> device = devices.get(deviceId);
101 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
102 return device.entity();
Madan Jampani61056bc2014-09-27 09:07:26 -0700103 }
104
105 @Override
106 public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
107 DeviceDescription deviceDescription) {
Madan Jampani4209def2014-09-29 13:54:57 -0700108 Timestamp newTimestamp = clockService.getTimestamp(deviceId);
Madan Jampani61056bc2014-09-27 09:07:26 -0700109 VersionedValue<Device> device = devices.get(deviceId);
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700110
Madan Jampani61056bc2014-09-27 09:07:26 -0700111 if (device == null) {
Madan Jampani4209def2014-09-29 13:54:57 -0700112 return createDevice(providerId, deviceId, deviceDescription, newTimestamp);
Madan Jampani61056bc2014-09-27 09:07:26 -0700113 }
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700114
Madan Jampani4209def2014-09-29 13:54:57 -0700115 checkState(newTimestamp.compareTo(device.timestamp()) > 0,
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700116 "Existing device has a timestamp in the future!");
Madan Jampani61056bc2014-09-27 09:07:26 -0700117
Madan Jampani4209def2014-09-29 13:54:57 -0700118 return updateDevice(providerId, device.entity(), deviceDescription, newTimestamp);
Madan Jampani61056bc2014-09-27 09:07:26 -0700119 }
120
121 // Creates the device and returns the appropriate event if necessary.
122 private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
123 DeviceDescription desc, Timestamp timestamp) {
Madan Jampani4209def2014-09-29 13:54:57 -0700124 Device device = new DefaultDevice(providerId, deviceId, desc.type(),
Madan Jampani61056bc2014-09-27 09:07:26 -0700125 desc.manufacturer(),
126 desc.hwVersion(), desc.swVersion(),
127 desc.serialNumber());
128
Madan Jampani4209def2014-09-29 13:54:57 -0700129 devices.put(deviceId, new VersionedValue<>(device, true, timestamp));
130 // TODO,FIXME: broadcast a message telling peers of a device event.
Madan Jampani61056bc2014-09-27 09:07:26 -0700131 return new DeviceEvent(DEVICE_ADDED, device, null);
132 }
133
134 // Updates the device and returns the appropriate event if necessary.
135 private DeviceEvent updateDevice(ProviderId providerId, Device device,
136 DeviceDescription desc, Timestamp timestamp) {
137 // We allow only certain attributes to trigger update
138 if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
139 !Objects.equals(device.swVersion(), desc.swVersion())) {
140
141 Device updated = new DefaultDevice(providerId, device.id(),
142 desc.type(),
143 desc.manufacturer(),
144 desc.hwVersion(),
145 desc.swVersion(),
146 desc.serialNumber());
147 devices.put(device.id(), new VersionedValue<Device>(updated, true, timestamp));
148 // FIXME: broadcast a message telling peers of a device event.
149 return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, updated, null);
150 }
151
152 // Otherwise merely attempt to change availability
Madan Jampani4209def2014-09-29 13:54:57 -0700153 Device updated = new DefaultDevice(providerId, device.id(),
Madan Jampani61056bc2014-09-27 09:07:26 -0700154 desc.type(),
155 desc.manufacturer(),
156 desc.hwVersion(),
157 desc.swVersion(),
158 desc.serialNumber());
159
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700160 VersionedValue<Device> oldDevice = devices.put(device.id(),
161 new VersionedValue<Device>(updated, true, timestamp));
Madan Jampani61056bc2014-09-27 09:07:26 -0700162 if (!oldDevice.isUp()) {
163 return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
164 } else {
165 return null;
166 }
167 }
168
169 @Override
170 public DeviceEvent markOffline(DeviceId deviceId) {
171 VersionedValue<Device> device = devices.get(deviceId);
172 boolean willRemove = device != null && device.isUp();
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700173 if (!willRemove) {
174 return null;
175 }
Madan Jampani61056bc2014-09-27 09:07:26 -0700176 Timestamp timestamp = clockService.getTimestamp(deviceId);
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700177 if (replaceIfLatest(device.entity(), false, timestamp)) {
Madan Jampani61056bc2014-09-27 09:07:26 -0700178 return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device.entity(), null);
179 }
180 return null;
181 }
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700182
Madan Jampani61056bc2014-09-27 09:07:26 -0700183 // Replace existing value if its timestamp is older.
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700184 private synchronized boolean replaceIfLatest(Device device, boolean isUp, Timestamp timestamp) {
Madan Jampani61056bc2014-09-27 09:07:26 -0700185 VersionedValue<Device> existingValue = devices.get(device.id());
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700186 if (timestamp.compareTo(existingValue.timestamp()) > 0) {
Madan Jampani61056bc2014-09-27 09:07:26 -0700187 devices.put(device.id(), new VersionedValue<Device>(device, isUp, timestamp));
188 return true;
189 }
190 return false;
191 }
192
193 @Override
194 public List<DeviceEvent> updatePorts(DeviceId deviceId,
195 List<PortDescription> portDescriptions) {
196 List<DeviceEvent> events = new ArrayList<>();
197 synchronized (this) {
198 VersionedValue<Device> device = devices.get(deviceId);
199 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
200 Map<PortNumber, VersionedValue<Port>> ports = getPortMap(deviceId);
Madan Jampani4209def2014-09-29 13:54:57 -0700201 Timestamp newTimestamp = clockService.getTimestamp(deviceId);
Madan Jampani61056bc2014-09-27 09:07:26 -0700202
203 // Add new ports
204 Set<PortNumber> processed = new HashSet<>();
205 for (PortDescription portDescription : portDescriptions) {
206 VersionedValue<Port> port = ports.get(portDescription.portNumber());
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700207 if (port == null) {
Madan Jampani4209def2014-09-29 13:54:57 -0700208 events.add(createPort(device, portDescription, ports, newTimestamp));
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700209 }
Madan Jampani4209def2014-09-29 13:54:57 -0700210 checkState(newTimestamp.compareTo(port.timestamp()) > 0,
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700211 "Existing port state has a timestamp in the future!");
Madan Jampani4209def2014-09-29 13:54:57 -0700212 events.add(updatePort(device.entity(), port.entity(), portDescription, ports, newTimestamp));
Madan Jampani61056bc2014-09-27 09:07:26 -0700213 processed.add(portDescription.portNumber());
214 }
215
216 updatePortMap(deviceId, ports);
217
218 events.addAll(pruneOldPorts(device.entity(), ports, processed));
219 }
220 return FluentIterable.from(events).filter(notNull()).toList();
221 }
222
223 // Creates a new port based on the port description adds it to the map and
224 // Returns corresponding event.
225 //@GuardedBy("this")
226 private DeviceEvent createPort(VersionedValue<Device> device, PortDescription portDescription,
227 Map<PortNumber, VersionedValue<Port>> ports, Timestamp timestamp) {
228 Port port = new DefaultPort(device.entity(), portDescription.portNumber(),
229 portDescription.isEnabled());
230 ports.put(port.number(), new VersionedValue<Port>(port, true, timestamp));
231 updatePortMap(device.entity().id(), ports);
232 return new DeviceEvent(PORT_ADDED, device.entity(), port);
233 }
234
235 // Checks if the specified port requires update and if so, it replaces the
236 // existing entry in the map and returns corresponding event.
237 //@GuardedBy("this")
Madan Jampani4209def2014-09-29 13:54:57 -0700238 private DeviceEvent updatePort(Device device, Port port,
Madan Jampani61056bc2014-09-27 09:07:26 -0700239 PortDescription portDescription,
240 Map<PortNumber, VersionedValue<Port>> ports,
241 Timestamp timestamp) {
Madan Jampani4209def2014-09-29 13:54:57 -0700242 if (port.isEnabled() != portDescription.isEnabled()) {
Madan Jampani61056bc2014-09-27 09:07:26 -0700243 VersionedValue<Port> updatedPort = new VersionedValue<Port>(
Madan Jampani4209def2014-09-29 13:54:57 -0700244 new DefaultPort(device, portDescription.portNumber(),
Madan Jampani61056bc2014-09-27 09:07:26 -0700245 portDescription.isEnabled()),
246 portDescription.isEnabled(),
247 timestamp);
Madan Jampani4209def2014-09-29 13:54:57 -0700248 ports.put(port.number(), updatedPort);
249 updatePortMap(device.id(), ports);
250 return new DeviceEvent(PORT_UPDATED, device, updatedPort.entity());
Madan Jampani61056bc2014-09-27 09:07:26 -0700251 }
252 return null;
253 }
254
255 // Prunes the specified list of ports based on which ports are in the
256 // processed list and returns list of corresponding events.
257 //@GuardedBy("this")
258 private List<DeviceEvent> pruneOldPorts(Device device,
259 Map<PortNumber, VersionedValue<Port>> ports,
260 Set<PortNumber> processed) {
261 List<DeviceEvent> events = new ArrayList<>();
262 Iterator<PortNumber> iterator = ports.keySet().iterator();
263 while (iterator.hasNext()) {
264 PortNumber portNumber = iterator.next();
265 if (!processed.contains(portNumber)) {
266 events.add(new DeviceEvent(PORT_REMOVED, device,
267 ports.get(portNumber).entity()));
268 iterator.remove();
269 }
270 }
271 if (!events.isEmpty()) {
272 updatePortMap(device.id(), ports);
273 }
274 return events;
275 }
276
277 // Gets the map of ports for the specified device; if one does not already
278 // exist, it creates and registers a new one.
279 // WARN: returned value is a copy, changes made to the Map
280 // needs to be written back using updatePortMap
281 //@GuardedBy("this")
282 private Map<PortNumber, VersionedValue<Port>> getPortMap(DeviceId deviceId) {
283 Map<PortNumber, VersionedValue<Port>> ports = devicePorts.get(deviceId);
284 if (ports == null) {
285 ports = new HashMap<>();
286 // this probably is waste of time in most cases.
287 updatePortMap(deviceId, ports);
288 }
289 return ports;
290 }
291
292 //@GuardedBy("this")
293 private void updatePortMap(DeviceId deviceId, Map<PortNumber, VersionedValue<Port>> ports) {
294 devicePorts.put(deviceId, ports);
295 }
296
297 @Override
298 public DeviceEvent updatePortStatus(DeviceId deviceId,
299 PortDescription portDescription) {
300 VersionedValue<Device> device = devices.get(deviceId);
301 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
302 Map<PortNumber, VersionedValue<Port>> ports = getPortMap(deviceId);
303 VersionedValue<Port> port = ports.get(portDescription.portNumber());
304 Timestamp timestamp = clockService.getTimestamp(deviceId);
Madan Jampani4209def2014-09-29 13:54:57 -0700305 return updatePort(device.entity(), port.entity(), portDescription, ports, timestamp);
Madan Jampani61056bc2014-09-27 09:07:26 -0700306 }
307
308 @Override
309 public List<Port> getPorts(DeviceId deviceId) {
310 Map<PortNumber, VersionedValue<Port>> versionedPorts = devicePorts.get(deviceId);
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700311 if (versionedPorts == null) {
312 return Collections.emptyList();
313 }
314 List<Port> ports = new ArrayList<>();
Madan Jampani61056bc2014-09-27 09:07:26 -0700315 for (VersionedValue<Port> port : versionedPorts.values()) {
316 ports.add(port.entity());
317 }
318 return ports;
319 }
320
321 @Override
322 public Port getPort(DeviceId deviceId, PortNumber portNumber) {
323 Map<PortNumber, VersionedValue<Port>> ports = devicePorts.get(deviceId);
324 return ports == null ? null : ports.get(portNumber).entity();
325 }
326
327 @Override
328 public boolean isAvailable(DeviceId deviceId) {
329 return devices.get(deviceId).isUp();
330 }
331
332 @Override
333 public DeviceEvent removeDevice(DeviceId deviceId) {
334 VersionedValue<Device> previousDevice = devices.remove(deviceId);
335 return previousDevice == null ? null :
336 new DeviceEvent(DEVICE_REMOVED, previousDevice.entity(), null);
337 }
338}