blob: f684d31e8022805904e27283775e7e3876d461b4 [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;
Thomas Vachuska1b3cf362019-08-29 17:11:18 -070045import org.onosproject.net.device.DeviceAdminService;
Carmelo Casconee5b28722018-06-22 17:28:28 +020046import org.onosproject.net.device.DeviceAgentEvent;
47import org.onosproject.net.device.DeviceAgentListener;
Andrea Campanella241896c2017-05-10 13:11:04 -070048import org.onosproject.net.device.DeviceDescription;
49import org.onosproject.net.device.DeviceDescriptionDiscovery;
Thomas Vachuska1b3cf362019-08-29 17:11:18 -070050import org.onosproject.net.device.DeviceEvent;
Andrea Campanella241896c2017-05-10 13:11:04 -070051import org.onosproject.net.device.DeviceHandshaker;
Thomas Vachuska1b3cf362019-08-29 17:11:18 -070052import org.onosproject.net.device.DeviceListener;
Andrea Campanella241896c2017-05-10 13:11:04 -070053import org.onosproject.net.device.DeviceProvider;
54import org.onosproject.net.device.DeviceProviderRegistry;
55import org.onosproject.net.device.DeviceProviderService;
Andrea Campanella241896c2017-05-10 13:11:04 -070056import org.onosproject.net.device.PortDescription;
Andrea Campanella241896c2017-05-10 13:11:04 -070057import org.onosproject.net.driver.Behaviour;
58import org.onosproject.net.driver.DefaultDriverData;
59import org.onosproject.net.driver.DefaultDriverHandler;
60import org.onosproject.net.driver.Driver;
61import org.onosproject.net.driver.DriverData;
62import org.onosproject.net.driver.DriverService;
Carmelo Cascone59f57de2017-07-11 19:55:09 -040063import org.onosproject.net.pi.model.PiPipeconf;
Andrea Campanellabc112a92017-06-26 19:06:43 +020064import org.onosproject.net.pi.model.PiPipeconfId;
Carmelo Cascone39c28ca2017-11-15 13:03:57 -080065import org.onosproject.net.pi.service.PiPipeconfService;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070066import org.onosproject.net.pi.service.PiPipeconfWatchdogEvent;
67import org.onosproject.net.pi.service.PiPipeconfWatchdogListener;
68import org.onosproject.net.pi.service.PiPipeconfWatchdogService;
Andrea Campanella241896c2017-05-10 13:11:04 -070069import org.onosproject.net.provider.AbstractProvider;
70import org.onosproject.net.provider.ProviderId;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080071import org.onosproject.provider.general.device.impl.DeviceTaskExecutor.DeviceTaskException;
Andrea Campanella19090322017-08-22 10:31:37 +020072import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070073import org.osgi.service.component.annotations.Activate;
74import org.osgi.service.component.annotations.Component;
75import org.osgi.service.component.annotations.Deactivate;
76import org.osgi.service.component.annotations.Modified;
77import org.osgi.service.component.annotations.Reference;
78import org.osgi.service.component.annotations.ReferenceCardinality;
Andrea Campanella241896c2017-05-10 13:11:04 -070079import org.slf4j.Logger;
80
Andrea Campanellabc112a92017-06-26 19:06:43 +020081import java.util.Collections;
Andrea Campanella19090322017-08-22 10:31:37 +020082import java.util.Dictionary;
Andrea Campanella241896c2017-05-10 13:11:04 -070083import java.util.List;
Thomas Vachuska5b38dc02018-05-10 15:24:40 -070084import java.util.Map;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080085import java.util.Objects;
Carmelo Cascone1dfc7862019-04-17 16:37:44 -070086import java.util.Set;
Andrea Campanella241896c2017-05-10 13:11:04 -070087import java.util.concurrent.CompletableFuture;
Carmelo Casconee5b28722018-06-22 17:28:28 +020088import java.util.concurrent.ExecutorService;
Andrea Campanella19090322017-08-22 10:31:37 +020089import java.util.concurrent.ScheduledFuture;
Andrea Campanella241896c2017-05-10 13:11:04 -070090import java.util.concurrent.TimeUnit;
Andrea Campanella241896c2017-05-10 13:11:04 -070091
Carmelo Cascone3977ea42019-02-28 13:43:42 -080092import static com.google.common.base.Preconditions.checkNotNull;
93import static com.google.common.base.Strings.isNullOrEmpty;
Carmelo Cascone61469462019-03-05 23:59:11 -080094import static java.lang.String.format;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080095import static java.lang.System.currentTimeMillis;
Carmelo Casconee5b28722018-06-22 17:28:28 +020096import static java.util.concurrent.Executors.newFixedThreadPool;
Andrea Campanella241896c2017-05-10 13:11:04 -070097import static org.onlab.util.Tools.groupedThreads;
Carmelo Cascone4b616312019-04-17 14:15:45 -070098import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.CHECKUP_INTERVAL;
99import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.CHECKUP_INTERVAL_DEFAULT;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800100import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_INTERVAL;
101import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_INTERVAL_DEFAULT;
Andrea Campanella241896c2017-05-10 13:11:04 -0700102import static org.slf4j.LoggerFactory.getLogger;
103
104/**
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800105 * Provider which uses drivers to discover devices, perform initial handshake,
106 * and notify the core of disconnection events. The implementation listens for
107 * events from netcfg or the drivers (via {@link DeviceAgentListener}) andP
108 * schedules task for each event.
Andrea Campanella241896c2017-05-10 13:11:04 -0700109 */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700110@Component(immediate = true,
111 property = {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700112 CHECKUP_INTERVAL + ":Integer=" + CHECKUP_INTERVAL_DEFAULT,
pierventreee394c42022-02-15 12:25:27 +0100113 STATS_POLL_INTERVAL + ":Integer=" + STATS_POLL_INTERVAL_DEFAULT
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700114 })
Andrea Campanella241896c2017-05-10 13:11:04 -0700115public class GeneralDeviceProvider extends AbstractProvider
116 implements DeviceProvider {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200117
Andrea Campanella241896c2017-05-10 13:11:04 -0700118 private final Logger log = getLogger(getClass());
119
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800120 private static final String APP_NAME = "org.onosproject.generaldeviceprovider";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700121 private static final String URI_SCHEME = "device";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700122 private static final String DEVICE_PROVIDER_PACKAGE =
123 "org.onosproject.general.provider.device";
124 private static final int CORE_POOL_SIZE = 10;
125 private static final String UNKNOWN = "unknown";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700126
pierventrebc91cea2022-02-18 05:16:48 -0800127 // We have measured a grace period of 5s with the
128 // current devices - giving some time more to absorb
129 // any fluctuation.
130 private static final int GRACE_PERIOD = 8000;
131
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700132 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200133 private DeviceProviderRegistry providerRegistry;
Andrea Campanella241896c2017-05-10 13:11:04 -0700134
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700135 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200136 private ComponentConfigService componentConfigService;
Andrea Campanella4929a812017-10-09 18:38:23 +0200137
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700138 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200139 private NetworkConfigRegistry cfgService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700140
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700141 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200142 private CoreService coreService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700143
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700144 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700145 private DeviceAdminService deviceService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700146
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700147 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200148 private DriverService driverService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700149
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700150 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200151 private MastershipService mastershipService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200152
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700153 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800154 private ClusterService clusterService;
155
156 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700157 private PiPipeconfService pipeconfService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700158
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700159 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700160 private PiPipeconfWatchdogService pipeconfWatchdogService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200161
Yi Tsenge616d752018-11-27 10:53:27 -0800162 // FIXME: no longer general if we add a dependency to a protocol-specific
163 // service. Possible solutions are: rename this provider to
164 // StratumDeviceProvider, find a way to allow this provider to register for
165 // protocol specific events (e.g. port events) via drivers (similar to
166 // DeviceAgentListener).
167 @Reference(cardinality = ReferenceCardinality.MANDATORY)
168 private GnmiController gnmiController;
169
170 private GnmiDeviceStateSubscriber gnmiDeviceStateSubscriber;
171
Carmelo Cascone61469462019-03-05 23:59:11 -0800172 /**
173 * Configure interval for checking device availability; default is 10 sec.
174 */
Carmelo Cascone4b616312019-04-17 14:15:45 -0700175 private int checkupInterval = CHECKUP_INTERVAL_DEFAULT;
Andrea Campanella19090322017-08-22 10:31:37 +0200176
Carmelo Cascone61469462019-03-05 23:59:11 -0800177 /**
178 * Configure poll frequency for port status and stats; default is 10 sec.
179 */
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800180 private int statsPollInterval = STATS_POLL_INTERVAL_DEFAULT;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200181
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700182 private final Map<DeviceId, DeviceHandshaker> handshakersWithListeners = Maps.newConcurrentMap();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700183 private final Map<DeviceId, Long> lastCheckups = Maps.newConcurrentMap();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700184 private final InternalPipeconfWatchdogListener pipeconfWatchdogListener = new InternalPipeconfWatchdogListener();
Carmelo Casconee5b28722018-06-22 17:28:28 +0200185 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
186 private final DeviceAgentListener deviceAgentListener = new InternalDeviceAgentListener();
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700187 private final DeviceListener deviceListener = new InternalDeviceListener();
Andrea Campanella241896c2017-05-10 13:11:04 -0700188
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800189 private ExecutorService mainExecutor;
190 private DeviceTaskExecutor<TaskType> taskExecutor;
Carmelo Cascone4b616312019-04-17 14:15:45 -0700191 private ScheduledFuture<?> checkupTask;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800192 private StatsPoller statsPoller;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700193 private DeviceProviderService providerService;
194
195 public GeneralDeviceProvider() {
196 super(new ProviderId(URI_SCHEME, DEVICE_PROVIDER_PACKAGE));
197 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700198
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800199 protected DeviceProviderService providerService() {
200 return providerService;
201 }
202
Andrea Campanella241896c2017-05-10 13:11:04 -0700203 @Activate
Andrea Campanella1e573442018-05-17 17:07:13 +0200204 public void activate(ComponentContext context) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800205 mainExecutor = newFixedThreadPool(CORE_POOL_SIZE, groupedThreads(
Carmelo Cascone4b616312019-04-17 14:15:45 -0700206 "onos/gdp", "%d", log));
pierventre309d98e2022-02-28 13:37:08 -0800207 taskExecutor = new DeviceTaskExecutor<>(mainExecutor, GDP_ALLOWLIST);
Andrea Campanella241896c2017-05-10 13:11:04 -0700208 providerService = providerRegistry.register(this);
Andrea Campanella4929a812017-10-09 18:38:23 +0200209 componentConfigService.registerProperties(getClass());
Andrea Campanella241896c2017-05-10 13:11:04 -0700210 coreService.registerApplication(APP_NAME);
Andrea Campanella241896c2017-05-10 13:11:04 -0700211 cfgService.addListener(cfgListener);
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700212 deviceService.addListener(deviceListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700213 pipeconfWatchdogService.addListener(pipeconfWatchdogListener);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800214 gnmiDeviceStateSubscriber = new GnmiDeviceStateSubscriber(
215 gnmiController, deviceService, mastershipService, providerService);
Yi Tsenge616d752018-11-27 10:53:27 -0800216 gnmiDeviceStateSubscriber.activate();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700217 startOrReschedulePeriodicCheckupTasks();
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800218 statsPoller = new StatsPoller(deviceService, mastershipService, providerService);
219 statsPoller.activate(statsPollInterval);
220 modified(context);
Andrea Campanella241896c2017-05-10 13:11:04 -0700221 log.info("Started");
222 }
223
Andrea Campanella19090322017-08-22 10:31:37 +0200224 @Modified
225 public void modified(ComponentContext context) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200226 if (context == null) {
227 return;
Andrea Campanella19090322017-08-22 10:31:37 +0200228 }
229
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200230 Dictionary<?, ?> properties = context.getProperties();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700231 final int oldCheckupInterval = checkupInterval;
232 checkupInterval = Tools.getIntegerProperty(
233 properties, CHECKUP_INTERVAL, CHECKUP_INTERVAL_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200234 log.info("Configured. {} is configured to {} seconds",
Carmelo Cascone4b616312019-04-17 14:15:45 -0700235 CHECKUP_INTERVAL, checkupInterval);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800236 final int oldStatsPollFrequency = statsPollInterval;
237 statsPollInterval = Tools.getIntegerProperty(
238 properties, STATS_POLL_INTERVAL, STATS_POLL_INTERVAL_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200239 log.info("Configured. {} is configured to {} seconds",
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800240 STATS_POLL_INTERVAL, statsPollInterval);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200241
Carmelo Cascone4b616312019-04-17 14:15:45 -0700242 if (oldCheckupInterval != checkupInterval) {
243 startOrReschedulePeriodicCheckupTasks();
Andrea Campanella19090322017-08-22 10:31:37 +0200244 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200245
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800246 if (oldStatsPollFrequency != statsPollInterval) {
247 statsPoller.reschedule(statsPollInterval);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200248 }
Andrea Campanella19090322017-08-22 10:31:37 +0200249 }
250
Andrea Campanella241896c2017-05-10 13:11:04 -0700251 @Deactivate
252 public void deactivate() {
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700253 deviceService.removeListener(deviceListener);
254
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800255 // Shutdown stats poller.
256 statsPoller.deactivate();
257 statsPoller = null;
Carmelo Cascone4b616312019-04-17 14:15:45 -0700258 // Shutdown periodic checkup task.
259 checkupTask.cancel(false);
260 checkupTask = null;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800261 // Shutdown main and task executor.
262 taskExecutor.cancel();
263 taskExecutor = null;
264 mainExecutor.shutdownNow();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700265 try {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800266 mainExecutor.awaitTermination(5, TimeUnit.SECONDS);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700267 } catch (InterruptedException e) {
268 log.warn("connectionExecutor not terminated properly");
269 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800270 mainExecutor = null;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700271 // Remove all device agent listeners
272 handshakersWithListeners.values().forEach(h -> h.removeDeviceAgentListener(id()));
273 handshakersWithListeners.clear();
274 // Other cleanup.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700275 lastCheckups.clear();
Andrea Campanella4929a812017-10-09 18:38:23 +0200276 componentConfigService.unregisterProperties(getClass(), false);
Andrea Campanella241896c2017-05-10 13:11:04 -0700277 cfgService.removeListener(cfgListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700278 pipeconfWatchdogService.removeListener(pipeconfWatchdogListener);
Andrea Campanella241896c2017-05-10 13:11:04 -0700279 providerRegistry.unregister(this);
280 providerService = null;
Yi Tsenge616d752018-11-27 10:53:27 -0800281 gnmiDeviceStateSubscriber.deactivate();
282 gnmiDeviceStateSubscriber = null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700283 log.info("Stopped");
284 }
285
Andrea Campanella241896c2017-05-10 13:11:04 -0700286 @Override
287 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800288 checkNotNull(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700289 submitTask(deviceId, TaskType.CHECKUP);
Andrea Campanella241896c2017-05-10 13:11:04 -0700290 }
291
292 @Override
293 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800294 final MastershipInfo mastershipInfo = mastershipService.getMastershipFor(deviceId);
295 final NodeId localNodeId = clusterService.getLocalNode().id();
296
297 if (!mastershipInfo.getRole(localNodeId).equals(newRole)) {
298 log.warn("Inconsistent mastership info for {}! Requested {}, but " +
299 "mastership service reports {}, will apply the latter...",
300 deviceId, newRole, mastershipInfo.getRole(localNodeId));
301 newRole = mastershipInfo.getRole(localNodeId);
302 }
303
Carmelo Cascone4b616312019-04-17 14:15:45 -0700304 final DeviceHandshaker handshaker = getBehaviour(
305 deviceId, DeviceHandshaker.class);
306 if (handshaker == null) {
307 log.error("Null handshaker. Unable to notify role {} to {}",
308 newRole, deviceId);
309 return;
310 }
311
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800312 // Derive preference value.
313 final int preference;
314 switch (newRole) {
315 case MASTER:
316 preference = 0;
317 break;
318 case STANDBY:
319 preference = mastershipInfo.backups().indexOf(localNodeId) + 1;
320 if (preference == 0) {
321 // Not found in list.
322 log.error("Unable to derive mastership preference for {}, " +
323 "requested role {} but local node ID was " +
324 "not found among list of backup nodes " +
Carmelo Cascone4b616312019-04-17 14:15:45 -0700325 "reported by mastership service",
326 deviceId, newRole);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800327 return;
328 }
329 break;
330 case NONE:
331 // No preference for NONE, apply as is.
332 log.info("Notifying role {} to {}", newRole, deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700333 handshaker.roleChanged(newRole);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800334 return;
335 default:
336 log.error("Unrecognized mastership role {}", newRole);
337 return;
338 }
339
340 log.info("Notifying role {} (preference {}) for term {} to {}",
341 newRole, preference, mastershipInfo.term(), deviceId);
342
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800343 try {
344 handshaker.roleChanged(preference, mastershipInfo.term());
345 } catch (UnsupportedOperationException e) {
346 // Preference-based method not supported.
347 handshaker.roleChanged(newRole);
348 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700349 }
350
351 @Override
352 public boolean isReachable(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700353 final DeviceHandshaker handshaker = getBehaviour(
354 deviceId, DeviceHandshaker.class);
Andrea Campanellac1ecdd02018-01-12 12:48:24 +0100355 if (handshaker == null) {
356 return false;
357 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800358 return handshaker.isReachable();
Andrea Campanella241896c2017-05-10 13:11:04 -0700359 }
360
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800361 @Override
362 public boolean isAvailable(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700363 final DeviceHandshaker handshaker = getBehaviour(
364 deviceId, DeviceHandshaker.class);
365 if (handshaker == null) {
366 return false;
367 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800368 try {
369 // Try without probing the device...
370 return handshaker.isAvailable();
371 } catch (UnsupportedOperationException e) {
372 // Driver does not support that.
373 return probeAvailability(handshaker);
374 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700375 }
376
Andrea Campanella241896c2017-05-10 13:11:04 -0700377 @Override
378 public void changePortState(DeviceId deviceId, PortNumber portNumber,
379 boolean enable) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200380 if (!deviceService.getDevice(deviceId).is(PortAdmin.class)) {
381 log.warn("Missing PortAdmin behaviour on {}, aborting port state change",
382 deviceId);
383 return;
Andrea Campanella241896c2017-05-10 13:11:04 -0700384 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200385 final PortAdmin portAdmin = deviceService.getDevice(deviceId)
386 .as(PortAdmin.class);
387 final CompletableFuture<Boolean> modifyTask = enable
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200388 ? portAdmin.enable(portNumber)
389 : portAdmin.disable(portNumber);
Carmelo Cascone61469462019-03-05 23:59:11 -0800390 final String descr = format("%s port %s on %s",
391 (enable ? "enable" : "disable"),
392 portNumber, deviceId);
393 modifyTask.whenComplete((success, ex) -> {
394 if (ex != null) {
395 log.error("Exception while trying to " + descr, ex);
396 } else if (!success) {
397 log.warn("Unable to " + descr);
398 }
399 });
Andrea Campanella241896c2017-05-10 13:11:04 -0700400 }
401
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700402 @Override
403 public void triggerDisconnect(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800404 checkNotNull(deviceId);
405 log.info("Triggering disconnection of device {}", deviceId);
406 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
407 }
408
pierventre63ce7412022-02-03 21:34:04 +0100409 @Override
410 public CompletableFuture<Boolean> probeReachability(DeviceId deviceId) {
411 final DeviceHandshaker handshaker = getBehaviour(
412 deviceId, DeviceHandshaker.class);
413 if (handshaker == null) {
414 return CompletableFuture.completedFuture(false);
415 }
416 return handshaker.probeReachability();
417 }
418
pierventrebc91cea2022-02-18 05:16:48 -0800419 @Override
420 public int gracePeriod() {
421 return GRACE_PERIOD;
422 }
423
pierventre63ce7412022-02-03 21:34:04 +0100424 private boolean probeReachabilitySync(DeviceId deviceId) {
pierventrebc91cea2022-02-18 05:16:48 -0800425 // Wait 3/4 of the checkup interval and make sure the thread
426 // is not blocked more than the checkUpInterval
pierventre63ce7412022-02-03 21:34:04 +0100427 return Tools.futureGetOrElse(probeReachability(deviceId), (checkupInterval * 3000 / 4),
428 TimeUnit.MILLISECONDS, Boolean.FALSE);
429 }
430
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800431 /**
432 * Listener for configuration events.
433 */
434 private class InternalNetworkConfigListener implements NetworkConfigListener {
435 @Override
436 public void event(NetworkConfigEvent event) {
437 DeviceId deviceId = (DeviceId) event.subject();
438 switch (event.type()) {
439 case CONFIG_ADDED:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700440 if (configIsPresent(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800441 submitTask(deviceId, TaskType.CONNECTION_SETUP);
442 }
443 break;
444 case CONFIG_UPDATED:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700445 if (configIsPresent(deviceId) && mgmtAddrUpdated(event)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800446 submitTask(deviceId, TaskType.CONNECTION_UPDATE);
447 }
448 break;
449 case CONFIG_REMOVED:
450 if (event.configClass().equals(BasicDeviceConfig.class)) {
451 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
452 }
453 break;
454 default:
455 // Ignore
456 break;
457 }
458 }
459
460 private boolean mgmtAddrUpdated(NetworkConfigEvent event) {
461 if (!event.prevConfig().isPresent() || !event.config().isPresent()) {
462 return false;
463 }
464 final BasicDeviceConfig prev = (BasicDeviceConfig) event.prevConfig().get();
465 final BasicDeviceConfig current = (BasicDeviceConfig) event.config().get();
466 return !Objects.equals(prev.managementAddress(), current.managementAddress());
467 }
468
469 @Override
470 public boolean isRelevant(NetworkConfigEvent event) {
471 return event.configClass().equals(BasicDeviceConfig.class) &&
472 (event.subject() instanceof DeviceId) &&
473 myScheme((DeviceId) event.subject());
474 }
475 }
476
477 /**
478 * Listener for device agent events.
479 */
480 private class InternalDeviceAgentListener implements DeviceAgentListener {
481 @Override
482 public void event(DeviceAgentEvent event) {
483 DeviceId deviceId = event.subject();
484 switch (event.type()) {
485 case CHANNEL_OPEN:
486 submitTask(deviceId, TaskType.CHANNEL_OPEN);
487 break;
488 case CHANNEL_CLOSED:
489 case CHANNEL_ERROR:
490 submitTask(deviceId, TaskType.CHANNEL_CLOSED);
491 break;
492 case ROLE_MASTER:
493 submitTask(deviceId, TaskType.ROLE_MASTER);
494 break;
495 case ROLE_STANDBY:
496 submitTask(deviceId, TaskType.ROLE_STANDBY);
497 break;
498 case ROLE_NONE:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700499 // FIXME: in case of device disconnection, agents will
500 // signal role NONE, preventing the DeviceManager to mark
501 // the device as offline, as only the master can do that. We
502 // should change the DeviceManager. For now, we disable
503 // signaling role NONE.
504 // submitTask(deviceId, TaskType.ROLE_NONE);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800505 break;
506 case NOT_MASTER:
507 submitTask(deviceId, TaskType.NOT_MASTER);
508 break;
509 default:
510 log.warn("Unrecognized device agent event {}", event.type());
511 }
512 }
513 }
514
515 /**
516 * Pipeline event listener.
517 */
518 private class InternalPipeconfWatchdogListener implements PiPipeconfWatchdogListener {
519 @Override
520 public void event(PiPipeconfWatchdogEvent event) {
521 final DeviceId deviceId = event.subject();
522 switch (event.type()) {
523 case PIPELINE_READY:
524 submitTask(deviceId, TaskType.PIPELINE_READY);
525 break;
526 case PIPELINE_UNKNOWN:
527 submitTask(deviceId, TaskType.PIPELINE_NOT_READY);
528 break;
529 default:
530 break;
531 }
532 }
533
534 @Override
535 public boolean isRelevant(PiPipeconfWatchdogEvent event) {
536 return myScheme(event.subject());
537 }
538 }
539
Carmelo Cascone4b616312019-04-17 14:15:45 -0700540 private void startOrReschedulePeriodicCheckupTasks() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800541 synchronized (this) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700542 if (checkupTask != null) {
543 checkupTask.cancel(false);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800544 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700545 checkupTask = SharedScheduledExecutors.getPoolThreadExecutor()
546 .scheduleAtFixedRate(
547 this::submitCheckupTasksForAllDevices,
548 1,
549 checkupInterval,
pierventre475290e2021-04-09 14:32:11 +0200550 TimeUnit.SECONDS,
551 true);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800552 }
553 }
554
Carmelo Cascone4b616312019-04-17 14:15:45 -0700555 private void submitCheckupTasksForAllDevices() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800556 // Async trigger a task for all devices in the cfg.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700557 log.debug("Submitting checkup task for all devices...");
Carmelo Cascone1dfc7862019-04-17 16:37:44 -0700558 final Set<DeviceId> deviceToCheck = Sets.newHashSet();
559 // All devices in the core and in the config that we care about.
560 deviceService.getDevices().forEach(d -> {
561 if (myScheme(d.id())) {
562 deviceToCheck.add(d.id());
563 }
564 });
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800565 cfgService.getSubjects(DeviceId.class).stream()
566 .filter(GeneralDeviceProvider::myScheme)
Carmelo Cascone1dfc7862019-04-17 16:37:44 -0700567 .filter(this::configIsPresent)
568 .forEach(deviceToCheck::add);
569 deviceToCheck.forEach(d -> submitTask(d, TaskType.CHECKUP));
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800570 }
571
572 /**
573 * Type of tasks performed by this provider.
574 */
575 enum TaskType {
576 CONNECTION_SETUP,
577 CONNECTION_UPDATE,
578 CONNECTION_TEARDOWN,
579 PIPELINE_READY,
580 CHANNEL_OPEN,
581 CHANNEL_CLOSED,
582 PIPELINE_NOT_READY,
Carmelo Cascone4b616312019-04-17 14:15:45 -0700583 CHECKUP,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800584 ROLE_MASTER,
585 ROLE_NONE,
586 ROLE_STANDBY,
587 NOT_MASTER,
588 }
589
pierventre309d98e2022-02-28 13:37:08 -0800590 private static final Set<TaskType> GDP_ALLOWLIST = Sets.newHashSet(TaskType.ROLE_MASTER, TaskType.ROLE_NONE,
591 TaskType.ROLE_STANDBY, TaskType.NOT_MASTER);
592
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800593 private void submitTask(DeviceId deviceId, TaskType taskType) {
594 taskExecutor.submit(deviceId, taskType, taskRunnable(deviceId, taskType));
595 }
596
597 private Runnable taskRunnable(DeviceId deviceId, TaskType taskType) {
598 switch (taskType) {
599 case CONNECTION_SETUP:
600 return () -> handleConnectionSetup(deviceId);
601 case CONNECTION_UPDATE:
602 return () -> handleConnectionUpdate(deviceId);
603 case CONNECTION_TEARDOWN:
604 return () -> handleConnectionTeardown(deviceId);
605 case CHANNEL_OPEN:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700606 case CHECKUP:
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800607 case PIPELINE_READY:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700608 return () -> doCheckupAndRepair(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700609 case CHANNEL_CLOSED:
610 case PIPELINE_NOT_READY:
611 return () -> markOfflineIfNeeded(deviceId);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800612 case ROLE_MASTER:
613 return () -> handleMastershipResponse(deviceId, MastershipRole.MASTER);
614 case ROLE_STANDBY:
615 return () -> handleMastershipResponse(deviceId, MastershipRole.STANDBY);
616 case ROLE_NONE:
617 return () -> handleMastershipResponse(deviceId, MastershipRole.NONE);
618 case NOT_MASTER:
619 return () -> handleNotMaster(deviceId);
620 default:
621 throw new IllegalArgumentException("Unrecognized task type " + taskType);
622 }
623 }
624
625 private void handleConnectionSetup(DeviceId deviceId) {
626 assertConfig(deviceId);
627 // Bind pipeconf (if any and if device is capable).
628 bindPipeconfIfRequired(deviceId);
629 // Get handshaker.
630 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700631 if (handshaker.hasConnection() || handshakersWithListeners.containsKey(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800632 throw new DeviceTaskException("connection already exists");
633 }
634 // Add device agent listener.
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800635 handshakersWithListeners.put(deviceId, handshaker);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700636 handshaker.addDeviceAgentListener(id(), deviceAgentListener);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800637 // Start connection via handshaker.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700638 if (!handshaker.connect()) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800639 // Failed! Remove listeners.
640 handshaker.removeDeviceAgentListener(id());
641 handshakersWithListeners.remove(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700642 // Clean up connection state leftovers.
643 handshaker.disconnect();
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800644 throw new DeviceTaskException("connection failed");
645 }
646 createOrUpdateDevice(deviceId, false);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800647 // From here we expect a CHANNEL_OPEN event to update availability.
648 }
649
650 private void handleConnectionUpdate(DeviceId deviceId) {
651 assertConfig(deviceId);
652 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700653 if (!handshaker.hasConnection()) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800654 // If driver reports that a connection still exists, perhaps the
655 // part of the netcfg that changed does not affect the connection.
656 // Otherwise, remove any previous connection state from the old
657 // netcfg and create a new one.
658 log.warn("Detected change of connection endpoints for {}, will " +
659 "tear down existing connection and set up a new one...",
660 deviceId);
661 handleConnectionTeardown(deviceId);
662 handleConnectionSetup(deviceId);
663 }
664 }
665
666 private void createOrUpdateDevice(DeviceId deviceId, boolean available) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800667 assertConfig(deviceId);
pierventre475290e2021-04-09 14:32:11 +0200668
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700669 if (available) {
Carmelo Casconed51a5552019-04-13 01:22:25 -0700670 // Push port descriptions. If marking online, make sure to update
671 // ports before other subsystems pick up the device event.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700672 final List<PortDescription> ports = getPortDetails(deviceId);
673 providerService.updatePorts(deviceId, ports);
674 }
pierventre475290e2021-04-09 14:32:11 +0200675
pierventreb8856852021-10-01 22:01:22 +0200676 DeviceDescription deviceDescription = getDeviceDescription(deviceId, available);
677 DeviceDescription storeDescription = providerService.getDeviceDescription(deviceId);
678 if (deviceService.getDevice(deviceId) != null &&
679 deviceService.isAvailable(deviceId) == available &&
680 storeDescription != null) {
pierventrebc91cea2022-02-18 05:16:48 -0800681 // FIXME SDFAB-650 rethink the APIs and abstractions around the DeviceStore.
682 // Device registration is a two-step process for the GDP. Initially, the device is
683 // registered with default avail. to false. Later, the checkup task will update the
684 // description with the default avail to true in order to mark it available. Today,
685 // there is only one API to mark online a device from the device provider which is
686 // deviceConnected which assumes an update on the device description. The device provider
687 // is the only one able to update the device description and we have to make sure that
688 // the default avail. is flipped to true as it is used to mark as online the device when
689 // it is created or updated. Otherwise, if an ONOS instance fails and restarts, when re-joining
690 // the cluster, it will get the device marked as offline and will not be able to update
691 // its status until it become the master. This process concurs with the markOnline done
692 // by the background thread in the DeviceManager and its the reason why we cannot just check
693 // the device availability but we need to compare also the desc. Checking here the equality,
694 // as in general we may want to upgrade the device description at run time.
pierventreb8856852021-10-01 22:01:22 +0200695 DeviceDescription testDeviceDescription = DefaultDeviceDescription.copyReplacingAnnotation(
696 deviceDescription, storeDescription.annotations());
697 if (testDeviceDescription.equals(storeDescription)) {
698 return;
699 }
pierventre475290e2021-04-09 14:32:11 +0200700 }
pierventreb8856852021-10-01 22:01:22 +0200701
702 providerService.deviceConnected(deviceId, deviceDescription);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800703 }
704
705 private boolean probeAvailability(DeviceHandshaker handshaker) {
Carmelo Cascone61469462019-03-05 23:59:11 -0800706 return Futures.getUnchecked(handshaker.probeAvailability());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800707 }
708
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800709 private void markOfflineIfNeeded(DeviceId deviceId) {
710 assertDeviceRegistered(deviceId);
711 if (deviceService.isAvailable(deviceId)) {
712 providerService.deviceDisconnected(deviceId);
713 }
714 }
715
Carmelo Cascone4b616312019-04-17 14:15:45 -0700716 private void doCheckupAndRepair(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800717
Carmelo Cascone4b616312019-04-17 14:15:45 -0700718 // This task should be invoked periodically for each device known by
719 // this provider, or as a consequence of events signaling potential
720 // availability changes of the device. We check that everything is in
721 // order, repair what's wrong, and eventually mark the the device as
722 // available (or not) in the core.
723
724 if (!configIsPresent(deviceId)) {
725 // We should have a connection only for devices in the config.
726 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
727 return;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800728 }
729
Carmelo Cascone4b616312019-04-17 14:15:45 -0700730 final DeviceHandshaker handshaker = handshakersWithListeners.get(deviceId);
731 if (handshaker == null) {
732 // Device in config but we have not initiated a connection.
733 // Perhaps we missed the config event?
734 submitTask(deviceId, TaskType.CONNECTION_SETUP);
735 return;
736 }
737
738 // If here, we have a handshaker meaning we already connected once to
739 // the device...
740 if (!handshaker.hasConnection()) {
741 // ... but now the driver reports there is NOT a connection.
742 // Perhaps the netcfg changed and we didn't pick the event?
743 log.warn("Re-establishing lost connection to {}", deviceId);
744 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
745 submitTask(deviceId, TaskType.CONNECTION_SETUP);
746 return;
747 }
748
749 // If here, device should be registered in the core.
750 assertDeviceRegistered(deviceId);
751
pierventre63ce7412022-02-03 21:34:04 +0100752 if (!handshaker.isReachable() || !probeReachabilitySync(deviceId)) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700753 // Device appears to be offline.
754 markOfflineIfNeeded(deviceId);
pierventre63ce7412022-02-03 21:34:04 +0100755 // We expect the protocol layer to implement some sort of
Carmelo Cascone4b616312019-04-17 14:15:45 -0700756 // connection backoff mechanism and to signal availability via
pierventre63ce7412022-02-03 21:34:04 +0100757 // CHANNEL_OPEN events.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700758 return;
759 }
760
761 // If here, device is reachable. Now do mastership and availability
762 // checkups. To avoid overload of checkup tasks which might involve
763 // sending messages over the network and triggering mastership
764 // elections. We require a minimum interval of 1/3 of the configured
765 // checkupInterval between consecutive checkup tasks when the device is
766 // known to be available.
767
768 final Long lastCheckup = lastCheckups.get(deviceId);
769 final boolean isAvailable = deviceService.isAvailable(deviceId);
770 if (isAvailable && lastCheckup != null &&
771 (currentTimeMillis() - lastCheckup) < (checkupInterval * 1000 / 3)) {
772 if (log.isDebugEnabled()) {
773 log.debug("Dropping checkup task for {} as it happened recently",
774 deviceId);
775 }
776 return;
777 }
778 lastCheckups.put(deviceId, currentTimeMillis());
779
780 // Make sure device has a valid mastership role.
781 final MastershipRole expectedRole = mastershipService.getLocalRole(deviceId);
782 if (expectedRole == MastershipRole.NONE) {
783 log.debug("Detected invalid role ({}) for {}, waiting for mastership " +
784 "service to fix this...",
785 expectedRole, deviceId);
786 // Gentle nudge to fix things...
787 mastershipService.requestRoleForSync(deviceId);
788 return;
789 }
790
791 final MastershipRole deviceRole = handshaker.getRole();
pierventredac25892022-02-03 21:56:37 +0100792 // FIXME: we should be checking the mastership term as well.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700793 if (expectedRole != deviceRole) {
pierventredac25892022-02-03 21:56:37 +0100794 // Let's be greedy, if the role is NONE likely is due to the lazy channel
795 if (deviceRole == MastershipRole.NONE) {
796 log.warn("Detected role mismatch for {}, core expects {}, " +
797 "but device reports {}, reassert the role... ",
798 deviceId, expectedRole, deviceRole);
pierventrebc91cea2022-02-18 05:16:48 -0800799 // If we are experiencing a severe issue, eventually
800 // the DeviceManager will move the mastership
pierventredac25892022-02-03 21:56:37 +0100801 roleChanged(deviceId, expectedRole);
802 } else {
803 log.debug("Detected role mismatch for {}, core expects {}, " +
804 "but device reports {}, waiting for mastership " +
805 "service to fix this...",
806 deviceId, expectedRole, deviceRole);
807 // Gentle nudge to fix things...
808 providerService.receivedRoleReply(deviceId, deviceRole);
809 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700810 return;
811 }
812
813 // Check and update availability, which differently from reachability
814 // describes the ability of the device to forward packets.
815 if (probeAvailability(handshaker)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800816 // Device ready to do its job.
817 createOrUpdateDevice(deviceId, true);
818 } else {
819 markOfflineIfNeeded(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700820 if (isPipelineProgrammable(deviceId)) {
821 // If reachable, but not available, and pipeline programmable,
822 // there is a high chance it's because the pipeline is not READY
823 // (independently from what the pipeconf watchdog reports, as
824 // the status there might be outdated). Encourage pipeconf
825 // watchdog to perform a pipeline probe ASAP.
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800826 pipeconfWatchdogService.triggerProbe(deviceId);
827 }
828 }
829 }
830
831 private void handleMastershipResponse(DeviceId deviceId, MastershipRole response) {
832 assertDeviceRegistered(deviceId);
833 log.debug("Device {} asserted role {}", deviceId, response);
834 providerService.receivedRoleReply(deviceId, response);
835 }
836
837 private void handleNotMaster(DeviceId deviceId) {
838 assertDeviceRegistered(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700839 handleMastershipResponse(deviceId, handshakerOrFail(deviceId).getRole());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800840 }
841
842 private void assertDeviceRegistered(DeviceId deviceId) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700843 if (!deviceIsRegistered(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800844 throw new DeviceTaskException("device not registered in the core");
845 }
846 }
847
Carmelo Cascone4b616312019-04-17 14:15:45 -0700848 private boolean deviceIsRegistered(DeviceId deviceId) {
849 return deviceService.getDevice(deviceId) != null;
850 }
851
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800852 private void handleConnectionTeardown(DeviceId deviceId) {
853 if (deviceService.getDevice(deviceId) != null
854 && deviceService.isAvailable(deviceId)) {
855 providerService.deviceDisconnected(deviceId);
856 }
857 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
858 handshaker.removeDeviceAgentListener(id());
859 handshakersWithListeners.remove(deviceId);
860 handshaker.disconnect();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700861 lastCheckups.remove(deviceId);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800862 }
863
864 private void bindPipeconfIfRequired(DeviceId deviceId) {
865 if (pipeconfService.getPipeconf(deviceId).isPresent()
866 || !isPipelineProgrammable(deviceId)) {
867 // Nothing to do.
868 // Device has already a pipeconf or is not programmable.
869 return;
870 }
871 // Get pipeconf from netcfg or driver (default one).
872 final PiPipelineProgrammable pipelineProg = getBehaviour(
873 deviceId, PiPipelineProgrammable.class);
874 final PiPipeconfId pipeconfId = getPipeconfId(deviceId, pipelineProg);
875 if (pipeconfId == null) {
876 throw new DeviceTaskException("unable to find pipeconf");
877 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700878 if (!pipeconfService.getPipeconf(pipeconfId).isPresent()) {
879 throw new DeviceTaskException(format(
880 "pipeconf %s not registered", pipeconfId));
881 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800882 // Store binding in pipeconf service.
883 pipeconfService.bindToDevice(pipeconfId, deviceId);
884 }
885
886 private PiPipeconfId getPipeconfId(DeviceId deviceId, PiPipelineProgrammable pipelineProg) {
887 // Places to look for a pipeconf ID (in priority order)):
888 // 1) netcfg
889 // 2) device driver (default one)
890 final PiPipeconfId pipeconfId = getPipeconfFromCfg(deviceId);
891 if (pipeconfId != null && !pipeconfId.id().isEmpty()) {
892 return pipeconfId;
893 }
894 if (pipelineProg != null
895 && pipelineProg.getDefaultPipeconf().isPresent()) {
896 final PiPipeconf defaultPipeconf = pipelineProg.getDefaultPipeconf().get();
897 log.info("Using default pipeconf {} for {}", defaultPipeconf.id(), deviceId);
898 return defaultPipeconf.id();
899 }
900 return null;
901 }
902
903 private PiPipeconfId getPipeconfFromCfg(DeviceId deviceId) {
904 BasicDeviceConfig config = cfgService.getConfig(
905 deviceId, BasicDeviceConfig.class);
906 if (config == null) {
907 return null;
908 }
909 return config.pipeconf() != null
910 ? new PiPipeconfId(config.pipeconf()) : null;
911 }
912
913 private DeviceHandshaker handshakerOrFail(DeviceId deviceId) {
914 final DeviceHandshaker handshaker = getBehaviour(
915 deviceId, DeviceHandshaker.class);
916 if (handshaker == null) {
917 throw new DeviceTaskException("missing handshaker behavior");
918 }
919 return handshaker;
920 }
921
Carmelo Cascone4b616312019-04-17 14:15:45 -0700922 private boolean configIsPresent(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800923 final BasicDeviceConfig basicDeviceCfg = cfgService.getConfig(
924 deviceId, BasicDeviceConfig.class);
925 return basicDeviceCfg != null && !isNullOrEmpty(basicDeviceCfg.driver());
926 }
927
928 private void assertConfig(DeviceId deviceId) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700929 if (!configIsPresent(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800930 throw new DeviceTaskException("configuration is not complete");
931 }
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700932 }
933
Andrea Campanella241896c2017-05-10 13:11:04 -0700934 private Driver getDriver(DeviceId deviceId) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700935 try {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200936 // DriverManager checks first using basic device config.
937 return driverService.getDriver(deviceId);
Andrea Campanella241896c2017-05-10 13:11:04 -0700938 } catch (ItemNotFoundException e) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200939 log.error("Driver not found for {}", deviceId);
940 return null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700941 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700942 }
943
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700944 private <T extends Behaviour> T getBehaviour(DeviceId deviceId, Class<T> type) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700945 Driver driver = getDriver(deviceId);
946 if (driver == null) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700947 return null;
948 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700949 if (!driver.hasBehaviour(type)) {
950 return null;
951 }
952 final DriverData data = new DefaultDriverData(driver, deviceId);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700953 final DefaultDriverHandler handler = new DefaultDriverHandler(data);
954 return driver.createBehaviour(handler, type);
Andrea Campanella241896c2017-05-10 13:11:04 -0700955 }
956
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800957 private boolean hasBehaviour(DeviceId deviceId, Class<? extends Behaviour> type) {
958 Driver driver = getDriver(deviceId);
959 if (driver == null) {
960 return false;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200961 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800962 return driver.hasBehaviour(type);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700963 }
964
965 private DeviceDescription getDeviceDescription(
966 DeviceId deviceId, boolean defaultAvailable) {
967 // Get one from driver or forge.
968 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
969 deviceId, DeviceDescriptionDiscovery.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700970 if (deviceDiscovery == null) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700971 return forgeDeviceDescription(deviceId, defaultAvailable);
972 }
Yi Tsengd7716482018-10-31 15:34:30 -0700973
974 final DeviceDescription d = deviceDiscovery.discoverDeviceDetails();
975 if (d == null) {
976 return forgeDeviceDescription(deviceId, defaultAvailable);
977 }
978 // Enforce defaultAvailable flag over the one obtained from driver.
979 return new DefaultDeviceDescription(d, defaultAvailable, d.annotations());
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700980 }
981
982 private List<PortDescription> getPortDetails(DeviceId deviceId) {
983 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
984 deviceId, DeviceDescriptionDiscovery.class);
985 if (deviceDiscovery != null) {
986 return deviceDiscovery.discoverPortDetails();
987 } else {
988 return Collections.emptyList();
989 }
990 }
991
992 private DeviceDescription forgeDeviceDescription(
993 DeviceId deviceId, boolean defaultAvailable) {
994 // Uses handshaker and provider config to get driver data.
995 final DeviceHandshaker handshaker = getBehaviour(
996 deviceId, DeviceHandshaker.class);
997 final Driver driver = handshaker != null
998 ? handshaker.handler().driver() : null;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700999 return new DefaultDeviceDescription(
1000 deviceId.uri(),
1001 Device.Type.SWITCH,
1002 driver != null ? driver.manufacturer() : UNKNOWN,
1003 driver != null ? driver.hwVersion() : UNKNOWN,
1004 driver != null ? driver.swVersion() : UNKNOWN,
1005 UNKNOWN,
1006 new ChassisId(),
1007 defaultAvailable,
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001008 DefaultAnnotations.EMPTY);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001009 }
1010
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001011 static boolean myScheme(DeviceId deviceId) {
1012 return deviceId.uri().getScheme().equals(URI_SCHEME);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +02001013 }
1014
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001015 private boolean isPipelineProgrammable(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001016 return hasBehaviour(deviceId, PiPipelineProgrammable.class);
Carmelo Casconede3b6842018-09-05 17:45:10 -07001017 }
Thomas Vachuska1b3cf362019-08-29 17:11:18 -07001018
1019 private class InternalDeviceListener implements DeviceListener {
1020 @Override
1021 public void event(DeviceEvent event) {
1022 log.info("Triggering disconnect for device {}", event.subject().id());
1023 triggerDisconnect(event.subject().id());
1024 }
1025
1026 @Override
1027 public boolean isRelevant(DeviceEvent event) {
Thomas Vachuska24c0ca42019-08-29 18:19:16 -07001028 return DeviceEvent.Type.DEVICE_REMOVED == event.type() && myScheme(event.subject().id());
Thomas Vachuska1b3cf362019-08-29 17:11:18 -07001029 }
1030 }
Andrea Campanella241896c2017-05-10 13:11:04 -07001031}