blob: 4a0d347d60c8f48c54e5c6b75cdfeeed322cad37 [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;
Yuta HIGUCHId76830b2014-09-30 19:08:40 -070043import java.util.concurrent.ConcurrentMap;
Madan Jampani61056bc2014-09-27 09:07:26 -070044
45import static com.google.common.base.Preconditions.checkArgument;
46import static org.onlab.onos.net.device.DeviceEvent.Type.*;
47import static org.slf4j.LoggerFactory.getLogger;
48
49/**
50 * Manages inventory of infrastructure devices using a protocol that takes into consideration
51 * the order in which device events occur.
52 */
53@Component(immediate = true)
54@Service
55public class OnosDistributedDeviceStore
Yuta HIGUCHI2e963892014-09-27 13:00:39 -070056 extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
Madan Jampani61056bc2014-09-27 09:07:26 -070057 implements DeviceStore {
58
59 private final Logger log = getLogger(getClass());
60
61 public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
62
Yuta HIGUCHId76830b2014-09-30 19:08:40 -070063 private ConcurrentMap<DeviceId, VersionedValue<Device>> devices;
64 private ConcurrentMap<DeviceId, Map<PortNumber, VersionedValue<Port>>> devicePorts;
Yuta HIGUCHI2e963892014-09-27 13:00:39 -070065
Madan Jampani61056bc2014-09-27 09:07:26 -070066 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
67 protected ClockService clockService;
68
Madan Jampani61056bc2014-09-27 09:07:26 -070069 @Activate
70 public void activate() {
Madan Jampani61056bc2014-09-27 09:07:26 -070071
72 devices = new ConcurrentHashMap<>();
73 devicePorts = new ConcurrentHashMap<>();
74
75 log.info("Started");
76 }
Yuta HIGUCHI2e963892014-09-27 13:00:39 -070077
Madan Jampani61056bc2014-09-27 09:07:26 -070078 @Deactivate
79 public void deactivate() {
80 log.info("Stopped");
81 }
82
83 @Override
84 public int getDeviceCount() {
85 return devices.size();
86 }
87
88 @Override
89 public Iterable<Device> getDevices() {
Madan Jampani61056bc2014-09-27 09:07:26 -070090 Builder<Device> builder = ImmutableSet.builder();
Madan Jampani4209def2014-09-29 13:54:57 -070091 synchronized (this) {
92 for (VersionedValue<Device> device : devices.values()) {
93 builder.add(device.entity());
94 }
95 return builder.build();
Madan Jampani61056bc2014-09-27 09:07:26 -070096 }
Madan Jampani61056bc2014-09-27 09:07:26 -070097 }
98
99 @Override
100 public Device getDevice(DeviceId deviceId) {
Madan Jampani4209def2014-09-29 13:54:57 -0700101 VersionedValue<Device> device = devices.get(deviceId);
102 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
103 return device.entity();
Madan Jampani61056bc2014-09-27 09:07:26 -0700104 }
105
106 @Override
107 public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
108 DeviceDescription deviceDescription) {
Madan Jampani4209def2014-09-29 13:54:57 -0700109 Timestamp newTimestamp = clockService.getTimestamp(deviceId);
Madan Jampani61056bc2014-09-27 09:07:26 -0700110 VersionedValue<Device> device = devices.get(deviceId);
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700111
Madan Jampani61056bc2014-09-27 09:07:26 -0700112 if (device == null) {
Madan Jampani4209def2014-09-29 13:54:57 -0700113 return createDevice(providerId, deviceId, deviceDescription, newTimestamp);
Madan Jampani61056bc2014-09-27 09:07:26 -0700114 }
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700115
Madan Jampani4209def2014-09-29 13:54:57 -0700116 checkState(newTimestamp.compareTo(device.timestamp()) > 0,
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700117 "Existing device has a timestamp in the future!");
Madan Jampani61056bc2014-09-27 09:07:26 -0700118
Madan Jampani4209def2014-09-29 13:54:57 -0700119 return updateDevice(providerId, device.entity(), deviceDescription, newTimestamp);
Madan Jampani61056bc2014-09-27 09:07:26 -0700120 }
121
122 // Creates the device and returns the appropriate event if necessary.
123 private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
124 DeviceDescription desc, Timestamp timestamp) {
Madan Jampani4209def2014-09-29 13:54:57 -0700125 Device device = new DefaultDevice(providerId, deviceId, desc.type(),
Madan Jampani61056bc2014-09-27 09:07:26 -0700126 desc.manufacturer(),
127 desc.hwVersion(), desc.swVersion(),
128 desc.serialNumber());
129
Madan Jampani4209def2014-09-29 13:54:57 -0700130 devices.put(deviceId, new VersionedValue<>(device, true, timestamp));
131 // TODO,FIXME: broadcast a message telling peers of a device event.
Madan Jampani61056bc2014-09-27 09:07:26 -0700132 return new DeviceEvent(DEVICE_ADDED, device, null);
133 }
134
135 // Updates the device and returns the appropriate event if necessary.
136 private DeviceEvent updateDevice(ProviderId providerId, Device device,
137 DeviceDescription desc, Timestamp timestamp) {
138 // We allow only certain attributes to trigger update
139 if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
140 !Objects.equals(device.swVersion(), desc.swVersion())) {
141
142 Device updated = new DefaultDevice(providerId, device.id(),
143 desc.type(),
144 desc.manufacturer(),
145 desc.hwVersion(),
146 desc.swVersion(),
147 desc.serialNumber());
148 devices.put(device.id(), new VersionedValue<Device>(updated, true, timestamp));
149 // FIXME: broadcast a message telling peers of a device event.
150 return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, updated, null);
151 }
152
153 // Otherwise merely attempt to change availability
Madan Jampani4209def2014-09-29 13:54:57 -0700154 Device updated = new DefaultDevice(providerId, device.id(),
Madan Jampani61056bc2014-09-27 09:07:26 -0700155 desc.type(),
156 desc.manufacturer(),
157 desc.hwVersion(),
158 desc.swVersion(),
159 desc.serialNumber());
160
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700161 VersionedValue<Device> oldDevice = devices.put(device.id(),
162 new VersionedValue<Device>(updated, true, timestamp));
Madan Jampani61056bc2014-09-27 09:07:26 -0700163 if (!oldDevice.isUp()) {
164 return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
165 } else {
166 return null;
167 }
168 }
169
170 @Override
171 public DeviceEvent markOffline(DeviceId deviceId) {
172 VersionedValue<Device> device = devices.get(deviceId);
173 boolean willRemove = device != null && device.isUp();
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700174 if (!willRemove) {
175 return null;
176 }
Madan Jampani61056bc2014-09-27 09:07:26 -0700177 Timestamp timestamp = clockService.getTimestamp(deviceId);
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700178 if (replaceIfLatest(device.entity(), false, timestamp)) {
Madan Jampani61056bc2014-09-27 09:07:26 -0700179 return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device.entity(), null);
180 }
181 return null;
182 }
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700183
Madan Jampani61056bc2014-09-27 09:07:26 -0700184 // Replace existing value if its timestamp is older.
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700185 private synchronized boolean replaceIfLatest(Device device, boolean isUp, Timestamp timestamp) {
Madan Jampani61056bc2014-09-27 09:07:26 -0700186 VersionedValue<Device> existingValue = devices.get(device.id());
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700187 if (timestamp.compareTo(existingValue.timestamp()) > 0) {
Madan Jampani61056bc2014-09-27 09:07:26 -0700188 devices.put(device.id(), new VersionedValue<Device>(device, isUp, timestamp));
189 return true;
190 }
191 return false;
192 }
193
194 @Override
195 public List<DeviceEvent> updatePorts(DeviceId deviceId,
196 List<PortDescription> portDescriptions) {
197 List<DeviceEvent> events = new ArrayList<>();
198 synchronized (this) {
199 VersionedValue<Device> device = devices.get(deviceId);
200 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
201 Map<PortNumber, VersionedValue<Port>> ports = getPortMap(deviceId);
Madan Jampani4209def2014-09-29 13:54:57 -0700202 Timestamp newTimestamp = clockService.getTimestamp(deviceId);
Madan Jampani61056bc2014-09-27 09:07:26 -0700203
204 // Add new ports
205 Set<PortNumber> processed = new HashSet<>();
206 for (PortDescription portDescription : portDescriptions) {
207 VersionedValue<Port> port = ports.get(portDescription.portNumber());
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700208 if (port == null) {
Madan Jampani4209def2014-09-29 13:54:57 -0700209 events.add(createPort(device, portDescription, ports, newTimestamp));
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700210 }
Madan Jampani4209def2014-09-29 13:54:57 -0700211 checkState(newTimestamp.compareTo(port.timestamp()) > 0,
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700212 "Existing port state has a timestamp in the future!");
Madan Jampani4209def2014-09-29 13:54:57 -0700213 events.add(updatePort(device.entity(), port.entity(), portDescription, ports, newTimestamp));
Madan Jampani61056bc2014-09-27 09:07:26 -0700214 processed.add(portDescription.portNumber());
215 }
216
217 updatePortMap(deviceId, ports);
218
219 events.addAll(pruneOldPorts(device.entity(), ports, processed));
220 }
221 return FluentIterable.from(events).filter(notNull()).toList();
222 }
223
224 // Creates a new port based on the port description adds it to the map and
225 // Returns corresponding event.
226 //@GuardedBy("this")
227 private DeviceEvent createPort(VersionedValue<Device> device, PortDescription portDescription,
228 Map<PortNumber, VersionedValue<Port>> ports, Timestamp timestamp) {
229 Port port = new DefaultPort(device.entity(), portDescription.portNumber(),
230 portDescription.isEnabled());
231 ports.put(port.number(), new VersionedValue<Port>(port, true, timestamp));
232 updatePortMap(device.entity().id(), ports);
233 return new DeviceEvent(PORT_ADDED, device.entity(), port);
234 }
235
236 // Checks if the specified port requires update and if so, it replaces the
237 // existing entry in the map and returns corresponding event.
238 //@GuardedBy("this")
Madan Jampani4209def2014-09-29 13:54:57 -0700239 private DeviceEvent updatePort(Device device, Port port,
Madan Jampani61056bc2014-09-27 09:07:26 -0700240 PortDescription portDescription,
241 Map<PortNumber, VersionedValue<Port>> ports,
242 Timestamp timestamp) {
Madan Jampani4209def2014-09-29 13:54:57 -0700243 if (port.isEnabled() != portDescription.isEnabled()) {
Madan Jampani61056bc2014-09-27 09:07:26 -0700244 VersionedValue<Port> updatedPort = new VersionedValue<Port>(
Madan Jampani4209def2014-09-29 13:54:57 -0700245 new DefaultPort(device, portDescription.portNumber(),
Madan Jampani61056bc2014-09-27 09:07:26 -0700246 portDescription.isEnabled()),
247 portDescription.isEnabled(),
248 timestamp);
Madan Jampani4209def2014-09-29 13:54:57 -0700249 ports.put(port.number(), updatedPort);
250 updatePortMap(device.id(), ports);
251 return new DeviceEvent(PORT_UPDATED, device, updatedPort.entity());
Madan Jampani61056bc2014-09-27 09:07:26 -0700252 }
253 return null;
254 }
255
256 // Prunes the specified list of ports based on which ports are in the
257 // processed list and returns list of corresponding events.
258 //@GuardedBy("this")
259 private List<DeviceEvent> pruneOldPorts(Device device,
260 Map<PortNumber, VersionedValue<Port>> ports,
261 Set<PortNumber> processed) {
262 List<DeviceEvent> events = new ArrayList<>();
263 Iterator<PortNumber> iterator = ports.keySet().iterator();
264 while (iterator.hasNext()) {
265 PortNumber portNumber = iterator.next();
266 if (!processed.contains(portNumber)) {
267 events.add(new DeviceEvent(PORT_REMOVED, device,
268 ports.get(portNumber).entity()));
269 iterator.remove();
270 }
271 }
272 if (!events.isEmpty()) {
273 updatePortMap(device.id(), ports);
274 }
275 return events;
276 }
277
278 // Gets the map of ports for the specified device; if one does not already
279 // exist, it creates and registers a new one.
280 // WARN: returned value is a copy, changes made to the Map
281 // needs to be written back using updatePortMap
282 //@GuardedBy("this")
283 private Map<PortNumber, VersionedValue<Port>> getPortMap(DeviceId deviceId) {
284 Map<PortNumber, VersionedValue<Port>> ports = devicePorts.get(deviceId);
285 if (ports == null) {
286 ports = new HashMap<>();
287 // this probably is waste of time in most cases.
288 updatePortMap(deviceId, ports);
289 }
290 return ports;
291 }
292
293 //@GuardedBy("this")
294 private void updatePortMap(DeviceId deviceId, Map<PortNumber, VersionedValue<Port>> ports) {
295 devicePorts.put(deviceId, ports);
296 }
297
298 @Override
299 public DeviceEvent updatePortStatus(DeviceId deviceId,
300 PortDescription portDescription) {
301 VersionedValue<Device> device = devices.get(deviceId);
302 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
303 Map<PortNumber, VersionedValue<Port>> ports = getPortMap(deviceId);
304 VersionedValue<Port> port = ports.get(portDescription.portNumber());
305 Timestamp timestamp = clockService.getTimestamp(deviceId);
Madan Jampani4209def2014-09-29 13:54:57 -0700306 return updatePort(device.entity(), port.entity(), portDescription, ports, timestamp);
Madan Jampani61056bc2014-09-27 09:07:26 -0700307 }
308
309 @Override
310 public List<Port> getPorts(DeviceId deviceId) {
311 Map<PortNumber, VersionedValue<Port>> versionedPorts = devicePorts.get(deviceId);
Yuta HIGUCHI2e963892014-09-27 13:00:39 -0700312 if (versionedPorts == null) {
313 return Collections.emptyList();
314 }
315 List<Port> ports = new ArrayList<>();
Madan Jampani61056bc2014-09-27 09:07:26 -0700316 for (VersionedValue<Port> port : versionedPorts.values()) {
317 ports.add(port.entity());
318 }
319 return ports;
320 }
321
322 @Override
323 public Port getPort(DeviceId deviceId, PortNumber portNumber) {
324 Map<PortNumber, VersionedValue<Port>> ports = devicePorts.get(deviceId);
325 return ports == null ? null : ports.get(portNumber).entity();
326 }
327
328 @Override
329 public boolean isAvailable(DeviceId deviceId) {
330 return devices.get(deviceId).isUp();
331 }
332
333 @Override
334 public DeviceEvent removeDevice(DeviceId deviceId) {
335 VersionedValue<Device> previousDevice = devices.remove(deviceId);
336 return previousDevice == null ? null :
337 new DeviceEvent(DEVICE_REMOVED, previousDevice.entity(), null);
338 }
339}