blob: 5b32187d69eb5039666ecfc1a02dc188b13efdd5 [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;
pierventref260c912022-02-28 20:34:42 -080022import org.apache.commons.lang3.tuple.Pair;
Andrea Campanella241896c2017-05-10 13:11:04 -070023import org.onlab.packet.ChassisId;
24import org.onlab.util.ItemNotFoundException;
Carmelo Cascone4b616312019-04-17 14:15:45 -070025import org.onlab.util.SharedScheduledExecutors;
Andrea Campanella19090322017-08-22 10:31:37 +020026import org.onlab.util.Tools;
Andrea Campanella4929a812017-10-09 18:38:23 +020027import org.onosproject.cfg.ComponentConfigService;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080028import org.onosproject.cluster.ClusterService;
29import org.onosproject.cluster.NodeId;
Andrea Campanella241896c2017-05-10 13:11:04 -070030import org.onosproject.core.CoreService;
Yi Tsenge616d752018-11-27 10:53:27 -080031import org.onosproject.gnmi.api.GnmiController;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080032import org.onosproject.mastership.MastershipInfo;
Andrea Campanella14e196d2017-07-24 18:11:36 +020033import org.onosproject.mastership.MastershipService;
Andrea Campanella241896c2017-05-10 13:11:04 -070034import org.onosproject.net.DefaultAnnotations;
35import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.MastershipRole;
38import org.onosproject.net.PortNumber;
Carmelo Cascone87892e22017-11-13 16:01:29 -080039import org.onosproject.net.behaviour.PiPipelineProgrammable;
Andrea Campanella241896c2017-05-10 13:11:04 -070040import org.onosproject.net.behaviour.PortAdmin;
Andrea Campanella241896c2017-05-10 13:11:04 -070041import org.onosproject.net.config.NetworkConfigEvent;
42import org.onosproject.net.config.NetworkConfigListener;
43import org.onosproject.net.config.NetworkConfigRegistry;
44import org.onosproject.net.config.basics.BasicDeviceConfig;
Andrea Campanella241896c2017-05-10 13:11:04 -070045import org.onosproject.net.device.DefaultDeviceDescription;
Thomas Vachuska1b3cf362019-08-29 17:11:18 -070046import org.onosproject.net.device.DeviceAdminService;
Carmelo Casconee5b28722018-06-22 17:28:28 +020047import org.onosproject.net.device.DeviceAgentEvent;
48import org.onosproject.net.device.DeviceAgentListener;
Andrea Campanella241896c2017-05-10 13:11:04 -070049import org.onosproject.net.device.DeviceDescription;
50import org.onosproject.net.device.DeviceDescriptionDiscovery;
Thomas Vachuska1b3cf362019-08-29 17:11:18 -070051import org.onosproject.net.device.DeviceEvent;
Andrea Campanella241896c2017-05-10 13:11:04 -070052import org.onosproject.net.device.DeviceHandshaker;
Thomas Vachuska1b3cf362019-08-29 17:11:18 -070053import org.onosproject.net.device.DeviceListener;
Andrea Campanella241896c2017-05-10 13:11:04 -070054import org.onosproject.net.device.DeviceProvider;
55import org.onosproject.net.device.DeviceProviderRegistry;
56import org.onosproject.net.device.DeviceProviderService;
Andrea Campanella241896c2017-05-10 13:11:04 -070057import org.onosproject.net.device.PortDescription;
Andrea Campanella241896c2017-05-10 13:11:04 -070058import org.onosproject.net.driver.Behaviour;
59import org.onosproject.net.driver.DefaultDriverData;
60import org.onosproject.net.driver.DefaultDriverHandler;
61import org.onosproject.net.driver.Driver;
62import org.onosproject.net.driver.DriverData;
63import org.onosproject.net.driver.DriverService;
Carmelo Cascone59f57de2017-07-11 19:55:09 -040064import org.onosproject.net.pi.model.PiPipeconf;
Andrea Campanellabc112a92017-06-26 19:06:43 +020065import org.onosproject.net.pi.model.PiPipeconfId;
Carmelo Cascone39c28ca2017-11-15 13:03:57 -080066import org.onosproject.net.pi.service.PiPipeconfService;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070067import org.onosproject.net.pi.service.PiPipeconfWatchdogEvent;
68import org.onosproject.net.pi.service.PiPipeconfWatchdogListener;
69import org.onosproject.net.pi.service.PiPipeconfWatchdogService;
Andrea Campanella241896c2017-05-10 13:11:04 -070070import org.onosproject.net.provider.AbstractProvider;
71import org.onosproject.net.provider.ProviderId;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080072import org.onosproject.provider.general.device.impl.DeviceTaskExecutor.DeviceTaskException;
Andrea Campanella19090322017-08-22 10:31:37 +020073import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070074import org.osgi.service.component.annotations.Activate;
75import org.osgi.service.component.annotations.Component;
76import org.osgi.service.component.annotations.Deactivate;
77import org.osgi.service.component.annotations.Modified;
78import org.osgi.service.component.annotations.Reference;
79import org.osgi.service.component.annotations.ReferenceCardinality;
Andrea Campanella241896c2017-05-10 13:11:04 -070080import org.slf4j.Logger;
81
Andrea Campanellabc112a92017-06-26 19:06:43 +020082import java.util.Collections;
Andrea Campanella19090322017-08-22 10:31:37 +020083import java.util.Dictionary;
Andrea Campanella241896c2017-05-10 13:11:04 -070084import java.util.List;
Thomas Vachuska5b38dc02018-05-10 15:24:40 -070085import java.util.Map;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080086import java.util.Objects;
Carmelo Cascone1dfc7862019-04-17 16:37:44 -070087import java.util.Set;
Andrea Campanella241896c2017-05-10 13:11:04 -070088import java.util.concurrent.CompletableFuture;
Carmelo Casconee5b28722018-06-22 17:28:28 +020089import java.util.concurrent.ExecutorService;
Andrea Campanella19090322017-08-22 10:31:37 +020090import java.util.concurrent.ScheduledFuture;
Andrea Campanella241896c2017-05-10 13:11:04 -070091import java.util.concurrent.TimeUnit;
Andrea Campanella241896c2017-05-10 13:11:04 -070092
Carmelo Cascone3977ea42019-02-28 13:43:42 -080093import static com.google.common.base.Preconditions.checkNotNull;
94import static com.google.common.base.Strings.isNullOrEmpty;
Carmelo Cascone61469462019-03-05 23:59:11 -080095import static java.lang.String.format;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080096import static java.lang.System.currentTimeMillis;
Carmelo Casconee5b28722018-06-22 17:28:28 +020097import static java.util.concurrent.Executors.newFixedThreadPool;
Andrea Campanella241896c2017-05-10 13:11:04 -070098import static org.onlab.util.Tools.groupedThreads;
Carmelo Cascone4b616312019-04-17 14:15:45 -070099import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.CHECKUP_INTERVAL;
100import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.CHECKUP_INTERVAL_DEFAULT;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800101import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_INTERVAL;
102import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_INTERVAL_DEFAULT;
Andrea Campanella241896c2017-05-10 13:11:04 -0700103import static org.slf4j.LoggerFactory.getLogger;
104
105/**
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800106 * Provider which uses drivers to discover devices, perform initial handshake,
107 * and notify the core of disconnection events. The implementation listens for
108 * events from netcfg or the drivers (via {@link DeviceAgentListener}) andP
109 * schedules task for each event.
Andrea Campanella241896c2017-05-10 13:11:04 -0700110 */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700111@Component(immediate = true,
112 property = {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700113 CHECKUP_INTERVAL + ":Integer=" + CHECKUP_INTERVAL_DEFAULT,
pierventref497d132022-02-15 12:25:27 +0100114 STATS_POLL_INTERVAL + ":Integer=" + STATS_POLL_INTERVAL_DEFAULT
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700115 })
Andrea Campanella241896c2017-05-10 13:11:04 -0700116public class GeneralDeviceProvider extends AbstractProvider
117 implements DeviceProvider {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200118
Andrea Campanella241896c2017-05-10 13:11:04 -0700119 private final Logger log = getLogger(getClass());
120
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800121 private static final String APP_NAME = "org.onosproject.generaldeviceprovider";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700122 private static final String URI_SCHEME = "device";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700123 private static final String DEVICE_PROVIDER_PACKAGE =
124 "org.onosproject.general.provider.device";
125 private static final int CORE_POOL_SIZE = 10;
126 private static final String UNKNOWN = "unknown";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700127
pierventreeed482b2022-02-18 05:16:48 -0800128 // We have measured a grace period of 5s with the
129 // current devices - giving some time more to absorb
130 // any fluctuation.
131 private static final int GRACE_PERIOD = 8000;
132
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700133 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200134 private DeviceProviderRegistry providerRegistry;
Andrea Campanella241896c2017-05-10 13:11:04 -0700135
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700136 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200137 private ComponentConfigService componentConfigService;
Andrea Campanella4929a812017-10-09 18:38:23 +0200138
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700139 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200140 private NetworkConfigRegistry cfgService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700141
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700142 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200143 private CoreService coreService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700144
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700145 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700146 private DeviceAdminService deviceService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700147
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700148 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200149 private DriverService driverService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700150
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700151 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200152 private MastershipService mastershipService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200153
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700154 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800155 private ClusterService clusterService;
156
157 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700158 private PiPipeconfService pipeconfService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700159
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700160 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700161 private PiPipeconfWatchdogService pipeconfWatchdogService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200162
Yi Tsenge616d752018-11-27 10:53:27 -0800163 // FIXME: no longer general if we add a dependency to a protocol-specific
164 // service. Possible solutions are: rename this provider to
165 // StratumDeviceProvider, find a way to allow this provider to register for
166 // protocol specific events (e.g. port events) via drivers (similar to
167 // DeviceAgentListener).
168 @Reference(cardinality = ReferenceCardinality.MANDATORY)
169 private GnmiController gnmiController;
170
171 private GnmiDeviceStateSubscriber gnmiDeviceStateSubscriber;
172
Carmelo Cascone61469462019-03-05 23:59:11 -0800173 /**
174 * Configure interval for checking device availability; default is 10 sec.
175 */
Carmelo Cascone4b616312019-04-17 14:15:45 -0700176 private int checkupInterval = CHECKUP_INTERVAL_DEFAULT;
Andrea Campanella19090322017-08-22 10:31:37 +0200177
Carmelo Cascone61469462019-03-05 23:59:11 -0800178 /**
179 * Configure poll frequency for port status and stats; default is 10 sec.
180 */
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800181 private int statsPollInterval = STATS_POLL_INTERVAL_DEFAULT;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200182
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700183 private final Map<DeviceId, DeviceHandshaker> handshakersWithListeners = Maps.newConcurrentMap();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700184 private final Map<DeviceId, Long> lastCheckups = Maps.newConcurrentMap();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700185 private final InternalPipeconfWatchdogListener pipeconfWatchdogListener = new InternalPipeconfWatchdogListener();
Carmelo Casconee5b28722018-06-22 17:28:28 +0200186 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
187 private final DeviceAgentListener deviceAgentListener = new InternalDeviceAgentListener();
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700188 private final DeviceListener deviceListener = new InternalDeviceListener();
Andrea Campanella241896c2017-05-10 13:11:04 -0700189
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800190 private ExecutorService mainExecutor;
191 private DeviceTaskExecutor<TaskType> taskExecutor;
Carmelo Cascone4b616312019-04-17 14:15:45 -0700192 private ScheduledFuture<?> checkupTask;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800193 private StatsPoller statsPoller;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700194 private DeviceProviderService providerService;
195
pierventref260c912022-02-28 20:34:42 -0800196 private final Map<DeviceId, Pair<MastershipRole, Integer>> lastRoleRequest =
197 Maps.newConcurrentMap();
198
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700199 public GeneralDeviceProvider() {
200 super(new ProviderId(URI_SCHEME, DEVICE_PROVIDER_PACKAGE));
201 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700202
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800203 protected DeviceProviderService providerService() {
204 return providerService;
205 }
206
Andrea Campanella241896c2017-05-10 13:11:04 -0700207 @Activate
Andrea Campanella1e573442018-05-17 17:07:13 +0200208 public void activate(ComponentContext context) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800209 mainExecutor = newFixedThreadPool(CORE_POOL_SIZE, groupedThreads(
Carmelo Cascone4b616312019-04-17 14:15:45 -0700210 "onos/gdp", "%d", log));
pierventre5a5c8aa2022-02-28 13:37:08 -0800211 taskExecutor = new DeviceTaskExecutor<>(mainExecutor, GDP_ALLOWLIST);
Andrea Campanella241896c2017-05-10 13:11:04 -0700212 providerService = providerRegistry.register(this);
Andrea Campanella4929a812017-10-09 18:38:23 +0200213 componentConfigService.registerProperties(getClass());
Andrea Campanella241896c2017-05-10 13:11:04 -0700214 coreService.registerApplication(APP_NAME);
Andrea Campanella241896c2017-05-10 13:11:04 -0700215 cfgService.addListener(cfgListener);
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700216 deviceService.addListener(deviceListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700217 pipeconfWatchdogService.addListener(pipeconfWatchdogListener);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800218 gnmiDeviceStateSubscriber = new GnmiDeviceStateSubscriber(
219 gnmiController, deviceService, mastershipService, providerService);
Yi Tsenge616d752018-11-27 10:53:27 -0800220 gnmiDeviceStateSubscriber.activate();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700221 startOrReschedulePeriodicCheckupTasks();
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800222 statsPoller = new StatsPoller(deviceService, mastershipService, providerService);
223 statsPoller.activate(statsPollInterval);
224 modified(context);
Andrea Campanella241896c2017-05-10 13:11:04 -0700225 log.info("Started");
226 }
227
Andrea Campanella19090322017-08-22 10:31:37 +0200228 @Modified
229 public void modified(ComponentContext context) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200230 if (context == null) {
231 return;
Andrea Campanella19090322017-08-22 10:31:37 +0200232 }
233
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200234 Dictionary<?, ?> properties = context.getProperties();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700235 final int oldCheckupInterval = checkupInterval;
236 checkupInterval = Tools.getIntegerProperty(
237 properties, CHECKUP_INTERVAL, CHECKUP_INTERVAL_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200238 log.info("Configured. {} is configured to {} seconds",
Carmelo Cascone4b616312019-04-17 14:15:45 -0700239 CHECKUP_INTERVAL, checkupInterval);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800240 final int oldStatsPollFrequency = statsPollInterval;
241 statsPollInterval = Tools.getIntegerProperty(
242 properties, STATS_POLL_INTERVAL, STATS_POLL_INTERVAL_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200243 log.info("Configured. {} is configured to {} seconds",
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800244 STATS_POLL_INTERVAL, statsPollInterval);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200245
Carmelo Cascone4b616312019-04-17 14:15:45 -0700246 if (oldCheckupInterval != checkupInterval) {
247 startOrReschedulePeriodicCheckupTasks();
Andrea Campanella19090322017-08-22 10:31:37 +0200248 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200249
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800250 if (oldStatsPollFrequency != statsPollInterval) {
251 statsPoller.reschedule(statsPollInterval);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200252 }
Andrea Campanella19090322017-08-22 10:31:37 +0200253 }
254
Andrea Campanella241896c2017-05-10 13:11:04 -0700255 @Deactivate
256 public void deactivate() {
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700257 deviceService.removeListener(deviceListener);
258
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800259 // Shutdown stats poller.
260 statsPoller.deactivate();
261 statsPoller = null;
Carmelo Cascone4b616312019-04-17 14:15:45 -0700262 // Shutdown periodic checkup task.
263 checkupTask.cancel(false);
264 checkupTask = null;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800265 // Shutdown main and task executor.
266 taskExecutor.cancel();
267 taskExecutor = null;
268 mainExecutor.shutdownNow();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700269 try {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800270 mainExecutor.awaitTermination(5, TimeUnit.SECONDS);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700271 } catch (InterruptedException e) {
272 log.warn("connectionExecutor not terminated properly");
273 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800274 mainExecutor = null;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700275 // Remove all device agent listeners
276 handshakersWithListeners.values().forEach(h -> h.removeDeviceAgentListener(id()));
277 handshakersWithListeners.clear();
278 // Other cleanup.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700279 lastCheckups.clear();
Andrea Campanella4929a812017-10-09 18:38:23 +0200280 componentConfigService.unregisterProperties(getClass(), false);
Andrea Campanella241896c2017-05-10 13:11:04 -0700281 cfgService.removeListener(cfgListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700282 pipeconfWatchdogService.removeListener(pipeconfWatchdogListener);
Andrea Campanella241896c2017-05-10 13:11:04 -0700283 providerRegistry.unregister(this);
284 providerService = null;
Yi Tsenge616d752018-11-27 10:53:27 -0800285 gnmiDeviceStateSubscriber.deactivate();
286 gnmiDeviceStateSubscriber = null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700287 log.info("Stopped");
288 }
289
Andrea Campanella241896c2017-05-10 13:11:04 -0700290 @Override
291 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800292 checkNotNull(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700293 submitTask(deviceId, TaskType.CHECKUP);
Andrea Campanella241896c2017-05-10 13:11:04 -0700294 }
295
296 @Override
297 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800298 final MastershipInfo mastershipInfo = mastershipService.getMastershipFor(deviceId);
299 final NodeId localNodeId = clusterService.getLocalNode().id();
300
301 if (!mastershipInfo.getRole(localNodeId).equals(newRole)) {
302 log.warn("Inconsistent mastership info for {}! Requested {}, but " +
303 "mastership service reports {}, will apply the latter...",
304 deviceId, newRole, mastershipInfo.getRole(localNodeId));
305 newRole = mastershipInfo.getRole(localNodeId);
306 }
307
Carmelo Cascone4b616312019-04-17 14:15:45 -0700308 final DeviceHandshaker handshaker = getBehaviour(
309 deviceId, DeviceHandshaker.class);
310 if (handshaker == null) {
311 log.error("Null handshaker. Unable to notify role {} to {}",
312 newRole, deviceId);
313 return;
314 }
315
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800316 // Derive preference value.
317 final int preference;
318 switch (newRole) {
319 case MASTER:
320 preference = 0;
321 break;
322 case STANDBY:
323 preference = mastershipInfo.backups().indexOf(localNodeId) + 1;
324 if (preference == 0) {
325 // Not found in list.
326 log.error("Unable to derive mastership preference for {}, " +
327 "requested role {} but local node ID was " +
328 "not found among list of backup nodes " +
Carmelo Cascone4b616312019-04-17 14:15:45 -0700329 "reported by mastership service",
330 deviceId, newRole);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800331 return;
332 }
333 break;
334 case NONE:
335 // No preference for NONE, apply as is.
pierventref260c912022-02-28 20:34:42 -0800336 Pair<MastershipRole, Integer> pairRolePref = Pair.of(newRole, -1);
337 if (log.isDebugEnabled()) {
338 log.debug("Notifying role {} to {}", newRole, deviceId);
339 } else if (!pairRolePref.equals(lastRoleRequest.get(deviceId))) {
340 log.info("Notifying role {} to {}", newRole, deviceId);
341 }
342 lastRoleRequest.put(deviceId, pairRolePref);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700343 handshaker.roleChanged(newRole);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800344 return;
345 default:
346 log.error("Unrecognized mastership role {}", newRole);
347 return;
348 }
349
pierventref260c912022-02-28 20:34:42 -0800350 Pair<MastershipRole, Integer> pairRolePref = Pair.of(newRole, preference);
351 if (log.isDebugEnabled()) {
352 log.debug("Notifying role {} (preference {}) for term {} to {}",
353 newRole, preference, mastershipInfo.term(), deviceId);
354 } else if (!pairRolePref.equals(lastRoleRequest.get(deviceId))) {
355 log.info("Notifying role {} (preference {}) for term {} to {}",
356 newRole, preference, mastershipInfo.term(), deviceId);
357 }
358 lastRoleRequest.put(deviceId, pairRolePref);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800359
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800360 try {
361 handshaker.roleChanged(preference, mastershipInfo.term());
362 } catch (UnsupportedOperationException e) {
363 // Preference-based method not supported.
364 handshaker.roleChanged(newRole);
365 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700366 }
367
368 @Override
369 public boolean isReachable(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700370 final DeviceHandshaker handshaker = getBehaviour(
371 deviceId, DeviceHandshaker.class);
Andrea Campanellac1ecdd02018-01-12 12:48:24 +0100372 if (handshaker == null) {
373 return false;
374 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800375 return handshaker.isReachable();
Andrea Campanella241896c2017-05-10 13:11:04 -0700376 }
377
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800378 @Override
379 public boolean isAvailable(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700380 final DeviceHandshaker handshaker = getBehaviour(
381 deviceId, DeviceHandshaker.class);
382 if (handshaker == null) {
383 return false;
384 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800385 try {
386 // Try without probing the device...
387 return handshaker.isAvailable();
388 } catch (UnsupportedOperationException e) {
389 // Driver does not support that.
390 return probeAvailability(handshaker);
391 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700392 }
393
Andrea Campanella241896c2017-05-10 13:11:04 -0700394 @Override
395 public void changePortState(DeviceId deviceId, PortNumber portNumber,
396 boolean enable) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200397 if (!deviceService.getDevice(deviceId).is(PortAdmin.class)) {
398 log.warn("Missing PortAdmin behaviour on {}, aborting port state change",
399 deviceId);
400 return;
Andrea Campanella241896c2017-05-10 13:11:04 -0700401 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200402 final PortAdmin portAdmin = deviceService.getDevice(deviceId)
403 .as(PortAdmin.class);
404 final CompletableFuture<Boolean> modifyTask = enable
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200405 ? portAdmin.enable(portNumber)
406 : portAdmin.disable(portNumber);
Carmelo Cascone61469462019-03-05 23:59:11 -0800407 final String descr = format("%s port %s on %s",
408 (enable ? "enable" : "disable"),
409 portNumber, deviceId);
410 modifyTask.whenComplete((success, ex) -> {
411 if (ex != null) {
412 log.error("Exception while trying to " + descr, ex);
413 } else if (!success) {
414 log.warn("Unable to " + descr);
415 }
416 });
Andrea Campanella241896c2017-05-10 13:11:04 -0700417 }
418
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700419 @Override
420 public void triggerDisconnect(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800421 checkNotNull(deviceId);
422 log.info("Triggering disconnection of device {}", deviceId);
423 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
424 }
425
pierventred7cae132022-02-03 21:34:04 +0100426 @Override
427 public CompletableFuture<Boolean> probeReachability(DeviceId deviceId) {
428 final DeviceHandshaker handshaker = getBehaviour(
429 deviceId, DeviceHandshaker.class);
430 if (handshaker == null) {
431 return CompletableFuture.completedFuture(false);
432 }
433 return handshaker.probeReachability();
434 }
435
pierventreeed482b2022-02-18 05:16:48 -0800436 @Override
437 public int gracePeriod() {
438 return GRACE_PERIOD;
439 }
440
pierventred7cae132022-02-03 21:34:04 +0100441 private boolean probeReachabilitySync(DeviceId deviceId) {
pierventreeed482b2022-02-18 05:16:48 -0800442 // Wait 3/4 of the checkup interval and make sure the thread
443 // is not blocked more than the checkUpInterval
pierventred7cae132022-02-03 21:34:04 +0100444 return Tools.futureGetOrElse(probeReachability(deviceId), (checkupInterval * 3000 / 4),
445 TimeUnit.MILLISECONDS, Boolean.FALSE);
446 }
447
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800448 /**
449 * Listener for configuration events.
450 */
451 private class InternalNetworkConfigListener implements NetworkConfigListener {
452 @Override
453 public void event(NetworkConfigEvent event) {
454 DeviceId deviceId = (DeviceId) event.subject();
455 switch (event.type()) {
456 case CONFIG_ADDED:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700457 if (configIsPresent(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800458 submitTask(deviceId, TaskType.CONNECTION_SETUP);
459 }
460 break;
461 case CONFIG_UPDATED:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700462 if (configIsPresent(deviceId) && mgmtAddrUpdated(event)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800463 submitTask(deviceId, TaskType.CONNECTION_UPDATE);
464 }
465 break;
466 case CONFIG_REMOVED:
467 if (event.configClass().equals(BasicDeviceConfig.class)) {
468 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
469 }
470 break;
471 default:
472 // Ignore
473 break;
474 }
475 }
476
477 private boolean mgmtAddrUpdated(NetworkConfigEvent event) {
478 if (!event.prevConfig().isPresent() || !event.config().isPresent()) {
479 return false;
480 }
481 final BasicDeviceConfig prev = (BasicDeviceConfig) event.prevConfig().get();
482 final BasicDeviceConfig current = (BasicDeviceConfig) event.config().get();
483 return !Objects.equals(prev.managementAddress(), current.managementAddress());
484 }
485
486 @Override
487 public boolean isRelevant(NetworkConfigEvent event) {
488 return event.configClass().equals(BasicDeviceConfig.class) &&
489 (event.subject() instanceof DeviceId) &&
490 myScheme((DeviceId) event.subject());
491 }
492 }
493
494 /**
495 * Listener for device agent events.
496 */
497 private class InternalDeviceAgentListener implements DeviceAgentListener {
498 @Override
499 public void event(DeviceAgentEvent event) {
500 DeviceId deviceId = event.subject();
501 switch (event.type()) {
502 case CHANNEL_OPEN:
503 submitTask(deviceId, TaskType.CHANNEL_OPEN);
504 break;
505 case CHANNEL_CLOSED:
506 case CHANNEL_ERROR:
507 submitTask(deviceId, TaskType.CHANNEL_CLOSED);
508 break;
509 case ROLE_MASTER:
510 submitTask(deviceId, TaskType.ROLE_MASTER);
511 break;
512 case ROLE_STANDBY:
513 submitTask(deviceId, TaskType.ROLE_STANDBY);
514 break;
515 case ROLE_NONE:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700516 // FIXME: in case of device disconnection, agents will
517 // signal role NONE, preventing the DeviceManager to mark
518 // the device as offline, as only the master can do that. We
519 // should change the DeviceManager. For now, we disable
520 // signaling role NONE.
521 // submitTask(deviceId, TaskType.ROLE_NONE);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800522 break;
523 case NOT_MASTER:
524 submitTask(deviceId, TaskType.NOT_MASTER);
525 break;
526 default:
527 log.warn("Unrecognized device agent event {}", event.type());
528 }
529 }
530 }
531
532 /**
533 * Pipeline event listener.
534 */
535 private class InternalPipeconfWatchdogListener implements PiPipeconfWatchdogListener {
536 @Override
537 public void event(PiPipeconfWatchdogEvent event) {
538 final DeviceId deviceId = event.subject();
539 switch (event.type()) {
540 case PIPELINE_READY:
541 submitTask(deviceId, TaskType.PIPELINE_READY);
542 break;
543 case PIPELINE_UNKNOWN:
544 submitTask(deviceId, TaskType.PIPELINE_NOT_READY);
545 break;
546 default:
547 break;
548 }
549 }
550
551 @Override
552 public boolean isRelevant(PiPipeconfWatchdogEvent event) {
553 return myScheme(event.subject());
554 }
555 }
556
Carmelo Cascone4b616312019-04-17 14:15:45 -0700557 private void startOrReschedulePeriodicCheckupTasks() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800558 synchronized (this) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700559 if (checkupTask != null) {
560 checkupTask.cancel(false);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800561 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700562 checkupTask = SharedScheduledExecutors.getPoolThreadExecutor()
563 .scheduleAtFixedRate(
564 this::submitCheckupTasksForAllDevices,
565 1,
566 checkupInterval,
pierventreb4120ff2021-04-09 14:32:11 +0200567 TimeUnit.SECONDS,
568 true);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800569 }
570 }
571
Carmelo Cascone4b616312019-04-17 14:15:45 -0700572 private void submitCheckupTasksForAllDevices() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800573 // Async trigger a task for all devices in the cfg.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700574 log.debug("Submitting checkup task for all devices...");
Carmelo Cascone1dfc7862019-04-17 16:37:44 -0700575 final Set<DeviceId> deviceToCheck = Sets.newHashSet();
576 // All devices in the core and in the config that we care about.
577 deviceService.getDevices().forEach(d -> {
578 if (myScheme(d.id())) {
579 deviceToCheck.add(d.id());
580 }
581 });
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800582 cfgService.getSubjects(DeviceId.class).stream()
583 .filter(GeneralDeviceProvider::myScheme)
Carmelo Cascone1dfc7862019-04-17 16:37:44 -0700584 .filter(this::configIsPresent)
585 .forEach(deviceToCheck::add);
586 deviceToCheck.forEach(d -> submitTask(d, TaskType.CHECKUP));
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800587 }
588
589 /**
590 * Type of tasks performed by this provider.
591 */
592 enum TaskType {
593 CONNECTION_SETUP,
594 CONNECTION_UPDATE,
595 CONNECTION_TEARDOWN,
596 PIPELINE_READY,
597 CHANNEL_OPEN,
598 CHANNEL_CLOSED,
599 PIPELINE_NOT_READY,
Carmelo Cascone4b616312019-04-17 14:15:45 -0700600 CHECKUP,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800601 ROLE_MASTER,
602 ROLE_NONE,
603 ROLE_STANDBY,
604 NOT_MASTER,
605 }
606
pierventre5a5c8aa2022-02-28 13:37:08 -0800607 private static final Set<TaskType> GDP_ALLOWLIST = Sets.newHashSet(TaskType.ROLE_MASTER, TaskType.ROLE_NONE,
608 TaskType.ROLE_STANDBY, TaskType.NOT_MASTER);
609
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800610 private void submitTask(DeviceId deviceId, TaskType taskType) {
611 taskExecutor.submit(deviceId, taskType, taskRunnable(deviceId, taskType));
612 }
613
614 private Runnable taskRunnable(DeviceId deviceId, TaskType taskType) {
615 switch (taskType) {
616 case CONNECTION_SETUP:
617 return () -> handleConnectionSetup(deviceId);
618 case CONNECTION_UPDATE:
619 return () -> handleConnectionUpdate(deviceId);
620 case CONNECTION_TEARDOWN:
621 return () -> handleConnectionTeardown(deviceId);
622 case CHANNEL_OPEN:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700623 case CHECKUP:
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800624 case PIPELINE_READY:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700625 return () -> doCheckupAndRepair(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700626 case CHANNEL_CLOSED:
627 case PIPELINE_NOT_READY:
628 return () -> markOfflineIfNeeded(deviceId);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800629 case ROLE_MASTER:
630 return () -> handleMastershipResponse(deviceId, MastershipRole.MASTER);
631 case ROLE_STANDBY:
632 return () -> handleMastershipResponse(deviceId, MastershipRole.STANDBY);
633 case ROLE_NONE:
634 return () -> handleMastershipResponse(deviceId, MastershipRole.NONE);
635 case NOT_MASTER:
636 return () -> handleNotMaster(deviceId);
637 default:
638 throw new IllegalArgumentException("Unrecognized task type " + taskType);
639 }
640 }
641
642 private void handleConnectionSetup(DeviceId deviceId) {
643 assertConfig(deviceId);
644 // Bind pipeconf (if any and if device is capable).
645 bindPipeconfIfRequired(deviceId);
646 // Get handshaker.
647 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700648 if (handshaker.hasConnection() || handshakersWithListeners.containsKey(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800649 throw new DeviceTaskException("connection already exists");
650 }
651 // Add device agent listener.
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800652 handshakersWithListeners.put(deviceId, handshaker);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700653 handshaker.addDeviceAgentListener(id(), deviceAgentListener);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800654 // Start connection via handshaker.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700655 if (!handshaker.connect()) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800656 // Failed! Remove listeners.
657 handshaker.removeDeviceAgentListener(id());
658 handshakersWithListeners.remove(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700659 // Clean up connection state leftovers.
660 handshaker.disconnect();
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800661 throw new DeviceTaskException("connection failed");
662 }
663 createOrUpdateDevice(deviceId, false);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800664 // From here we expect a CHANNEL_OPEN event to update availability.
665 }
666
667 private void handleConnectionUpdate(DeviceId deviceId) {
668 assertConfig(deviceId);
669 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700670 if (!handshaker.hasConnection()) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800671 // If driver reports that a connection still exists, perhaps the
672 // part of the netcfg that changed does not affect the connection.
673 // Otherwise, remove any previous connection state from the old
674 // netcfg and create a new one.
675 log.warn("Detected change of connection endpoints for {}, will " +
676 "tear down existing connection and set up a new one...",
677 deviceId);
678 handleConnectionTeardown(deviceId);
679 handleConnectionSetup(deviceId);
680 }
681 }
682
683 private void createOrUpdateDevice(DeviceId deviceId, boolean available) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800684 assertConfig(deviceId);
pierventreb4120ff2021-04-09 14:32:11 +0200685
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700686 if (available) {
Carmelo Casconed51a5552019-04-13 01:22:25 -0700687 // Push port descriptions. If marking online, make sure to update
688 // ports before other subsystems pick up the device event.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700689 final List<PortDescription> ports = getPortDetails(deviceId);
690 providerService.updatePorts(deviceId, ports);
691 }
pierventreb4120ff2021-04-09 14:32:11 +0200692
pierventree1f80102021-10-01 22:01:22 +0200693 DeviceDescription deviceDescription = getDeviceDescription(deviceId, available);
694 DeviceDescription storeDescription = providerService.getDeviceDescription(deviceId);
695 if (deviceService.getDevice(deviceId) != null &&
696 deviceService.isAvailable(deviceId) == available &&
697 storeDescription != null) {
pierventreeed482b2022-02-18 05:16:48 -0800698 // FIXME SDFAB-650 rethink the APIs and abstractions around the DeviceStore.
699 // Device registration is a two-step process for the GDP. Initially, the device is
700 // registered with default avail. to false. Later, the checkup task will update the
701 // description with the default avail to true in order to mark it available. Today,
702 // there is only one API to mark online a device from the device provider which is
703 // deviceConnected which assumes an update on the device description. The device provider
704 // is the only one able to update the device description and we have to make sure that
705 // the default avail. is flipped to true as it is used to mark as online the device when
706 // it is created or updated. Otherwise, if an ONOS instance fails and restarts, when re-joining
707 // the cluster, it will get the device marked as offline and will not be able to update
708 // its status until it become the master. This process concurs with the markOnline done
709 // by the background thread in the DeviceManager and its the reason why we cannot just check
710 // the device availability but we need to compare also the desc. Checking here the equality,
711 // as in general we may want to upgrade the device description at run time.
pierventree1f80102021-10-01 22:01:22 +0200712 DeviceDescription testDeviceDescription = DefaultDeviceDescription.copyReplacingAnnotation(
713 deviceDescription, storeDescription.annotations());
714 if (testDeviceDescription.equals(storeDescription)) {
715 return;
716 }
pierventreb4120ff2021-04-09 14:32:11 +0200717 }
pierventree1f80102021-10-01 22:01:22 +0200718
719 providerService.deviceConnected(deviceId, deviceDescription);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800720 }
721
722 private boolean probeAvailability(DeviceHandshaker handshaker) {
Carmelo Cascone61469462019-03-05 23:59:11 -0800723 return Futures.getUnchecked(handshaker.probeAvailability());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800724 }
725
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800726 private void markOfflineIfNeeded(DeviceId deviceId) {
727 assertDeviceRegistered(deviceId);
728 if (deviceService.isAvailable(deviceId)) {
729 providerService.deviceDisconnected(deviceId);
730 }
731 }
732
Carmelo Cascone4b616312019-04-17 14:15:45 -0700733 private void doCheckupAndRepair(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800734
Carmelo Cascone4b616312019-04-17 14:15:45 -0700735 // This task should be invoked periodically for each device known by
736 // this provider, or as a consequence of events signaling potential
737 // availability changes of the device. We check that everything is in
738 // order, repair what's wrong, and eventually mark the the device as
739 // available (or not) in the core.
740
741 if (!configIsPresent(deviceId)) {
742 // We should have a connection only for devices in the config.
743 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
744 return;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800745 }
746
Carmelo Cascone4b616312019-04-17 14:15:45 -0700747 final DeviceHandshaker handshaker = handshakersWithListeners.get(deviceId);
748 if (handshaker == null) {
749 // Device in config but we have not initiated a connection.
750 // Perhaps we missed the config event?
751 submitTask(deviceId, TaskType.CONNECTION_SETUP);
752 return;
753 }
754
755 // If here, we have a handshaker meaning we already connected once to
756 // the device...
757 if (!handshaker.hasConnection()) {
758 // ... but now the driver reports there is NOT a connection.
759 // Perhaps the netcfg changed and we didn't pick the event?
760 log.warn("Re-establishing lost connection to {}", deviceId);
761 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
762 submitTask(deviceId, TaskType.CONNECTION_SETUP);
763 return;
764 }
765
766 // If here, device should be registered in the core.
767 assertDeviceRegistered(deviceId);
768
pierventred7cae132022-02-03 21:34:04 +0100769 if (!handshaker.isReachable() || !probeReachabilitySync(deviceId)) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700770 // Device appears to be offline.
771 markOfflineIfNeeded(deviceId);
pierventred7cae132022-02-03 21:34:04 +0100772 // We expect the protocol layer to implement some sort of
Carmelo Cascone4b616312019-04-17 14:15:45 -0700773 // connection backoff mechanism and to signal availability via
pierventred7cae132022-02-03 21:34:04 +0100774 // CHANNEL_OPEN events.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700775 return;
776 }
777
778 // If here, device is reachable. Now do mastership and availability
779 // checkups. To avoid overload of checkup tasks which might involve
780 // sending messages over the network and triggering mastership
781 // elections. We require a minimum interval of 1/3 of the configured
782 // checkupInterval between consecutive checkup tasks when the device is
783 // known to be available.
784
785 final Long lastCheckup = lastCheckups.get(deviceId);
786 final boolean isAvailable = deviceService.isAvailable(deviceId);
787 if (isAvailable && lastCheckup != null &&
788 (currentTimeMillis() - lastCheckup) < (checkupInterval * 1000 / 3)) {
789 if (log.isDebugEnabled()) {
790 log.debug("Dropping checkup task for {} as it happened recently",
791 deviceId);
792 }
793 return;
794 }
795 lastCheckups.put(deviceId, currentTimeMillis());
796
797 // Make sure device has a valid mastership role.
798 final MastershipRole expectedRole = mastershipService.getLocalRole(deviceId);
799 if (expectedRole == MastershipRole.NONE) {
800 log.debug("Detected invalid role ({}) for {}, waiting for mastership " +
801 "service to fix this...",
802 expectedRole, deviceId);
803 // Gentle nudge to fix things...
804 mastershipService.requestRoleForSync(deviceId);
805 return;
806 }
807
808 final MastershipRole deviceRole = handshaker.getRole();
pierventred510b7d2022-02-03 21:56:37 +0100809 // FIXME: we should be checking the mastership term as well.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700810 if (expectedRole != deviceRole) {
pierventred510b7d2022-02-03 21:56:37 +0100811 // Let's be greedy, if the role is NONE likely is due to the lazy channel
812 if (deviceRole == MastershipRole.NONE) {
813 log.warn("Detected role mismatch for {}, core expects {}, " +
814 "but device reports {}, reassert the role... ",
815 deviceId, expectedRole, deviceRole);
pierventreeed482b2022-02-18 05:16:48 -0800816 // If we are experiencing a severe issue, eventually
817 // the DeviceManager will move the mastership
pierventred510b7d2022-02-03 21:56:37 +0100818 roleChanged(deviceId, expectedRole);
819 } else {
820 log.debug("Detected role mismatch for {}, core expects {}, " +
821 "but device reports {}, waiting for mastership " +
822 "service to fix this...",
823 deviceId, expectedRole, deviceRole);
824 // Gentle nudge to fix things...
825 providerService.receivedRoleReply(deviceId, deviceRole);
826 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700827 return;
828 }
829
830 // Check and update availability, which differently from reachability
831 // describes the ability of the device to forward packets.
832 if (probeAvailability(handshaker)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800833 // Device ready to do its job.
834 createOrUpdateDevice(deviceId, true);
835 } else {
836 markOfflineIfNeeded(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700837 if (isPipelineProgrammable(deviceId)) {
838 // If reachable, but not available, and pipeline programmable,
839 // there is a high chance it's because the pipeline is not READY
840 // (independently from what the pipeconf watchdog reports, as
841 // the status there might be outdated). Encourage pipeconf
842 // watchdog to perform a pipeline probe ASAP.
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800843 pipeconfWatchdogService.triggerProbe(deviceId);
844 }
845 }
846 }
847
848 private void handleMastershipResponse(DeviceId deviceId, MastershipRole response) {
849 assertDeviceRegistered(deviceId);
850 log.debug("Device {} asserted role {}", deviceId, response);
851 providerService.receivedRoleReply(deviceId, response);
852 }
853
854 private void handleNotMaster(DeviceId deviceId) {
855 assertDeviceRegistered(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700856 handleMastershipResponse(deviceId, handshakerOrFail(deviceId).getRole());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800857 }
858
859 private void assertDeviceRegistered(DeviceId deviceId) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700860 if (!deviceIsRegistered(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800861 throw new DeviceTaskException("device not registered in the core");
862 }
863 }
864
Carmelo Cascone4b616312019-04-17 14:15:45 -0700865 private boolean deviceIsRegistered(DeviceId deviceId) {
866 return deviceService.getDevice(deviceId) != null;
867 }
868
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800869 private void handleConnectionTeardown(DeviceId deviceId) {
870 if (deviceService.getDevice(deviceId) != null
871 && deviceService.isAvailable(deviceId)) {
872 providerService.deviceDisconnected(deviceId);
873 }
874 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
875 handshaker.removeDeviceAgentListener(id());
876 handshakersWithListeners.remove(deviceId);
877 handshaker.disconnect();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700878 lastCheckups.remove(deviceId);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800879 }
880
881 private void bindPipeconfIfRequired(DeviceId deviceId) {
882 if (pipeconfService.getPipeconf(deviceId).isPresent()
883 || !isPipelineProgrammable(deviceId)) {
884 // Nothing to do.
885 // Device has already a pipeconf or is not programmable.
886 return;
887 }
888 // Get pipeconf from netcfg or driver (default one).
889 final PiPipelineProgrammable pipelineProg = getBehaviour(
890 deviceId, PiPipelineProgrammable.class);
891 final PiPipeconfId pipeconfId = getPipeconfId(deviceId, pipelineProg);
892 if (pipeconfId == null) {
893 throw new DeviceTaskException("unable to find pipeconf");
894 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700895 if (!pipeconfService.getPipeconf(pipeconfId).isPresent()) {
896 throw new DeviceTaskException(format(
897 "pipeconf %s not registered", pipeconfId));
898 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800899 // Store binding in pipeconf service.
900 pipeconfService.bindToDevice(pipeconfId, deviceId);
901 }
902
903 private PiPipeconfId getPipeconfId(DeviceId deviceId, PiPipelineProgrammable pipelineProg) {
904 // Places to look for a pipeconf ID (in priority order)):
905 // 1) netcfg
906 // 2) device driver (default one)
907 final PiPipeconfId pipeconfId = getPipeconfFromCfg(deviceId);
908 if (pipeconfId != null && !pipeconfId.id().isEmpty()) {
909 return pipeconfId;
910 }
911 if (pipelineProg != null
912 && pipelineProg.getDefaultPipeconf().isPresent()) {
913 final PiPipeconf defaultPipeconf = pipelineProg.getDefaultPipeconf().get();
914 log.info("Using default pipeconf {} for {}", defaultPipeconf.id(), deviceId);
915 return defaultPipeconf.id();
916 }
917 return null;
918 }
919
920 private PiPipeconfId getPipeconfFromCfg(DeviceId deviceId) {
921 BasicDeviceConfig config = cfgService.getConfig(
922 deviceId, BasicDeviceConfig.class);
923 if (config == null) {
924 return null;
925 }
926 return config.pipeconf() != null
927 ? new PiPipeconfId(config.pipeconf()) : null;
928 }
929
930 private DeviceHandshaker handshakerOrFail(DeviceId deviceId) {
931 final DeviceHandshaker handshaker = getBehaviour(
932 deviceId, DeviceHandshaker.class);
933 if (handshaker == null) {
934 throw new DeviceTaskException("missing handshaker behavior");
935 }
936 return handshaker;
937 }
938
Carmelo Cascone4b616312019-04-17 14:15:45 -0700939 private boolean configIsPresent(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800940 final BasicDeviceConfig basicDeviceCfg = cfgService.getConfig(
941 deviceId, BasicDeviceConfig.class);
942 return basicDeviceCfg != null && !isNullOrEmpty(basicDeviceCfg.driver());
943 }
944
945 private void assertConfig(DeviceId deviceId) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700946 if (!configIsPresent(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800947 throw new DeviceTaskException("configuration is not complete");
948 }
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700949 }
950
Andrea Campanella241896c2017-05-10 13:11:04 -0700951 private Driver getDriver(DeviceId deviceId) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700952 try {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200953 // DriverManager checks first using basic device config.
954 return driverService.getDriver(deviceId);
Andrea Campanella241896c2017-05-10 13:11:04 -0700955 } catch (ItemNotFoundException e) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200956 log.error("Driver not found for {}", deviceId);
957 return null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700958 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700959 }
960
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700961 private <T extends Behaviour> T getBehaviour(DeviceId deviceId, Class<T> type) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700962 Driver driver = getDriver(deviceId);
963 if (driver == null) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700964 return null;
965 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700966 if (!driver.hasBehaviour(type)) {
967 return null;
968 }
969 final DriverData data = new DefaultDriverData(driver, deviceId);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700970 final DefaultDriverHandler handler = new DefaultDriverHandler(data);
971 return driver.createBehaviour(handler, type);
Andrea Campanella241896c2017-05-10 13:11:04 -0700972 }
973
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800974 private boolean hasBehaviour(DeviceId deviceId, Class<? extends Behaviour> type) {
975 Driver driver = getDriver(deviceId);
976 if (driver == null) {
977 return false;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200978 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800979 return driver.hasBehaviour(type);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700980 }
981
982 private DeviceDescription getDeviceDescription(
983 DeviceId deviceId, boolean defaultAvailable) {
984 // Get one from driver or forge.
985 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
986 deviceId, DeviceDescriptionDiscovery.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700987 if (deviceDiscovery == null) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700988 return forgeDeviceDescription(deviceId, defaultAvailable);
989 }
Yi Tsengd7716482018-10-31 15:34:30 -0700990
991 final DeviceDescription d = deviceDiscovery.discoverDeviceDetails();
992 if (d == null) {
993 return forgeDeviceDescription(deviceId, defaultAvailable);
994 }
995 // Enforce defaultAvailable flag over the one obtained from driver.
996 return new DefaultDeviceDescription(d, defaultAvailable, d.annotations());
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700997 }
998
999 private List<PortDescription> getPortDetails(DeviceId deviceId) {
1000 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
1001 deviceId, DeviceDescriptionDiscovery.class);
1002 if (deviceDiscovery != null) {
1003 return deviceDiscovery.discoverPortDetails();
1004 } else {
1005 return Collections.emptyList();
1006 }
1007 }
1008
1009 private DeviceDescription forgeDeviceDescription(
1010 DeviceId deviceId, boolean defaultAvailable) {
1011 // Uses handshaker and provider config to get driver data.
1012 final DeviceHandshaker handshaker = getBehaviour(
1013 deviceId, DeviceHandshaker.class);
1014 final Driver driver = handshaker != null
1015 ? handshaker.handler().driver() : null;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001016 return new DefaultDeviceDescription(
1017 deviceId.uri(),
1018 Device.Type.SWITCH,
1019 driver != null ? driver.manufacturer() : UNKNOWN,
1020 driver != null ? driver.hwVersion() : UNKNOWN,
1021 driver != null ? driver.swVersion() : UNKNOWN,
1022 UNKNOWN,
1023 new ChassisId(),
1024 defaultAvailable,
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001025 DefaultAnnotations.EMPTY);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001026 }
1027
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001028 static boolean myScheme(DeviceId deviceId) {
1029 return deviceId.uri().getScheme().equals(URI_SCHEME);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +02001030 }
1031
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001032 private boolean isPipelineProgrammable(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001033 return hasBehaviour(deviceId, PiPipelineProgrammable.class);
Carmelo Casconede3b6842018-09-05 17:45:10 -07001034 }
Thomas Vachuska1b3cf362019-08-29 17:11:18 -07001035
1036 private class InternalDeviceListener implements DeviceListener {
1037 @Override
1038 public void event(DeviceEvent event) {
1039 log.info("Triggering disconnect for device {}", event.subject().id());
1040 triggerDisconnect(event.subject().id());
1041 }
1042
1043 @Override
1044 public boolean isRelevant(DeviceEvent event) {
Thomas Vachuska24c0ca42019-08-29 18:19:16 -07001045 return DeviceEvent.Type.DEVICE_REMOVED == event.type() && myScheme(event.subject().id());
Thomas Vachuska1b3cf362019-08-29 17:11:18 -07001046 }
1047 }
Andrea Campanella241896c2017-05-10 13:11:04 -07001048}