blob: 0d79edf98e3f2b3dea1d113a122689eb051adb96 [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;
4
5import com.google.common.base.Preconditions;
6import 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;
28import org.onlab.onos.store.Timestamp;
29import org.onlab.onos.store.common.ClockService;
30import org.onlab.onos.store.impl.AbstractDistributedStore;
31import 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
55 extends AbstractDistributedStore<DeviceEvent, DeviceStoreDelegate>
56 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;
64
65 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
66 protected ClockService clockService;
67
68 @Override
69 @Activate
70 public void activate() {
71 super.activate();
72
73 devices = new ConcurrentHashMap<>();
74 devicePorts = new ConcurrentHashMap<>();
75
76 log.info("Started");
77 }
78
79 @Deactivate
80 public void deactivate() {
81 log.info("Stopped");
82 }
83
84 @Override
85 public int getDeviceCount() {
86 return devices.size();
87 }
88
89 @Override
90 public Iterable<Device> getDevices() {
91 // TODO builder v.s. copyOf. Guava semms to be using copyOf?
92 // FIXME: synchronize.
93 Builder<Device> builder = ImmutableSet.builder();
94 for (VersionedValue<? extends Device> device : devices.values()) {
95 builder.add(device.entity());
96 }
97 return builder.build();
98 }
99
100 @Override
101 public Device getDevice(DeviceId deviceId) {
102 return devices.get(deviceId).entity();
103 }
104
105 @Override
106 public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
107 DeviceDescription deviceDescription) {
108 Timestamp now = clockService.getTimestamp(deviceId);
109 VersionedValue<Device> device = devices.get(deviceId);
110
111 if (device == null) {
112 return createDevice(providerId, deviceId, deviceDescription, now);
113 }
114
115 Preconditions.checkState(now.compareTo(device.timestamp()) > 0, "Existing device has a timestamp in the future!");
116
117 return updateDevice(providerId, device.entity(), deviceDescription, now);
118 }
119
120 // Creates the device and returns the appropriate event if necessary.
121 private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
122 DeviceDescription desc, Timestamp timestamp) {
123 DefaultDevice device = new DefaultDevice(providerId, deviceId, desc.type(),
124 desc.manufacturer(),
125 desc.hwVersion(), desc.swVersion(),
126 desc.serialNumber());
127
128 devices.put(deviceId, new VersionedValue<Device>(device, true, timestamp));
129 // FIXME: broadcast a message telling peers of a device event.
130 return new DeviceEvent(DEVICE_ADDED, device, null);
131 }
132
133 // Updates the device and returns the appropriate event if necessary.
134 private DeviceEvent updateDevice(ProviderId providerId, Device device,
135 DeviceDescription desc, Timestamp timestamp) {
136 // We allow only certain attributes to trigger update
137 if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
138 !Objects.equals(device.swVersion(), desc.swVersion())) {
139
140 Device updated = new DefaultDevice(providerId, device.id(),
141 desc.type(),
142 desc.manufacturer(),
143 desc.hwVersion(),
144 desc.swVersion(),
145 desc.serialNumber());
146 devices.put(device.id(), new VersionedValue<Device>(updated, true, timestamp));
147 // FIXME: broadcast a message telling peers of a device event.
148 return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, updated, null);
149 }
150
151 // Otherwise merely attempt to change availability
152 DefaultDevice updated = new DefaultDevice(providerId, device.id(),
153 desc.type(),
154 desc.manufacturer(),
155 desc.hwVersion(),
156 desc.swVersion(),
157 desc.serialNumber());
158
159 VersionedValue<Device> oldDevice = devices.put(device.id(), new VersionedValue<Device>(updated, true, timestamp));
160 if (!oldDevice.isUp()) {
161 return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
162 } else {
163 return null;
164 }
165 }
166
167 @Override
168 public DeviceEvent markOffline(DeviceId deviceId) {
169 VersionedValue<Device> device = devices.get(deviceId);
170 boolean willRemove = device != null && device.isUp();
171 if (!willRemove) return null;
172 Timestamp timestamp = clockService.getTimestamp(deviceId);
173 if (replaceIfLatest(device.entity(), false, timestamp))
174 {
175 return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device.entity(), null);
176 }
177 return null;
178 }
179
180 // Replace existing value if its timestamp is older.
181 private synchronized boolean replaceIfLatest(Device device, boolean isUp, Timestamp timestamp)
182 {
183 VersionedValue<Device> existingValue = devices.get(device.id());
184 if (timestamp.compareTo(existingValue.timestamp()) > 0)
185 {
186 devices.put(device.id(), new VersionedValue<Device>(device, isUp, timestamp));
187 return true;
188 }
189 return false;
190 }
191
192 @Override
193 public List<DeviceEvent> updatePorts(DeviceId deviceId,
194 List<PortDescription> portDescriptions) {
195 List<DeviceEvent> events = new ArrayList<>();
196 synchronized (this) {
197 VersionedValue<Device> device = devices.get(deviceId);
198 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
199 Map<PortNumber, VersionedValue<Port>> ports = getPortMap(deviceId);
200 Timestamp timestamp = clockService.getTimestamp(deviceId);
201
202 // Add new ports
203 Set<PortNumber> processed = new HashSet<>();
204 for (PortDescription portDescription : portDescriptions) {
205 VersionedValue<Port> port = ports.get(portDescription.portNumber());
206 if (port == null) events.add(createPort(device, portDescription, ports, timestamp));
207 Preconditions.checkState(timestamp.compareTo(port.timestamp()) > 0, "Existing port state has a timestamp in the future!");
208 events.add(updatePort(device, port, portDescription, ports, timestamp));
209 processed.add(portDescription.portNumber());
210 }
211
212 updatePortMap(deviceId, ports);
213
214 events.addAll(pruneOldPorts(device.entity(), ports, processed));
215 }
216 return FluentIterable.from(events).filter(notNull()).toList();
217 }
218
219 // Creates a new port based on the port description adds it to the map and
220 // Returns corresponding event.
221 //@GuardedBy("this")
222 private DeviceEvent createPort(VersionedValue<Device> device, PortDescription portDescription,
223 Map<PortNumber, VersionedValue<Port>> ports, Timestamp timestamp) {
224 Port port = new DefaultPort(device.entity(), portDescription.portNumber(),
225 portDescription.isEnabled());
226 ports.put(port.number(), new VersionedValue<Port>(port, true, timestamp));
227 updatePortMap(device.entity().id(), ports);
228 return new DeviceEvent(PORT_ADDED, device.entity(), port);
229 }
230
231 // Checks if the specified port requires update and if so, it replaces the
232 // existing entry in the map and returns corresponding event.
233 //@GuardedBy("this")
234 private DeviceEvent updatePort(VersionedValue<Device> device, VersionedValue<Port> port,
235 PortDescription portDescription,
236 Map<PortNumber, VersionedValue<Port>> ports,
237 Timestamp timestamp) {
238 if (port.entity().isEnabled() != portDescription.isEnabled()) {
239 VersionedValue<Port> updatedPort = new VersionedValue<Port>(
240 new DefaultPort(device.entity(), portDescription.portNumber(),
241 portDescription.isEnabled()),
242 portDescription.isEnabled(),
243 timestamp);
244 ports.put(port.entity().number(), updatedPort);
245 updatePortMap(device.entity().id(), ports);
246 return new DeviceEvent(PORT_UPDATED, device.entity(), updatedPort.entity());
247 }
248 return null;
249 }
250
251 // Prunes the specified list of ports based on which ports are in the
252 // processed list and returns list of corresponding events.
253 //@GuardedBy("this")
254 private List<DeviceEvent> pruneOldPorts(Device device,
255 Map<PortNumber, VersionedValue<Port>> ports,
256 Set<PortNumber> processed) {
257 List<DeviceEvent> events = new ArrayList<>();
258 Iterator<PortNumber> iterator = ports.keySet().iterator();
259 while (iterator.hasNext()) {
260 PortNumber portNumber = iterator.next();
261 if (!processed.contains(portNumber)) {
262 events.add(new DeviceEvent(PORT_REMOVED, device,
263 ports.get(portNumber).entity()));
264 iterator.remove();
265 }
266 }
267 if (!events.isEmpty()) {
268 updatePortMap(device.id(), ports);
269 }
270 return events;
271 }
272
273 // Gets the map of ports for the specified device; if one does not already
274 // exist, it creates and registers a new one.
275 // WARN: returned value is a copy, changes made to the Map
276 // needs to be written back using updatePortMap
277 //@GuardedBy("this")
278 private Map<PortNumber, VersionedValue<Port>> getPortMap(DeviceId deviceId) {
279 Map<PortNumber, VersionedValue<Port>> ports = devicePorts.get(deviceId);
280 if (ports == null) {
281 ports = new HashMap<>();
282 // this probably is waste of time in most cases.
283 updatePortMap(deviceId, ports);
284 }
285 return ports;
286 }
287
288 //@GuardedBy("this")
289 private void updatePortMap(DeviceId deviceId, Map<PortNumber, VersionedValue<Port>> ports) {
290 devicePorts.put(deviceId, ports);
291 }
292
293 @Override
294 public DeviceEvent updatePortStatus(DeviceId deviceId,
295 PortDescription portDescription) {
296 VersionedValue<Device> device = devices.get(deviceId);
297 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
298 Map<PortNumber, VersionedValue<Port>> ports = getPortMap(deviceId);
299 VersionedValue<Port> port = ports.get(portDescription.portNumber());
300 Timestamp timestamp = clockService.getTimestamp(deviceId);
301 return updatePort(device, port, portDescription, ports, timestamp);
302 }
303
304 @Override
305 public List<Port> getPorts(DeviceId deviceId) {
306 Map<PortNumber, VersionedValue<Port>> versionedPorts = devicePorts.get(deviceId);
307 if (versionedPorts == null) return Collections.emptyList();
308 List<Port> ports = new ArrayList<Port>();
309 for (VersionedValue<Port> port : versionedPorts.values()) {
310 ports.add(port.entity());
311 }
312 return ports;
313 }
314
315 @Override
316 public Port getPort(DeviceId deviceId, PortNumber portNumber) {
317 Map<PortNumber, VersionedValue<Port>> ports = devicePorts.get(deviceId);
318 return ports == null ? null : ports.get(portNumber).entity();
319 }
320
321 @Override
322 public boolean isAvailable(DeviceId deviceId) {
323 return devices.get(deviceId).isUp();
324 }
325
326 @Override
327 public DeviceEvent removeDevice(DeviceId deviceId) {
328 VersionedValue<Device> previousDevice = devices.remove(deviceId);
329 return previousDevice == null ? null :
330 new DeviceEvent(DEVICE_REMOVED, previousDevice.entity(), null);
331 }
332}