blob: a1d7fb85f3f2feb80fe971c18f5276ee6fee5f8d [file] [log] [blame]
Andrea Campanella241896c2017-05-10 13:11:04 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Andrea Campanella241896c2017-05-10 13:11:04 -07003 *
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
Andrea Campanellabc112a92017-06-26 19:06:43 +020019import com.google.common.collect.Maps;
Carmelo Cascone1dfc7862019-04-17 16:37:44 -070020import com.google.common.collect.Sets;
Carmelo Cascone61469462019-03-05 23:59:11 -080021import com.google.common.util.concurrent.Futures;
Andrea Campanella241896c2017-05-10 13:11:04 -070022import org.onlab.packet.ChassisId;
23import org.onlab.util.ItemNotFoundException;
Carmelo Cascone4b616312019-04-17 14:15:45 -070024import org.onlab.util.SharedScheduledExecutors;
Andrea Campanella19090322017-08-22 10:31:37 +020025import org.onlab.util.Tools;
Andrea Campanella4929a812017-10-09 18:38:23 +020026import org.onosproject.cfg.ComponentConfigService;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080027import org.onosproject.cluster.ClusterService;
28import org.onosproject.cluster.NodeId;
Andrea Campanella241896c2017-05-10 13:11:04 -070029import org.onosproject.core.CoreService;
Yi Tsenge616d752018-11-27 10:53:27 -080030import org.onosproject.gnmi.api.GnmiController;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080031import org.onosproject.mastership.MastershipInfo;
Andrea Campanella14e196d2017-07-24 18:11:36 +020032import org.onosproject.mastership.MastershipService;
Andrea Campanella241896c2017-05-10 13:11:04 -070033import org.onosproject.net.DefaultAnnotations;
34import org.onosproject.net.Device;
35import org.onosproject.net.DeviceId;
36import org.onosproject.net.MastershipRole;
37import org.onosproject.net.PortNumber;
Carmelo Cascone87892e22017-11-13 16:01:29 -080038import org.onosproject.net.behaviour.PiPipelineProgrammable;
Andrea Campanella241896c2017-05-10 13:11:04 -070039import org.onosproject.net.behaviour.PortAdmin;
Andrea Campanella241896c2017-05-10 13:11:04 -070040import org.onosproject.net.config.NetworkConfigEvent;
41import org.onosproject.net.config.NetworkConfigListener;
42import org.onosproject.net.config.NetworkConfigRegistry;
43import org.onosproject.net.config.basics.BasicDeviceConfig;
Andrea Campanella241896c2017-05-10 13:11:04 -070044import org.onosproject.net.device.DefaultDeviceDescription;
Carmelo Casconee5b28722018-06-22 17:28:28 +020045import org.onosproject.net.device.DeviceAgentEvent;
46import org.onosproject.net.device.DeviceAgentListener;
Andrea Campanella241896c2017-05-10 13:11:04 -070047import org.onosproject.net.device.DeviceDescription;
48import org.onosproject.net.device.DeviceDescriptionDiscovery;
Andrea Campanella241896c2017-05-10 13:11:04 -070049import org.onosproject.net.device.DeviceHandshaker;
Andrea Campanella241896c2017-05-10 13:11:04 -070050import org.onosproject.net.device.DeviceProvider;
51import org.onosproject.net.device.DeviceProviderRegistry;
52import org.onosproject.net.device.DeviceProviderService;
53import org.onosproject.net.device.DeviceService;
54import org.onosproject.net.device.PortDescription;
Andrea Campanella241896c2017-05-10 13:11:04 -070055import 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;
Carmelo Cascone59f57de2017-07-11 19:55:09 -040061import org.onosproject.net.pi.model.PiPipeconf;
Andrea Campanellabc112a92017-06-26 19:06:43 +020062import org.onosproject.net.pi.model.PiPipeconfId;
Carmelo Cascone39c28ca2017-11-15 13:03:57 -080063import org.onosproject.net.pi.service.PiPipeconfService;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070064import org.onosproject.net.pi.service.PiPipeconfWatchdogEvent;
65import org.onosproject.net.pi.service.PiPipeconfWatchdogListener;
66import org.onosproject.net.pi.service.PiPipeconfWatchdogService;
Andrea Campanella241896c2017-05-10 13:11:04 -070067import org.onosproject.net.provider.AbstractProvider;
68import org.onosproject.net.provider.ProviderId;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080069import org.onosproject.provider.general.device.impl.DeviceTaskExecutor.DeviceTaskException;
Andrea Campanella19090322017-08-22 10:31:37 +020070import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070071import org.osgi.service.component.annotations.Activate;
72import org.osgi.service.component.annotations.Component;
73import org.osgi.service.component.annotations.Deactivate;
74import org.osgi.service.component.annotations.Modified;
75import org.osgi.service.component.annotations.Reference;
76import org.osgi.service.component.annotations.ReferenceCardinality;
Andrea Campanella241896c2017-05-10 13:11:04 -070077import org.slf4j.Logger;
78
Andrea Campanellabc112a92017-06-26 19:06:43 +020079import java.util.Collections;
Andrea Campanella19090322017-08-22 10:31:37 +020080import java.util.Dictionary;
Andrea Campanella241896c2017-05-10 13:11:04 -070081import java.util.List;
Thomas Vachuska5b38dc02018-05-10 15:24:40 -070082import java.util.Map;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080083import java.util.Objects;
Carmelo Cascone1dfc7862019-04-17 16:37:44 -070084import java.util.Set;
Andrea Campanella241896c2017-05-10 13:11:04 -070085import java.util.concurrent.CompletableFuture;
Carmelo Casconee5b28722018-06-22 17:28:28 +020086import java.util.concurrent.ExecutorService;
Andrea Campanella19090322017-08-22 10:31:37 +020087import java.util.concurrent.ScheduledFuture;
Andrea Campanella241896c2017-05-10 13:11:04 -070088import java.util.concurrent.TimeUnit;
Andrea Campanella241896c2017-05-10 13:11:04 -070089
Carmelo Cascone3977ea42019-02-28 13:43:42 -080090import static com.google.common.base.Preconditions.checkNotNull;
91import static com.google.common.base.Strings.isNullOrEmpty;
Carmelo Cascone61469462019-03-05 23:59:11 -080092import static java.lang.String.format;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080093import static java.lang.System.currentTimeMillis;
Carmelo Casconee5b28722018-06-22 17:28:28 +020094import static java.util.concurrent.Executors.newFixedThreadPool;
Andrea Campanella241896c2017-05-10 13:11:04 -070095import static org.onlab.util.Tools.groupedThreads;
Carmelo Cascone4b616312019-04-17 14:15:45 -070096import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.CHECKUP_INTERVAL;
97import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.CHECKUP_INTERVAL_DEFAULT;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080098import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_INTERVAL;
99import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_INTERVAL_DEFAULT;
Andrea Campanella241896c2017-05-10 13:11:04 -0700100import static org.slf4j.LoggerFactory.getLogger;
101
102/**
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800103 * Provider which uses drivers to discover devices, perform initial handshake,
104 * and notify the core of disconnection events. The implementation listens for
105 * events from netcfg or the drivers (via {@link DeviceAgentListener}) andP
106 * schedules task for each event.
Andrea Campanella241896c2017-05-10 13:11:04 -0700107 */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700108@Component(immediate = true,
109 property = {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700110 CHECKUP_INTERVAL + ":Integer=" + CHECKUP_INTERVAL_DEFAULT,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800111 STATS_POLL_INTERVAL + ":Integer=" + STATS_POLL_INTERVAL_DEFAULT,
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700112 })
Andrea Campanella241896c2017-05-10 13:11:04 -0700113public class GeneralDeviceProvider extends AbstractProvider
114 implements DeviceProvider {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200115
Andrea Campanella241896c2017-05-10 13:11:04 -0700116 private final Logger log = getLogger(getClass());
117
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800118 private static final String APP_NAME = "org.onosproject.generaldeviceprovider";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700119 private static final String URI_SCHEME = "device";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700120 private static final String DEVICE_PROVIDER_PACKAGE =
121 "org.onosproject.general.provider.device";
122 private static final int CORE_POOL_SIZE = 10;
123 private static final String UNKNOWN = "unknown";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700124
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700125 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200126 private DeviceProviderRegistry providerRegistry;
Andrea Campanella241896c2017-05-10 13:11:04 -0700127
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700128 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200129 private ComponentConfigService componentConfigService;
Andrea Campanella4929a812017-10-09 18:38:23 +0200130
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700131 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200132 private NetworkConfigRegistry cfgService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700133
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700134 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200135 private CoreService coreService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700136
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700137 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200138 private DeviceService deviceService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700139
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700140 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200141 private DriverService driverService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700142
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700143 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200144 private MastershipService mastershipService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200145
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700146 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800147 private ClusterService clusterService;
148
149 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700150 private PiPipeconfService pipeconfService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700151
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700152 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700153 private PiPipeconfWatchdogService pipeconfWatchdogService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200154
Yi Tsenge616d752018-11-27 10:53:27 -0800155 // FIXME: no longer general if we add a dependency to a protocol-specific
156 // service. Possible solutions are: rename this provider to
157 // StratumDeviceProvider, find a way to allow this provider to register for
158 // protocol specific events (e.g. port events) via drivers (similar to
159 // DeviceAgentListener).
160 @Reference(cardinality = ReferenceCardinality.MANDATORY)
161 private GnmiController gnmiController;
162
163 private GnmiDeviceStateSubscriber gnmiDeviceStateSubscriber;
164
Carmelo Cascone61469462019-03-05 23:59:11 -0800165 /**
166 * Configure interval for checking device availability; default is 10 sec.
167 */
Carmelo Cascone4b616312019-04-17 14:15:45 -0700168 private int checkupInterval = CHECKUP_INTERVAL_DEFAULT;
Andrea Campanella19090322017-08-22 10:31:37 +0200169
Carmelo Cascone61469462019-03-05 23:59:11 -0800170 /**
171 * Configure poll frequency for port status and stats; default is 10 sec.
172 */
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800173 private int statsPollInterval = STATS_POLL_INTERVAL_DEFAULT;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200174
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700175 private final Map<DeviceId, DeviceHandshaker> handshakersWithListeners = Maps.newConcurrentMap();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700176 private final Map<DeviceId, Long> lastCheckups = Maps.newConcurrentMap();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700177 private final InternalPipeconfWatchdogListener pipeconfWatchdogListener = new InternalPipeconfWatchdogListener();
Carmelo Casconee5b28722018-06-22 17:28:28 +0200178 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
179 private final DeviceAgentListener deviceAgentListener = new InternalDeviceAgentListener();
Andrea Campanella241896c2017-05-10 13:11:04 -0700180
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800181 private ExecutorService mainExecutor;
182 private DeviceTaskExecutor<TaskType> taskExecutor;
Carmelo Cascone4b616312019-04-17 14:15:45 -0700183 private ScheduledFuture<?> checkupTask;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800184 private StatsPoller statsPoller;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700185 private DeviceProviderService providerService;
186
187 public GeneralDeviceProvider() {
188 super(new ProviderId(URI_SCHEME, DEVICE_PROVIDER_PACKAGE));
189 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700190
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800191 protected DeviceProviderService providerService() {
192 return providerService;
193 }
194
Andrea Campanella241896c2017-05-10 13:11:04 -0700195 @Activate
Andrea Campanella1e573442018-05-17 17:07:13 +0200196 public void activate(ComponentContext context) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800197 mainExecutor = newFixedThreadPool(CORE_POOL_SIZE, groupedThreads(
Carmelo Cascone4b616312019-04-17 14:15:45 -0700198 "onos/gdp", "%d", log));
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800199 taskExecutor = new DeviceTaskExecutor<>(mainExecutor);
Andrea Campanella241896c2017-05-10 13:11:04 -0700200 providerService = providerRegistry.register(this);
Andrea Campanella4929a812017-10-09 18:38:23 +0200201 componentConfigService.registerProperties(getClass());
Andrea Campanella241896c2017-05-10 13:11:04 -0700202 coreService.registerApplication(APP_NAME);
Andrea Campanella241896c2017-05-10 13:11:04 -0700203 cfgService.addListener(cfgListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700204 pipeconfWatchdogService.addListener(pipeconfWatchdogListener);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800205 gnmiDeviceStateSubscriber = new GnmiDeviceStateSubscriber(
206 gnmiController, deviceService, mastershipService, providerService);
Yi Tsenge616d752018-11-27 10:53:27 -0800207 gnmiDeviceStateSubscriber.activate();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700208 startOrReschedulePeriodicCheckupTasks();
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800209 statsPoller = new StatsPoller(deviceService, mastershipService, providerService);
210 statsPoller.activate(statsPollInterval);
211 modified(context);
Andrea Campanella241896c2017-05-10 13:11:04 -0700212 log.info("Started");
213 }
214
Andrea Campanella19090322017-08-22 10:31:37 +0200215 @Modified
216 public void modified(ComponentContext context) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200217 if (context == null) {
218 return;
Andrea Campanella19090322017-08-22 10:31:37 +0200219 }
220
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200221 Dictionary<?, ?> properties = context.getProperties();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700222 final int oldCheckupInterval = checkupInterval;
223 checkupInterval = Tools.getIntegerProperty(
224 properties, CHECKUP_INTERVAL, CHECKUP_INTERVAL_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200225 log.info("Configured. {} is configured to {} seconds",
Carmelo Cascone4b616312019-04-17 14:15:45 -0700226 CHECKUP_INTERVAL, checkupInterval);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800227 final int oldStatsPollFrequency = statsPollInterval;
228 statsPollInterval = Tools.getIntegerProperty(
229 properties, STATS_POLL_INTERVAL, STATS_POLL_INTERVAL_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200230 log.info("Configured. {} is configured to {} seconds",
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800231 STATS_POLL_INTERVAL, statsPollInterval);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200232
Carmelo Cascone4b616312019-04-17 14:15:45 -0700233 if (oldCheckupInterval != checkupInterval) {
234 startOrReschedulePeriodicCheckupTasks();
Andrea Campanella19090322017-08-22 10:31:37 +0200235 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200236
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800237 if (oldStatsPollFrequency != statsPollInterval) {
238 statsPoller.reschedule(statsPollInterval);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200239 }
Andrea Campanella19090322017-08-22 10:31:37 +0200240 }
241
Andrea Campanella241896c2017-05-10 13:11:04 -0700242 @Deactivate
243 public void deactivate() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800244 // Shutdown stats poller.
245 statsPoller.deactivate();
246 statsPoller = null;
Carmelo Cascone4b616312019-04-17 14:15:45 -0700247 // Shutdown periodic checkup task.
248 checkupTask.cancel(false);
249 checkupTask = null;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800250 // Shutdown main and task executor.
251 taskExecutor.cancel();
252 taskExecutor = null;
253 mainExecutor.shutdownNow();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700254 try {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800255 mainExecutor.awaitTermination(5, TimeUnit.SECONDS);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700256 } catch (InterruptedException e) {
257 log.warn("connectionExecutor not terminated properly");
258 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800259 mainExecutor = null;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700260 // Remove all device agent listeners
261 handshakersWithListeners.values().forEach(h -> h.removeDeviceAgentListener(id()));
262 handshakersWithListeners.clear();
263 // Other cleanup.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700264 lastCheckups.clear();
Andrea Campanella4929a812017-10-09 18:38:23 +0200265 componentConfigService.unregisterProperties(getClass(), false);
Andrea Campanella241896c2017-05-10 13:11:04 -0700266 cfgService.removeListener(cfgListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700267 pipeconfWatchdogService.removeListener(pipeconfWatchdogListener);
Andrea Campanella241896c2017-05-10 13:11:04 -0700268 providerRegistry.unregister(this);
269 providerService = null;
Yi Tsenge616d752018-11-27 10:53:27 -0800270 gnmiDeviceStateSubscriber.deactivate();
271 gnmiDeviceStateSubscriber = null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700272 log.info("Stopped");
273 }
274
Andrea Campanella241896c2017-05-10 13:11:04 -0700275 @Override
276 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800277 checkNotNull(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700278 submitTask(deviceId, TaskType.CHECKUP);
Andrea Campanella241896c2017-05-10 13:11:04 -0700279 }
280
281 @Override
282 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800283 final MastershipInfo mastershipInfo = mastershipService.getMastershipFor(deviceId);
284 final NodeId localNodeId = clusterService.getLocalNode().id();
285
286 if (!mastershipInfo.getRole(localNodeId).equals(newRole)) {
287 log.warn("Inconsistent mastership info for {}! Requested {}, but " +
288 "mastership service reports {}, will apply the latter...",
289 deviceId, newRole, mastershipInfo.getRole(localNodeId));
290 newRole = mastershipInfo.getRole(localNodeId);
291 }
292
Carmelo Cascone4b616312019-04-17 14:15:45 -0700293 final DeviceHandshaker handshaker = getBehaviour(
294 deviceId, DeviceHandshaker.class);
295 if (handshaker == null) {
296 log.error("Null handshaker. Unable to notify role {} to {}",
297 newRole, deviceId);
298 return;
299 }
300
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800301 // Derive preference value.
302 final int preference;
303 switch (newRole) {
304 case MASTER:
305 preference = 0;
306 break;
307 case STANDBY:
308 preference = mastershipInfo.backups().indexOf(localNodeId) + 1;
309 if (preference == 0) {
310 // Not found in list.
311 log.error("Unable to derive mastership preference for {}, " +
312 "requested role {} but local node ID was " +
313 "not found among list of backup nodes " +
Carmelo Cascone4b616312019-04-17 14:15:45 -0700314 "reported by mastership service",
315 deviceId, newRole);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800316 return;
317 }
318 break;
319 case NONE:
320 // No preference for NONE, apply as is.
321 log.info("Notifying role {} to {}", newRole, deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700322 handshaker.roleChanged(newRole);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800323 return;
324 default:
325 log.error("Unrecognized mastership role {}", newRole);
326 return;
327 }
328
329 log.info("Notifying role {} (preference {}) for term {} to {}",
330 newRole, preference, mastershipInfo.term(), deviceId);
331
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800332 try {
333 handshaker.roleChanged(preference, mastershipInfo.term());
334 } catch (UnsupportedOperationException e) {
335 // Preference-based method not supported.
336 handshaker.roleChanged(newRole);
337 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700338 }
339
340 @Override
341 public boolean isReachable(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700342 final DeviceHandshaker handshaker = getBehaviour(
343 deviceId, DeviceHandshaker.class);
Andrea Campanellac1ecdd02018-01-12 12:48:24 +0100344 if (handshaker == null) {
345 return false;
346 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800347 return handshaker.isReachable();
Andrea Campanella241896c2017-05-10 13:11:04 -0700348 }
349
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800350 @Override
351 public boolean isAvailable(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700352 final DeviceHandshaker handshaker = getBehaviour(
353 deviceId, DeviceHandshaker.class);
354 if (handshaker == null) {
355 return false;
356 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800357 try {
358 // Try without probing the device...
359 return handshaker.isAvailable();
360 } catch (UnsupportedOperationException e) {
361 // Driver does not support that.
362 return probeAvailability(handshaker);
363 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700364 }
365
Andrea Campanella241896c2017-05-10 13:11:04 -0700366 @Override
367 public void changePortState(DeviceId deviceId, PortNumber portNumber,
368 boolean enable) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200369 if (!deviceService.getDevice(deviceId).is(PortAdmin.class)) {
370 log.warn("Missing PortAdmin behaviour on {}, aborting port state change",
371 deviceId);
372 return;
Andrea Campanella241896c2017-05-10 13:11:04 -0700373 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200374 final PortAdmin portAdmin = deviceService.getDevice(deviceId)
375 .as(PortAdmin.class);
376 final CompletableFuture<Boolean> modifyTask = enable
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200377 ? portAdmin.enable(portNumber)
378 : portAdmin.disable(portNumber);
Carmelo Cascone61469462019-03-05 23:59:11 -0800379 final String descr = format("%s port %s on %s",
380 (enable ? "enable" : "disable"),
381 portNumber, deviceId);
382 modifyTask.whenComplete((success, ex) -> {
383 if (ex != null) {
384 log.error("Exception while trying to " + descr, ex);
385 } else if (!success) {
386 log.warn("Unable to " + descr);
387 }
388 });
Andrea Campanella241896c2017-05-10 13:11:04 -0700389 }
390
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700391 @Override
392 public void triggerDisconnect(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800393 checkNotNull(deviceId);
394 log.info("Triggering disconnection of device {}", deviceId);
395 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
396 }
397
398 /**
399 * Listener for configuration events.
400 */
401 private class InternalNetworkConfigListener implements NetworkConfigListener {
402 @Override
403 public void event(NetworkConfigEvent event) {
404 DeviceId deviceId = (DeviceId) event.subject();
405 switch (event.type()) {
406 case CONFIG_ADDED:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700407 if (configIsPresent(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800408 submitTask(deviceId, TaskType.CONNECTION_SETUP);
409 }
410 break;
411 case CONFIG_UPDATED:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700412 if (configIsPresent(deviceId) && mgmtAddrUpdated(event)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800413 submitTask(deviceId, TaskType.CONNECTION_UPDATE);
414 }
415 break;
416 case CONFIG_REMOVED:
417 if (event.configClass().equals(BasicDeviceConfig.class)) {
418 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
419 }
420 break;
421 default:
422 // Ignore
423 break;
424 }
425 }
426
427 private boolean mgmtAddrUpdated(NetworkConfigEvent event) {
428 if (!event.prevConfig().isPresent() || !event.config().isPresent()) {
429 return false;
430 }
431 final BasicDeviceConfig prev = (BasicDeviceConfig) event.prevConfig().get();
432 final BasicDeviceConfig current = (BasicDeviceConfig) event.config().get();
433 return !Objects.equals(prev.managementAddress(), current.managementAddress());
434 }
435
436 @Override
437 public boolean isRelevant(NetworkConfigEvent event) {
438 return event.configClass().equals(BasicDeviceConfig.class) &&
439 (event.subject() instanceof DeviceId) &&
440 myScheme((DeviceId) event.subject());
441 }
442 }
443
444 /**
445 * Listener for device agent events.
446 */
447 private class InternalDeviceAgentListener implements DeviceAgentListener {
448 @Override
449 public void event(DeviceAgentEvent event) {
450 DeviceId deviceId = event.subject();
451 switch (event.type()) {
452 case CHANNEL_OPEN:
453 submitTask(deviceId, TaskType.CHANNEL_OPEN);
454 break;
455 case CHANNEL_CLOSED:
456 case CHANNEL_ERROR:
457 submitTask(deviceId, TaskType.CHANNEL_CLOSED);
458 break;
459 case ROLE_MASTER:
460 submitTask(deviceId, TaskType.ROLE_MASTER);
461 break;
462 case ROLE_STANDBY:
463 submitTask(deviceId, TaskType.ROLE_STANDBY);
464 break;
465 case ROLE_NONE:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700466 // FIXME: in case of device disconnection, agents will
467 // signal role NONE, preventing the DeviceManager to mark
468 // the device as offline, as only the master can do that. We
469 // should change the DeviceManager. For now, we disable
470 // signaling role NONE.
471 // submitTask(deviceId, TaskType.ROLE_NONE);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800472 break;
473 case NOT_MASTER:
474 submitTask(deviceId, TaskType.NOT_MASTER);
475 break;
476 default:
477 log.warn("Unrecognized device agent event {}", event.type());
478 }
479 }
480 }
481
482 /**
483 * Pipeline event listener.
484 */
485 private class InternalPipeconfWatchdogListener implements PiPipeconfWatchdogListener {
486 @Override
487 public void event(PiPipeconfWatchdogEvent event) {
488 final DeviceId deviceId = event.subject();
489 switch (event.type()) {
490 case PIPELINE_READY:
491 submitTask(deviceId, TaskType.PIPELINE_READY);
492 break;
493 case PIPELINE_UNKNOWN:
494 submitTask(deviceId, TaskType.PIPELINE_NOT_READY);
495 break;
496 default:
497 break;
498 }
499 }
500
501 @Override
502 public boolean isRelevant(PiPipeconfWatchdogEvent event) {
503 return myScheme(event.subject());
504 }
505 }
506
Carmelo Cascone4b616312019-04-17 14:15:45 -0700507 private void startOrReschedulePeriodicCheckupTasks() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800508 synchronized (this) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700509 if (checkupTask != null) {
510 checkupTask.cancel(false);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800511 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700512 checkupTask = SharedScheduledExecutors.getPoolThreadExecutor()
513 .scheduleAtFixedRate(
514 this::submitCheckupTasksForAllDevices,
515 1,
516 checkupInterval,
517 TimeUnit.SECONDS);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800518 }
519 }
520
Carmelo Cascone4b616312019-04-17 14:15:45 -0700521 private void submitCheckupTasksForAllDevices() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800522 // Async trigger a task for all devices in the cfg.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700523 log.debug("Submitting checkup task for all devices...");
Carmelo Cascone1dfc7862019-04-17 16:37:44 -0700524 final Set<DeviceId> deviceToCheck = Sets.newHashSet();
525 // All devices in the core and in the config that we care about.
526 deviceService.getDevices().forEach(d -> {
527 if (myScheme(d.id())) {
528 deviceToCheck.add(d.id());
529 }
530 });
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800531 cfgService.getSubjects(DeviceId.class).stream()
532 .filter(GeneralDeviceProvider::myScheme)
Carmelo Cascone1dfc7862019-04-17 16:37:44 -0700533 .filter(this::configIsPresent)
534 .forEach(deviceToCheck::add);
535 deviceToCheck.forEach(d -> submitTask(d, TaskType.CHECKUP));
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800536 }
537
538 /**
539 * Type of tasks performed by this provider.
540 */
541 enum TaskType {
542 CONNECTION_SETUP,
543 CONNECTION_UPDATE,
544 CONNECTION_TEARDOWN,
545 PIPELINE_READY,
546 CHANNEL_OPEN,
547 CHANNEL_CLOSED,
548 PIPELINE_NOT_READY,
Carmelo Cascone4b616312019-04-17 14:15:45 -0700549 CHECKUP,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800550 ROLE_MASTER,
551 ROLE_NONE,
552 ROLE_STANDBY,
553 NOT_MASTER,
554 }
555
556 private void submitTask(DeviceId deviceId, TaskType taskType) {
557 taskExecutor.submit(deviceId, taskType, taskRunnable(deviceId, taskType));
558 }
559
560 private Runnable taskRunnable(DeviceId deviceId, TaskType taskType) {
561 switch (taskType) {
562 case CONNECTION_SETUP:
563 return () -> handleConnectionSetup(deviceId);
564 case CONNECTION_UPDATE:
565 return () -> handleConnectionUpdate(deviceId);
566 case CONNECTION_TEARDOWN:
567 return () -> handleConnectionTeardown(deviceId);
568 case CHANNEL_OPEN:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700569 case CHECKUP:
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800570 case PIPELINE_READY:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700571 return () -> doCheckupAndRepair(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700572 case CHANNEL_CLOSED:
573 case PIPELINE_NOT_READY:
574 return () -> markOfflineIfNeeded(deviceId);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800575 case ROLE_MASTER:
576 return () -> handleMastershipResponse(deviceId, MastershipRole.MASTER);
577 case ROLE_STANDBY:
578 return () -> handleMastershipResponse(deviceId, MastershipRole.STANDBY);
579 case ROLE_NONE:
580 return () -> handleMastershipResponse(deviceId, MastershipRole.NONE);
581 case NOT_MASTER:
582 return () -> handleNotMaster(deviceId);
583 default:
584 throw new IllegalArgumentException("Unrecognized task type " + taskType);
585 }
586 }
587
588 private void handleConnectionSetup(DeviceId deviceId) {
589 assertConfig(deviceId);
590 // Bind pipeconf (if any and if device is capable).
591 bindPipeconfIfRequired(deviceId);
592 // Get handshaker.
593 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700594 if (handshaker.hasConnection() || handshakersWithListeners.containsKey(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800595 throw new DeviceTaskException("connection already exists");
596 }
597 // Add device agent listener.
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800598 handshakersWithListeners.put(deviceId, handshaker);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700599 handshaker.addDeviceAgentListener(id(), deviceAgentListener);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800600 // Start connection via handshaker.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700601 if (!handshaker.connect()) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800602 // Failed! Remove listeners.
603 handshaker.removeDeviceAgentListener(id());
604 handshakersWithListeners.remove(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700605 // Clean up connection state leftovers.
606 handshaker.disconnect();
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800607 throw new DeviceTaskException("connection failed");
608 }
609 createOrUpdateDevice(deviceId, false);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800610 // From here we expect a CHANNEL_OPEN event to update availability.
611 }
612
613 private void handleConnectionUpdate(DeviceId deviceId) {
614 assertConfig(deviceId);
615 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700616 if (!handshaker.hasConnection()) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800617 // If driver reports that a connection still exists, perhaps the
618 // part of the netcfg that changed does not affect the connection.
619 // Otherwise, remove any previous connection state from the old
620 // netcfg and create a new one.
621 log.warn("Detected change of connection endpoints for {}, will " +
622 "tear down existing connection and set up a new one...",
623 deviceId);
624 handleConnectionTeardown(deviceId);
625 handleConnectionSetup(deviceId);
626 }
627 }
628
629 private void createOrUpdateDevice(DeviceId deviceId, boolean available) {
630 if (deviceService.getDevice(deviceId) != null
631 && deviceService.isAvailable(deviceId) == available) {
632 // Other nodes might have advertised this device before us.
633 return;
634 }
635 assertConfig(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700636 if (available) {
Carmelo Casconed51a5552019-04-13 01:22:25 -0700637 // Push port descriptions. If marking online, make sure to update
638 // ports before other subsystems pick up the device event.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700639 final List<PortDescription> ports = getPortDetails(deviceId);
640 providerService.updatePorts(deviceId, ports);
641 }
Carmelo Casconed51a5552019-04-13 01:22:25 -0700642 providerService.deviceConnected(deviceId, getDeviceDescription(
643 deviceId, available));
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800644 }
645
646 private boolean probeAvailability(DeviceHandshaker handshaker) {
Carmelo Cascone61469462019-03-05 23:59:11 -0800647 return Futures.getUnchecked(handshaker.probeAvailability());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800648 }
649
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800650 private void markOfflineIfNeeded(DeviceId deviceId) {
651 assertDeviceRegistered(deviceId);
652 if (deviceService.isAvailable(deviceId)) {
653 providerService.deviceDisconnected(deviceId);
654 }
655 }
656
Carmelo Cascone4b616312019-04-17 14:15:45 -0700657 private void doCheckupAndRepair(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800658
Carmelo Cascone4b616312019-04-17 14:15:45 -0700659 // This task should be invoked periodically for each device known by
660 // this provider, or as a consequence of events signaling potential
661 // availability changes of the device. We check that everything is in
662 // order, repair what's wrong, and eventually mark the the device as
663 // available (or not) in the core.
664
665 if (!configIsPresent(deviceId)) {
666 // We should have a connection only for devices in the config.
667 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
668 return;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800669 }
670
Carmelo Cascone4b616312019-04-17 14:15:45 -0700671 final DeviceHandshaker handshaker = handshakersWithListeners.get(deviceId);
672 if (handshaker == null) {
673 // Device in config but we have not initiated a connection.
674 // Perhaps we missed the config event?
675 submitTask(deviceId, TaskType.CONNECTION_SETUP);
676 return;
677 }
678
679 // If here, we have a handshaker meaning we already connected once to
680 // the device...
681 if (!handshaker.hasConnection()) {
682 // ... but now the driver reports there is NOT a connection.
683 // Perhaps the netcfg changed and we didn't pick the event?
684 log.warn("Re-establishing lost connection to {}", deviceId);
685 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
686 submitTask(deviceId, TaskType.CONNECTION_SETUP);
687 return;
688 }
689
690 // If here, device should be registered in the core.
691 assertDeviceRegistered(deviceId);
692
693 if (!handshaker.isReachable()) {
694 // Device appears to be offline.
695 markOfflineIfNeeded(deviceId);
696 // While we expect the protocol layer to implement some sort of
697 // connection backoff mechanism and to signal availability via
698 // CHANNEL_OPEN events, we stimulate some channel activity now.
699 // Trigger probe over the network and forget about it (not waiting
700 // for future to complete). If channel is ready, we expect to come
701 // back here via a CHANNEL_OPEN event.
702 handshaker.probeReachability();
703 return;
704 }
705
706 // If here, device is reachable. Now do mastership and availability
707 // checkups. To avoid overload of checkup tasks which might involve
708 // sending messages over the network and triggering mastership
709 // elections. We require a minimum interval of 1/3 of the configured
710 // checkupInterval between consecutive checkup tasks when the device is
711 // known to be available.
712
713 final Long lastCheckup = lastCheckups.get(deviceId);
714 final boolean isAvailable = deviceService.isAvailable(deviceId);
715 if (isAvailable && lastCheckup != null &&
716 (currentTimeMillis() - lastCheckup) < (checkupInterval * 1000 / 3)) {
717 if (log.isDebugEnabled()) {
718 log.debug("Dropping checkup task for {} as it happened recently",
719 deviceId);
720 }
721 return;
722 }
723 lastCheckups.put(deviceId, currentTimeMillis());
724
725 // Make sure device has a valid mastership role.
726 final MastershipRole expectedRole = mastershipService.getLocalRole(deviceId);
727 if (expectedRole == MastershipRole.NONE) {
728 log.debug("Detected invalid role ({}) for {}, waiting for mastership " +
729 "service to fix this...",
730 expectedRole, deviceId);
731 // Gentle nudge to fix things...
732 mastershipService.requestRoleForSync(deviceId);
733 return;
734 }
735
736 final MastershipRole deviceRole = handshaker.getRole();
737 if (expectedRole != deviceRole) {
738 // FIXME: we should be checking the mastership term as well.
739 log.debug("Detected role mismatch for {}, core expects {}, " +
740 "but device reports {}, waiting for mastership " +
741 "service to fix this...",
742 deviceId, expectedRole, deviceRole);
743 // Gentle nudge to fix things...
744 providerService.receivedRoleReply(deviceId, deviceRole);
745 return;
746 }
747
748 // Check and update availability, which differently from reachability
749 // describes the ability of the device to forward packets.
750 if (probeAvailability(handshaker)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800751 // Device ready to do its job.
752 createOrUpdateDevice(deviceId, true);
753 } else {
754 markOfflineIfNeeded(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700755 if (isPipelineProgrammable(deviceId)) {
756 // If reachable, but not available, and pipeline programmable,
757 // there is a high chance it's because the pipeline is not READY
758 // (independently from what the pipeconf watchdog reports, as
759 // the status there might be outdated). Encourage pipeconf
760 // watchdog to perform a pipeline probe ASAP.
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800761 pipeconfWatchdogService.triggerProbe(deviceId);
762 }
763 }
764 }
765
766 private void handleMastershipResponse(DeviceId deviceId, MastershipRole response) {
767 assertDeviceRegistered(deviceId);
768 log.debug("Device {} asserted role {}", deviceId, response);
769 providerService.receivedRoleReply(deviceId, response);
770 }
771
772 private void handleNotMaster(DeviceId deviceId) {
773 assertDeviceRegistered(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700774 handleMastershipResponse(deviceId, handshakerOrFail(deviceId).getRole());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800775 }
776
777 private void assertDeviceRegistered(DeviceId deviceId) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700778 if (!deviceIsRegistered(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800779 throw new DeviceTaskException("device not registered in the core");
780 }
781 }
782
Carmelo Cascone4b616312019-04-17 14:15:45 -0700783 private boolean deviceIsRegistered(DeviceId deviceId) {
784 return deviceService.getDevice(deviceId) != null;
785 }
786
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800787 private void handleConnectionTeardown(DeviceId deviceId) {
788 if (deviceService.getDevice(deviceId) != null
789 && deviceService.isAvailable(deviceId)) {
790 providerService.deviceDisconnected(deviceId);
791 }
792 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
793 handshaker.removeDeviceAgentListener(id());
794 handshakersWithListeners.remove(deviceId);
795 handshaker.disconnect();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700796 lastCheckups.remove(deviceId);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800797 }
798
799 private void bindPipeconfIfRequired(DeviceId deviceId) {
800 if (pipeconfService.getPipeconf(deviceId).isPresent()
801 || !isPipelineProgrammable(deviceId)) {
802 // Nothing to do.
803 // Device has already a pipeconf or is not programmable.
804 return;
805 }
806 // Get pipeconf from netcfg or driver (default one).
807 final PiPipelineProgrammable pipelineProg = getBehaviour(
808 deviceId, PiPipelineProgrammable.class);
809 final PiPipeconfId pipeconfId = getPipeconfId(deviceId, pipelineProg);
810 if (pipeconfId == null) {
811 throw new DeviceTaskException("unable to find pipeconf");
812 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700813 if (!pipeconfService.getPipeconf(pipeconfId).isPresent()) {
814 throw new DeviceTaskException(format(
815 "pipeconf %s not registered", pipeconfId));
816 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800817 // Store binding in pipeconf service.
818 pipeconfService.bindToDevice(pipeconfId, deviceId);
819 }
820
821 private PiPipeconfId getPipeconfId(DeviceId deviceId, PiPipelineProgrammable pipelineProg) {
822 // Places to look for a pipeconf ID (in priority order)):
823 // 1) netcfg
824 // 2) device driver (default one)
825 final PiPipeconfId pipeconfId = getPipeconfFromCfg(deviceId);
826 if (pipeconfId != null && !pipeconfId.id().isEmpty()) {
827 return pipeconfId;
828 }
829 if (pipelineProg != null
830 && pipelineProg.getDefaultPipeconf().isPresent()) {
831 final PiPipeconf defaultPipeconf = pipelineProg.getDefaultPipeconf().get();
832 log.info("Using default pipeconf {} for {}", defaultPipeconf.id(), deviceId);
833 return defaultPipeconf.id();
834 }
835 return null;
836 }
837
838 private PiPipeconfId getPipeconfFromCfg(DeviceId deviceId) {
839 BasicDeviceConfig config = cfgService.getConfig(
840 deviceId, BasicDeviceConfig.class);
841 if (config == null) {
842 return null;
843 }
844 return config.pipeconf() != null
845 ? new PiPipeconfId(config.pipeconf()) : null;
846 }
847
848 private DeviceHandshaker handshakerOrFail(DeviceId deviceId) {
849 final DeviceHandshaker handshaker = getBehaviour(
850 deviceId, DeviceHandshaker.class);
851 if (handshaker == null) {
852 throw new DeviceTaskException("missing handshaker behavior");
853 }
854 return handshaker;
855 }
856
Carmelo Cascone4b616312019-04-17 14:15:45 -0700857 private boolean configIsPresent(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800858 final BasicDeviceConfig basicDeviceCfg = cfgService.getConfig(
859 deviceId, BasicDeviceConfig.class);
860 return basicDeviceCfg != null && !isNullOrEmpty(basicDeviceCfg.driver());
861 }
862
863 private void assertConfig(DeviceId deviceId) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700864 if (!configIsPresent(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800865 throw new DeviceTaskException("configuration is not complete");
866 }
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700867 }
868
Andrea Campanella241896c2017-05-10 13:11:04 -0700869 private Driver getDriver(DeviceId deviceId) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700870 try {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200871 // DriverManager checks first using basic device config.
872 return driverService.getDriver(deviceId);
Andrea Campanella241896c2017-05-10 13:11:04 -0700873 } catch (ItemNotFoundException e) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200874 log.error("Driver not found for {}", deviceId);
875 return null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700876 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700877 }
878
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700879 private <T extends Behaviour> T getBehaviour(DeviceId deviceId, Class<T> type) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700880 Driver driver = getDriver(deviceId);
881 if (driver == null) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700882 return null;
883 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700884 if (!driver.hasBehaviour(type)) {
885 return null;
886 }
887 final DriverData data = new DefaultDriverData(driver, deviceId);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700888 final DefaultDriverHandler handler = new DefaultDriverHandler(data);
889 return driver.createBehaviour(handler, type);
Andrea Campanella241896c2017-05-10 13:11:04 -0700890 }
891
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800892 private boolean hasBehaviour(DeviceId deviceId, Class<? extends Behaviour> type) {
893 Driver driver = getDriver(deviceId);
894 if (driver == null) {
895 return false;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200896 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800897 return driver.hasBehaviour(type);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700898 }
899
900 private DeviceDescription getDeviceDescription(
901 DeviceId deviceId, boolean defaultAvailable) {
902 // Get one from driver or forge.
903 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
904 deviceId, DeviceDescriptionDiscovery.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700905 if (deviceDiscovery == null) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700906 return forgeDeviceDescription(deviceId, defaultAvailable);
907 }
Yi Tsengd7716482018-10-31 15:34:30 -0700908
909 final DeviceDescription d = deviceDiscovery.discoverDeviceDetails();
910 if (d == null) {
911 return forgeDeviceDescription(deviceId, defaultAvailable);
912 }
913 // Enforce defaultAvailable flag over the one obtained from driver.
914 return new DefaultDeviceDescription(d, defaultAvailable, d.annotations());
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700915 }
916
917 private List<PortDescription> getPortDetails(DeviceId deviceId) {
918 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
919 deviceId, DeviceDescriptionDiscovery.class);
920 if (deviceDiscovery != null) {
921 return deviceDiscovery.discoverPortDetails();
922 } else {
923 return Collections.emptyList();
924 }
925 }
926
927 private DeviceDescription forgeDeviceDescription(
928 DeviceId deviceId, boolean defaultAvailable) {
929 // Uses handshaker and provider config to get driver data.
930 final DeviceHandshaker handshaker = getBehaviour(
931 deviceId, DeviceHandshaker.class);
932 final Driver driver = handshaker != null
933 ? handshaker.handler().driver() : null;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700934 return new DefaultDeviceDescription(
935 deviceId.uri(),
936 Device.Type.SWITCH,
937 driver != null ? driver.manufacturer() : UNKNOWN,
938 driver != null ? driver.hwVersion() : UNKNOWN,
939 driver != null ? driver.swVersion() : UNKNOWN,
940 UNKNOWN,
941 new ChassisId(),
942 defaultAvailable,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800943 DefaultAnnotations.EMPTY);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700944 }
945
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800946 static boolean myScheme(DeviceId deviceId) {
947 return deviceId.uri().getScheme().equals(URI_SCHEME);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200948 }
949
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700950 private boolean isPipelineProgrammable(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800951 return hasBehaviour(deviceId, PiPipelineProgrammable.class);
Carmelo Casconede3b6842018-09-05 17:45:10 -0700952 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700953}