blob: 8909591f6ba3c24fd6dc867fb3e1eacf297920c3 [file] [log] [blame]
Andrea Campanella241896c2017-05-10 13:11:04 -07001/*
2 * Copyright 2017-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.provider.general.device.impl;
18
19import com.google.common.annotations.Beta;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.onlab.packet.ChassisId;
26import org.onlab.util.ItemNotFoundException;
27import org.onosproject.core.CoreService;
28import org.onosproject.net.AnnotationKeys;
29import org.onosproject.net.DefaultAnnotations;
30import org.onosproject.net.Device;
31import org.onosproject.net.DeviceId;
32import org.onosproject.net.MastershipRole;
33import org.onosproject.net.PortNumber;
34import org.onosproject.net.SparseAnnotations;
35import org.onosproject.net.behaviour.PortAdmin;
36import org.onosproject.net.config.ConfigFactory;
37import org.onosproject.net.config.NetworkConfigEvent;
38import org.onosproject.net.config.NetworkConfigListener;
39import org.onosproject.net.config.NetworkConfigRegistry;
40import org.onosproject.net.config.basics.BasicDeviceConfig;
41import org.onosproject.net.config.basics.SubjectFactories;
42import org.onosproject.net.device.DefaultDeviceDescription;
43import org.onosproject.net.device.DeviceDescription;
44import org.onosproject.net.device.DeviceDescriptionDiscovery;
45import org.onosproject.net.device.DeviceEvent;
46import org.onosproject.net.device.DeviceHandshaker;
47import org.onosproject.net.device.DeviceListener;
48import org.onosproject.net.device.DeviceProvider;
49import org.onosproject.net.device.DeviceProviderRegistry;
50import org.onosproject.net.device.DeviceProviderService;
51import org.onosproject.net.device.DeviceService;
52import org.onosproject.net.device.PortDescription;
53import org.onosproject.net.device.PortStatistics;
54import org.onosproject.net.device.PortStatisticsDiscovery;
55import org.onosproject.net.driver.Behaviour;
56import org.onosproject.net.driver.DefaultDriverData;
57import org.onosproject.net.driver.DefaultDriverHandler;
58import org.onosproject.net.driver.Driver;
59import org.onosproject.net.driver.DriverData;
60import org.onosproject.net.driver.DriverService;
61import org.onosproject.net.key.DeviceKeyAdminService;
62import org.onosproject.net.provider.AbstractProvider;
63import org.onosproject.net.provider.ProviderId;
64import org.onosproject.provider.general.device.api.GeneralProviderDeviceConfig;
65import org.slf4j.Logger;
66
67import java.util.ArrayList;
68import java.util.Collection;
69import java.util.List;
70import java.util.concurrent.CompletableFuture;
71import java.util.concurrent.ExecutionException;
72import java.util.concurrent.ScheduledExecutorService;
73import java.util.concurrent.TimeUnit;
74import java.util.concurrent.TimeoutException;
75
76import static java.util.concurrent.Executors.newScheduledThreadPool;
77import static org.onlab.util.Tools.groupedThreads;
78import static org.onosproject.net.device.DeviceEvent.Type;
79import static org.slf4j.LoggerFactory.getLogger;
80
81/**
82 * Provider which uses drivers to detect device and do initial handshake
83 * and channel establishment with devices. Any other provider specific operation
84 * is also delegated to the DeviceHandshaker driver.
85 */
86@Beta
87@Component(immediate = true)
88public class GeneralDeviceProvider extends AbstractProvider
89 implements DeviceProvider {
90 private final Logger log = getLogger(getClass());
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected DeviceProviderRegistry providerRegistry;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected NetworkConfigRegistry cfgService;
97
98 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99 protected CoreService coreService;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102 protected DeviceService deviceService;
103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected DriverService driverService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected DeviceKeyAdminService deviceKeyAdminService;
109
110 protected static final String APP_NAME = "org.onosproject.generaldeviceprovider";
111 protected static final String URI_SCHEME = "device";
112 protected static final String CFG_SCHEME = "generalprovider";
113 private static final String DEVICE_PROVIDER_PACKAGE = "org.onosproject.general.provider.device";
114 private static final int CORE_POOL_SIZE = 10;
115 private static final String UNKNOWN = "unknown";
116 private static final int PORT_STATS_PERIOD_SECONDS = 10;
117
118
119 protected ScheduledExecutorService connectionExecutor
120 = newScheduledThreadPool(CORE_POOL_SIZE,
121 groupedThreads("onos/generaldeviceprovider-device",
122 "connection-executor-%d", log));
123 protected ScheduledExecutorService portStatsExecutor
124 = newScheduledThreadPool(CORE_POOL_SIZE,
125 groupedThreads("onos/generaldeviceprovider-port-stats",
126 "port-stats-executor-%d", log));
127
128 protected DeviceProviderService providerService;
129 private InternalDeviceListener deviceListener = new InternalDeviceListener();
130
131 protected final ConfigFactory factory =
132 new ConfigFactory<DeviceId, GeneralProviderDeviceConfig>(
133 SubjectFactories.DEVICE_SUBJECT_FACTORY,
134 GeneralProviderDeviceConfig.class, CFG_SCHEME) {
135 @Override
136 public GeneralProviderDeviceConfig createConfig() {
137 return new GeneralProviderDeviceConfig();
138 }
139 };
140
141 protected final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
142
143
144 @Activate
145 public void activate() {
146 providerService = providerRegistry.register(this);
147 coreService.registerApplication(APP_NAME);
148 cfgService.registerConfigFactory(factory);
149 cfgService.addListener(cfgListener);
150 deviceService.addListener(deviceListener);
151 //This will fail if ONOS has CFG and drivers which depend on this provider
152 // are activated, failing due to not finding the driver.
153 cfgService.getSubjects(DeviceId.class, GeneralProviderDeviceConfig.class)
154 .forEach(did -> connectionExecutor.execute(() -> connectDevice(did)));
155 log.info("Started");
156 }
157
158
159 @Deactivate
160 public void deactivate() {
161 portStatsExecutor.shutdown();
162 cfgService.removeListener(cfgListener);
163 //Not Removing the device so they can still be used from other driver providers
164 //cfgService.getSubjects(DeviceId.class, GeneralProviderDeviceConfig.class)
165 // .forEach(did -> connectionExecutor.execute(() -> disconnectDevice(did)));
166 connectionExecutor.shutdown();
167 deviceService.removeListener(deviceListener);
168 providerRegistry.unregister(this);
169 providerService = null;
170 cfgService.unregisterConfigFactory(factory);
171 log.info("Stopped");
172 }
173
174 public GeneralDeviceProvider() {
175 super(new ProviderId(URI_SCHEME, DEVICE_PROVIDER_PACKAGE));
176 }
177
178
179 @Override
180 public void triggerProbe(DeviceId deviceId) {
181 //TODO Really don't see the point of this in non OF Context,
182 // for now testing reachability, can be moved to no-op
183 log.debug("Triggering probe equals testing reachability on device {}", deviceId);
184 isReachable(deviceId);
185 }
186
187 @Override
188 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
189 log.debug("Received role {} for device {}", newRole, deviceId);
190 CompletableFuture<MastershipRole> roleReply = getHandshaker(deviceId).roleChanged(newRole);
191 roleReply.thenAcceptAsync(mastership -> providerService.receivedRoleReply(deviceId, newRole, mastership));
192 }
193
194 @Override
195 public boolean isReachable(DeviceId deviceId) {
196 log.debug("Testing rechability for device {}", deviceId);
197 CompletableFuture<Boolean> reachable = getHandshaker(deviceId).isReachable();
198 try {
199 return reachable.get(10, TimeUnit.SECONDS);
200 } catch (InterruptedException | ExecutionException | TimeoutException e) {
201 log.error("Device {} is not reachable", deviceId, e);
202 return false;
203 }
204 }
205
206 @Override
207 public void changePortState(DeviceId deviceId, PortNumber portNumber,
208 boolean enable) {
209 if (deviceService.getDevice(deviceId).is(PortAdmin.class)) {
210
211 PortAdmin portAdmin = getPortAdmin(deviceId);
212 CompletableFuture<Boolean> modified;
213 if (enable) {
214 modified = portAdmin.enable(portNumber);
215 } else {
216 modified = portAdmin.disable(portNumber);
217 }
218 modified.thenAcceptAsync(result -> {
219 if (!result) {
220 log.warn("Your device {} port {} status can't be changed to {}",
221 deviceId, portNumber, enable);
222 }
223 });
224
225 } else {
226 log.warn("Device {} does not support PortAdmin behaviour", deviceId);
227 }
228 }
229
230 private DeviceHandshaker getHandshaker(DeviceId deviceId) {
231 Driver driver = getDriver(deviceId);
232 return getBehaviour(driver, DeviceHandshaker.class,
233 new DefaultDriverData(driver, deviceId));
234 }
235
236 private PortAdmin getPortAdmin(DeviceId deviceId) {
237 Driver driver = getDriver(deviceId);
238 return getBehaviour(driver, PortAdmin.class,
239 new DefaultDriverData(driver, deviceId));
240
241 }
242
243 private Driver getDriver(DeviceId deviceId) {
244 Driver driver;
245 try {
246 driver = driverService.getDriver(deviceId);
247 } catch (ItemNotFoundException e) {
248 log.debug("Falling back to configuration to fetch driver " +
249 "for device {}", deviceId);
250 driver = driverService.getDriver(
251 cfgService.getConfig(deviceId, BasicDeviceConfig.class).driver());
252 }
253 return driver;
254 }
255
256 //needed since the device manager will not return the driver through implementation()
257 // method since the device is not pushed to the core so for the connectDevice
258 // we need to work around that in order to test before calling
259 // store.createOrUpdateDevice
260 private <T extends Behaviour> T getBehaviour(Driver driver, Class<T> type,
261 DriverData data) {
262 if (driver.hasBehaviour(type)) {
263 DefaultDriverHandler handler = new DefaultDriverHandler(data);
264 return driver.createBehaviour(handler, type);
265 } else {
266 return null;
267 }
268 }
269
270 //Connects a general device
271 private void connectDevice(DeviceId deviceId) {
272 //retrieve the configuration
273 GeneralProviderDeviceConfig providerConfig =
274 cfgService.getConfig(deviceId, GeneralProviderDeviceConfig.class);
275 BasicDeviceConfig basicDeviceConfig =
276 cfgService.getConfig(deviceId, BasicDeviceConfig.class);
277
278 if (providerConfig == null || basicDeviceConfig == null) {
279 log.error("Configuration is NULL: basic config {}, general provider " +
280 "config {}", basicDeviceConfig, providerConfig);
281 } else {
282 log.info("Connecting to device {}", deviceId);
283
284 Driver driver = driverService.getDriver(basicDeviceConfig.driver());
285 DriverData driverData = new DefaultDriverData(driver, deviceId);
286
287 DeviceHandshaker handshaker =
288 getBehaviour(driver, DeviceHandshaker.class, driverData);
289
290 if (handshaker != null) {
291
292 //Storing deviceKeyId and all other config values
293 // as data in the driver with protocol_<info>
294 // name as the key. e.g protocol_ip
295 providerConfig.protocolsInfo()
296 .forEach((protocol, deviceInfoConfig) -> {
297 deviceInfoConfig.configValues()
298 .forEach((k, v) -> driverData.set(protocol + "_" + k, v));
299 driverData.set(protocol + "_key", deviceInfoConfig.deviceKeyId());
300 });
301
302 //Connecting to the device
303 CompletableFuture<Boolean> connected = handshaker.connect();
304
305 connected.thenAcceptAsync(result -> {
306 if (result) {
307
308 //Populated with the default values obtained by the driver
309 ChassisId cid = new ChassisId();
310 SparseAnnotations annotations = DefaultAnnotations.builder()
311 .set(AnnotationKeys.PROTOCOL,
312 providerConfig.protocolsInfo().keySet().toString())
313 .build();
314 DeviceDescription description =
315 new DefaultDeviceDescription(deviceId.uri(), Device.Type.SWITCH,
316 driver.manufacturer(), driver.hwVersion(),
317 driver.swVersion(), UNKNOWN,
318 cid, false, annotations);
319 //Empty list of ports
320 List<PortDescription> ports = new ArrayList<>();
321
322 if (driver.hasBehaviour(DeviceDescriptionDiscovery.class)) {
323 DeviceDescriptionDiscovery deviceDiscovery = driver
324 .createBehaviour(driverData, DeviceDescriptionDiscovery.class);
325
326 DeviceDescription newdescription = deviceDiscovery.discoverDeviceDetails();
327 if (newdescription != null) {
328 description = newdescription;
329 }
330 ports = deviceDiscovery.discoverPortDetails();
331 }
332 providerService.deviceConnected(deviceId, description);
333 providerService.updatePorts(deviceId, ports);
334
335 } else {
336 log.warn("Can't connect to device {}", deviceId);
337 }
338 });
339 } else {
340 log.error("Device {}, with driver {} does not support DeviceHandshaker " +
341 "behaviour, {}", deviceId, driver.name(), driver.behaviours());
342 }
343 }
344 }
345
346 private void disconnectDevice(DeviceId deviceId) {
347 log.info("Disconnecting for device {}", deviceId);
348 DeviceHandshaker handshaker = getHandshaker(deviceId);
349 if (handshaker != null) {
350 CompletableFuture<Boolean> disconnect = handshaker.disconnect();
351
352 disconnect.thenAcceptAsync(result -> {
353 if (result) {
354 log.info("Disconnected device {}", deviceId);
355 providerService.deviceDisconnected(deviceId);
356 } else {
357 log.warn("Device {} was unable to disconnect", deviceId);
358 }
359 });
360 } else {
361 //gracefully ignoring.
362 log.info("No DeviceHandshaker for device {}", deviceId);
363 }
364 }
365
366 //Needed to catch the exception in the executors since are not rethrown otherwise.
367 private Runnable exceptionSafe(Runnable runnable) {
368 return () -> {
369 try {
370 runnable.run();
371 } catch (Exception e) {
372 log.error("Unhandled Exception", e);
373 }
374 };
375 }
376
377 private void updatePortStatistics(DeviceId deviceId) {
378 Collection<PortStatistics> statistics = deviceService.getDevice(deviceId)
379 .as(PortStatisticsDiscovery.class)
380 .discoverPortStatistics();
381 providerService.updatePortStatistics(deviceId, statistics);
382 }
383
384 /**
385 * Listener for configuration events.
386 */
387 private class InternalNetworkConfigListener implements NetworkConfigListener {
388
389
390 @Override
391 public void event(NetworkConfigEvent event) {
392 DeviceId deviceId = (DeviceId) event.subject();
393 //Assuming that the deviceId comes with uri 'device:'
394 if (!deviceId.uri().getScheme().equals(URI_SCHEME)) {
395 // not under my scheme, skipping
396 log.debug("{} is not my scheme, skipping", deviceId);
397 return;
398 }
399 if (deviceService.getDevice(deviceId) == null || !deviceService.isAvailable(deviceId)) {
400 connectionExecutor.submit(exceptionSafe(() -> connectDevice(deviceId)));
401 } else {
402 log.info("Device {} is already connected to ONOS and is available", deviceId);
403 }
404 }
405
406 @Override
407 public boolean isRelevant(NetworkConfigEvent event) {
408 return event.configClass().equals(GeneralProviderDeviceConfig.class) &&
409 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
410 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
411 }
412 }
413
414 /**
415 * Listener for core device events.
416 */
417 private class InternalDeviceListener implements DeviceListener {
418 @Override
419 public void event(DeviceEvent event) {
420 Type type = event.type();
421
422 if (type.equals((Type.DEVICE_ADDED))) {
423
424 //For now this is scheduled periodically, when streaming API will
425 // be available we check and base it on the streaming API (e.g. gNMI)
426 if (deviceService.getDevice(event.subject().id()).
427 is(PortStatisticsDiscovery.class)) {
428 portStatsExecutor.scheduleAtFixedRate(exceptionSafe(() ->
429 updatePortStatistics(event.subject().id())),
430 0, PORT_STATS_PERIOD_SECONDS, TimeUnit.SECONDS);
431 updatePortStatistics(event.subject().id());
432 }
433
434 } else if (type.equals(Type.DEVICE_REMOVED)) {
435 connectionExecutor.submit(exceptionSafe(() ->
436 disconnectDevice(event.subject().id())));
437 }
438 }
439
440 @Override
441 public boolean isRelevant(DeviceEvent event) {
442 return URI_SCHEME.toUpperCase()
443 .equals(event.subject().id().uri().toString());
444 }
445 }
446}