blob: 4c3018a1b73e39825cd80e86a180e3afa35ffc3d [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;
pierventrec19294d2022-02-02 22:33:41 +010030import org.onosproject.drivers.gnmi.OpenConfigGnmiDeviceDescriptionDiscovery;
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;
pierventrec19294d2022-02-02 22:33:41 +0100101import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.READ_PORT_ID;
102import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.READ_PORT_ID_DEFAULT;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800103import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_INTERVAL;
104import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_INTERVAL_DEFAULT;
Andrea Campanella241896c2017-05-10 13:11:04 -0700105import static org.slf4j.LoggerFactory.getLogger;
106
107/**
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800108 * Provider which uses drivers to discover devices, perform initial handshake,
109 * and notify the core of disconnection events. The implementation listens for
110 * events from netcfg or the drivers (via {@link DeviceAgentListener}) andP
111 * schedules task for each event.
Andrea Campanella241896c2017-05-10 13:11:04 -0700112 */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700113@Component(immediate = true,
114 property = {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700115 CHECKUP_INTERVAL + ":Integer=" + CHECKUP_INTERVAL_DEFAULT,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800116 STATS_POLL_INTERVAL + ":Integer=" + STATS_POLL_INTERVAL_DEFAULT,
pierventrec19294d2022-02-02 22:33:41 +0100117 READ_PORT_ID + ":Boolean=" + READ_PORT_ID_DEFAULT,
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700118 })
Andrea Campanella241896c2017-05-10 13:11:04 -0700119public class GeneralDeviceProvider extends AbstractProvider
120 implements DeviceProvider {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200121
Andrea Campanella241896c2017-05-10 13:11:04 -0700122 private final Logger log = getLogger(getClass());
123
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800124 private static final String APP_NAME = "org.onosproject.generaldeviceprovider";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700125 private static final String URI_SCHEME = "device";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700126 private static final String DEVICE_PROVIDER_PACKAGE =
127 "org.onosproject.general.provider.device";
128 private static final int CORE_POOL_SIZE = 10;
129 private static final String UNKNOWN = "unknown";
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700130
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700131 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200132 private DeviceProviderRegistry providerRegistry;
Andrea Campanella241896c2017-05-10 13:11:04 -0700133
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700134 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200135 private ComponentConfigService componentConfigService;
Andrea Campanella4929a812017-10-09 18:38:23 +0200136
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700137 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200138 private NetworkConfigRegistry cfgService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700139
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700140 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200141 private CoreService coreService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700142
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700143 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700144 private DeviceAdminService deviceService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700145
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700146 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200147 private DriverService driverService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700148
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700149 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200150 private MastershipService mastershipService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200151
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700152 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800153 private ClusterService clusterService;
154
155 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700156 private PiPipeconfService pipeconfService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700157
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700158 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700159 private PiPipeconfWatchdogService pipeconfWatchdogService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200160
Yi Tsenge616d752018-11-27 10:53:27 -0800161 // FIXME: no longer general if we add a dependency to a protocol-specific
162 // service. Possible solutions are: rename this provider to
163 // StratumDeviceProvider, find a way to allow this provider to register for
164 // protocol specific events (e.g. port events) via drivers (similar to
165 // DeviceAgentListener).
166 @Reference(cardinality = ReferenceCardinality.MANDATORY)
167 private GnmiController gnmiController;
168
169 private GnmiDeviceStateSubscriber gnmiDeviceStateSubscriber;
170
Carmelo Cascone61469462019-03-05 23:59:11 -0800171 /**
172 * Configure interval for checking device availability; default is 10 sec.
173 */
Carmelo Cascone4b616312019-04-17 14:15:45 -0700174 private int checkupInterval = CHECKUP_INTERVAL_DEFAULT;
Andrea Campanella19090322017-08-22 10:31:37 +0200175
Carmelo Cascone61469462019-03-05 23:59:11 -0800176 /**
177 * Configure poll frequency for port status and stats; default is 10 sec.
178 */
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800179 private int statsPollInterval = STATS_POLL_INTERVAL_DEFAULT;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200180
pierventrec19294d2022-02-02 22:33:41 +0100181 /**
182 * Configure read port-id for gnmi drivers; default is false.
183 */
184 private boolean readPortId = READ_PORT_ID_DEFAULT;
185
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700186 private final Map<DeviceId, DeviceHandshaker> handshakersWithListeners = Maps.newConcurrentMap();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700187 private final Map<DeviceId, Long> lastCheckups = Maps.newConcurrentMap();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700188 private final InternalPipeconfWatchdogListener pipeconfWatchdogListener = new InternalPipeconfWatchdogListener();
Carmelo Casconee5b28722018-06-22 17:28:28 +0200189 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
190 private final DeviceAgentListener deviceAgentListener = new InternalDeviceAgentListener();
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700191 private final DeviceListener deviceListener = new InternalDeviceListener();
Andrea Campanella241896c2017-05-10 13:11:04 -0700192
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800193 private ExecutorService mainExecutor;
194 private DeviceTaskExecutor<TaskType> taskExecutor;
Carmelo Cascone4b616312019-04-17 14:15:45 -0700195 private ScheduledFuture<?> checkupTask;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800196 private StatsPoller statsPoller;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700197 private DeviceProviderService providerService;
198
199 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));
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800211 taskExecutor = new DeviceTaskExecutor<>(mainExecutor);
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);
pierventrec19294d2022-02-02 22:33:41 +0100245 final boolean oldReaPortId = readPortId;
246 String strReadPortId = Tools.get(properties, READ_PORT_ID);
247 readPortId = Boolean.parseBoolean(strReadPortId);
248 log.info("Configured. {} is configured to {}",
249 READ_PORT_ID, readPortId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200250
Carmelo Cascone4b616312019-04-17 14:15:45 -0700251 if (oldCheckupInterval != checkupInterval) {
252 startOrReschedulePeriodicCheckupTasks();
Andrea Campanella19090322017-08-22 10:31:37 +0200253 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200254
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800255 if (oldStatsPollFrequency != statsPollInterval) {
256 statsPoller.reschedule(statsPollInterval);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200257 }
pierventrec19294d2022-02-02 22:33:41 +0100258
259 if (oldReaPortId != readPortId) {
260 // FIXME temporary solution will be removed when the
261 // transition to p4rt translation is completed
262 OpenConfigGnmiDeviceDescriptionDiscovery.readPortId = readPortId;
263 }
Andrea Campanella19090322017-08-22 10:31:37 +0200264 }
265
Andrea Campanella241896c2017-05-10 13:11:04 -0700266 @Deactivate
267 public void deactivate() {
Thomas Vachuska1b3cf362019-08-29 17:11:18 -0700268 deviceService.removeListener(deviceListener);
269
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800270 // Shutdown stats poller.
271 statsPoller.deactivate();
272 statsPoller = null;
Carmelo Cascone4b616312019-04-17 14:15:45 -0700273 // Shutdown periodic checkup task.
274 checkupTask.cancel(false);
275 checkupTask = null;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800276 // Shutdown main and task executor.
277 taskExecutor.cancel();
278 taskExecutor = null;
279 mainExecutor.shutdownNow();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700280 try {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800281 mainExecutor.awaitTermination(5, TimeUnit.SECONDS);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700282 } catch (InterruptedException e) {
283 log.warn("connectionExecutor not terminated properly");
284 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800285 mainExecutor = null;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700286 // Remove all device agent listeners
287 handshakersWithListeners.values().forEach(h -> h.removeDeviceAgentListener(id()));
288 handshakersWithListeners.clear();
289 // Other cleanup.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700290 lastCheckups.clear();
Andrea Campanella4929a812017-10-09 18:38:23 +0200291 componentConfigService.unregisterProperties(getClass(), false);
Andrea Campanella241896c2017-05-10 13:11:04 -0700292 cfgService.removeListener(cfgListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700293 pipeconfWatchdogService.removeListener(pipeconfWatchdogListener);
Andrea Campanella241896c2017-05-10 13:11:04 -0700294 providerRegistry.unregister(this);
295 providerService = null;
Yi Tsenge616d752018-11-27 10:53:27 -0800296 gnmiDeviceStateSubscriber.deactivate();
297 gnmiDeviceStateSubscriber = null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700298 log.info("Stopped");
299 }
300
Andrea Campanella241896c2017-05-10 13:11:04 -0700301 @Override
302 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800303 checkNotNull(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700304 submitTask(deviceId, TaskType.CHECKUP);
Andrea Campanella241896c2017-05-10 13:11:04 -0700305 }
306
307 @Override
308 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800309 final MastershipInfo mastershipInfo = mastershipService.getMastershipFor(deviceId);
310 final NodeId localNodeId = clusterService.getLocalNode().id();
311
312 if (!mastershipInfo.getRole(localNodeId).equals(newRole)) {
313 log.warn("Inconsistent mastership info for {}! Requested {}, but " +
314 "mastership service reports {}, will apply the latter...",
315 deviceId, newRole, mastershipInfo.getRole(localNodeId));
316 newRole = mastershipInfo.getRole(localNodeId);
317 }
318
Carmelo Cascone4b616312019-04-17 14:15:45 -0700319 final DeviceHandshaker handshaker = getBehaviour(
320 deviceId, DeviceHandshaker.class);
321 if (handshaker == null) {
322 log.error("Null handshaker. Unable to notify role {} to {}",
323 newRole, deviceId);
324 return;
325 }
326
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800327 // Derive preference value.
328 final int preference;
329 switch (newRole) {
330 case MASTER:
331 preference = 0;
332 break;
333 case STANDBY:
334 preference = mastershipInfo.backups().indexOf(localNodeId) + 1;
335 if (preference == 0) {
336 // Not found in list.
337 log.error("Unable to derive mastership preference for {}, " +
338 "requested role {} but local node ID was " +
339 "not found among list of backup nodes " +
Carmelo Cascone4b616312019-04-17 14:15:45 -0700340 "reported by mastership service",
341 deviceId, newRole);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800342 return;
343 }
344 break;
345 case NONE:
346 // No preference for NONE, apply as is.
347 log.info("Notifying role {} to {}", newRole, deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700348 handshaker.roleChanged(newRole);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800349 return;
350 default:
351 log.error("Unrecognized mastership role {}", newRole);
352 return;
353 }
354
355 log.info("Notifying role {} (preference {}) for term {} to {}",
356 newRole, preference, mastershipInfo.term(), deviceId);
357
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800358 try {
359 handshaker.roleChanged(preference, mastershipInfo.term());
360 } catch (UnsupportedOperationException e) {
361 // Preference-based method not supported.
362 handshaker.roleChanged(newRole);
363 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700364 }
365
366 @Override
367 public boolean isReachable(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700368 final DeviceHandshaker handshaker = getBehaviour(
369 deviceId, DeviceHandshaker.class);
Andrea Campanellac1ecdd02018-01-12 12:48:24 +0100370 if (handshaker == null) {
371 return false;
372 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800373 return handshaker.isReachable();
Andrea Campanella241896c2017-05-10 13:11:04 -0700374 }
375
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800376 @Override
377 public boolean isAvailable(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700378 final DeviceHandshaker handshaker = getBehaviour(
379 deviceId, DeviceHandshaker.class);
380 if (handshaker == null) {
381 return false;
382 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800383 try {
384 // Try without probing the device...
385 return handshaker.isAvailable();
386 } catch (UnsupportedOperationException e) {
387 // Driver does not support that.
388 return probeAvailability(handshaker);
389 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700390 }
391
Andrea Campanella241896c2017-05-10 13:11:04 -0700392 @Override
393 public void changePortState(DeviceId deviceId, PortNumber portNumber,
394 boolean enable) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200395 if (!deviceService.getDevice(deviceId).is(PortAdmin.class)) {
396 log.warn("Missing PortAdmin behaviour on {}, aborting port state change",
397 deviceId);
398 return;
Andrea Campanella241896c2017-05-10 13:11:04 -0700399 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200400 final PortAdmin portAdmin = deviceService.getDevice(deviceId)
401 .as(PortAdmin.class);
402 final CompletableFuture<Boolean> modifyTask = enable
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200403 ? portAdmin.enable(portNumber)
404 : portAdmin.disable(portNumber);
Carmelo Cascone61469462019-03-05 23:59:11 -0800405 final String descr = format("%s port %s on %s",
406 (enable ? "enable" : "disable"),
407 portNumber, deviceId);
408 modifyTask.whenComplete((success, ex) -> {
409 if (ex != null) {
410 log.error("Exception while trying to " + descr, ex);
411 } else if (!success) {
412 log.warn("Unable to " + descr);
413 }
414 });
Andrea Campanella241896c2017-05-10 13:11:04 -0700415 }
416
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700417 @Override
418 public void triggerDisconnect(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800419 checkNotNull(deviceId);
420 log.info("Triggering disconnection of device {}", deviceId);
421 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
422 }
423
pierventred7cae132022-02-03 21:34:04 +0100424 @Override
425 public CompletableFuture<Boolean> probeReachability(DeviceId deviceId) {
426 final DeviceHandshaker handshaker = getBehaviour(
427 deviceId, DeviceHandshaker.class);
428 if (handshaker == null) {
429 return CompletableFuture.completedFuture(false);
430 }
431 return handshaker.probeReachability();
432 }
433
434 private boolean probeReachabilitySync(DeviceId deviceId) {
435 // Wait 3/4 of the checkup interval
436 return Tools.futureGetOrElse(probeReachability(deviceId), (checkupInterval * 3000 / 4),
437 TimeUnit.MILLISECONDS, Boolean.FALSE);
438 }
439
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800440 /**
441 * Listener for configuration events.
442 */
443 private class InternalNetworkConfigListener implements NetworkConfigListener {
444 @Override
445 public void event(NetworkConfigEvent event) {
446 DeviceId deviceId = (DeviceId) event.subject();
447 switch (event.type()) {
448 case CONFIG_ADDED:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700449 if (configIsPresent(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800450 submitTask(deviceId, TaskType.CONNECTION_SETUP);
451 }
452 break;
453 case CONFIG_UPDATED:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700454 if (configIsPresent(deviceId) && mgmtAddrUpdated(event)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800455 submitTask(deviceId, TaskType.CONNECTION_UPDATE);
456 }
457 break;
458 case CONFIG_REMOVED:
459 if (event.configClass().equals(BasicDeviceConfig.class)) {
460 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
461 }
462 break;
463 default:
464 // Ignore
465 break;
466 }
467 }
468
469 private boolean mgmtAddrUpdated(NetworkConfigEvent event) {
470 if (!event.prevConfig().isPresent() || !event.config().isPresent()) {
471 return false;
472 }
473 final BasicDeviceConfig prev = (BasicDeviceConfig) event.prevConfig().get();
474 final BasicDeviceConfig current = (BasicDeviceConfig) event.config().get();
475 return !Objects.equals(prev.managementAddress(), current.managementAddress());
476 }
477
478 @Override
479 public boolean isRelevant(NetworkConfigEvent event) {
480 return event.configClass().equals(BasicDeviceConfig.class) &&
481 (event.subject() instanceof DeviceId) &&
482 myScheme((DeviceId) event.subject());
483 }
484 }
485
486 /**
487 * Listener for device agent events.
488 */
489 private class InternalDeviceAgentListener implements DeviceAgentListener {
490 @Override
491 public void event(DeviceAgentEvent event) {
492 DeviceId deviceId = event.subject();
493 switch (event.type()) {
494 case CHANNEL_OPEN:
495 submitTask(deviceId, TaskType.CHANNEL_OPEN);
496 break;
497 case CHANNEL_CLOSED:
498 case CHANNEL_ERROR:
499 submitTask(deviceId, TaskType.CHANNEL_CLOSED);
500 break;
501 case ROLE_MASTER:
502 submitTask(deviceId, TaskType.ROLE_MASTER);
503 break;
504 case ROLE_STANDBY:
505 submitTask(deviceId, TaskType.ROLE_STANDBY);
506 break;
507 case ROLE_NONE:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700508 // FIXME: in case of device disconnection, agents will
509 // signal role NONE, preventing the DeviceManager to mark
510 // the device as offline, as only the master can do that. We
511 // should change the DeviceManager. For now, we disable
512 // signaling role NONE.
513 // submitTask(deviceId, TaskType.ROLE_NONE);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800514 break;
515 case NOT_MASTER:
516 submitTask(deviceId, TaskType.NOT_MASTER);
517 break;
518 default:
519 log.warn("Unrecognized device agent event {}", event.type());
520 }
521 }
522 }
523
524 /**
525 * Pipeline event listener.
526 */
527 private class InternalPipeconfWatchdogListener implements PiPipeconfWatchdogListener {
528 @Override
529 public void event(PiPipeconfWatchdogEvent event) {
530 final DeviceId deviceId = event.subject();
531 switch (event.type()) {
532 case PIPELINE_READY:
533 submitTask(deviceId, TaskType.PIPELINE_READY);
534 break;
535 case PIPELINE_UNKNOWN:
536 submitTask(deviceId, TaskType.PIPELINE_NOT_READY);
537 break;
538 default:
539 break;
540 }
541 }
542
543 @Override
544 public boolean isRelevant(PiPipeconfWatchdogEvent event) {
545 return myScheme(event.subject());
546 }
547 }
548
Carmelo Cascone4b616312019-04-17 14:15:45 -0700549 private void startOrReschedulePeriodicCheckupTasks() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800550 synchronized (this) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700551 if (checkupTask != null) {
552 checkupTask.cancel(false);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800553 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700554 checkupTask = SharedScheduledExecutors.getPoolThreadExecutor()
555 .scheduleAtFixedRate(
556 this::submitCheckupTasksForAllDevices,
557 1,
558 checkupInterval,
pierventreb4120ff2021-04-09 14:32:11 +0200559 TimeUnit.SECONDS,
560 true);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800561 }
562 }
563
Carmelo Cascone4b616312019-04-17 14:15:45 -0700564 private void submitCheckupTasksForAllDevices() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800565 // Async trigger a task for all devices in the cfg.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700566 log.debug("Submitting checkup task for all devices...");
Carmelo Cascone1dfc7862019-04-17 16:37:44 -0700567 final Set<DeviceId> deviceToCheck = Sets.newHashSet();
568 // All devices in the core and in the config that we care about.
569 deviceService.getDevices().forEach(d -> {
570 if (myScheme(d.id())) {
571 deviceToCheck.add(d.id());
572 }
573 });
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800574 cfgService.getSubjects(DeviceId.class).stream()
575 .filter(GeneralDeviceProvider::myScheme)
Carmelo Cascone1dfc7862019-04-17 16:37:44 -0700576 .filter(this::configIsPresent)
577 .forEach(deviceToCheck::add);
578 deviceToCheck.forEach(d -> submitTask(d, TaskType.CHECKUP));
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800579 }
580
581 /**
582 * Type of tasks performed by this provider.
583 */
584 enum TaskType {
585 CONNECTION_SETUP,
586 CONNECTION_UPDATE,
587 CONNECTION_TEARDOWN,
588 PIPELINE_READY,
589 CHANNEL_OPEN,
590 CHANNEL_CLOSED,
591 PIPELINE_NOT_READY,
Carmelo Cascone4b616312019-04-17 14:15:45 -0700592 CHECKUP,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800593 ROLE_MASTER,
594 ROLE_NONE,
595 ROLE_STANDBY,
596 NOT_MASTER,
597 }
598
599 private void submitTask(DeviceId deviceId, TaskType taskType) {
600 taskExecutor.submit(deviceId, taskType, taskRunnable(deviceId, taskType));
601 }
602
603 private Runnable taskRunnable(DeviceId deviceId, TaskType taskType) {
604 switch (taskType) {
605 case CONNECTION_SETUP:
606 return () -> handleConnectionSetup(deviceId);
607 case CONNECTION_UPDATE:
608 return () -> handleConnectionUpdate(deviceId);
609 case CONNECTION_TEARDOWN:
610 return () -> handleConnectionTeardown(deviceId);
611 case CHANNEL_OPEN:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700612 case CHECKUP:
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800613 case PIPELINE_READY:
Carmelo Cascone4b616312019-04-17 14:15:45 -0700614 return () -> doCheckupAndRepair(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700615 case CHANNEL_CLOSED:
616 case PIPELINE_NOT_READY:
617 return () -> markOfflineIfNeeded(deviceId);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800618 case ROLE_MASTER:
619 return () -> handleMastershipResponse(deviceId, MastershipRole.MASTER);
620 case ROLE_STANDBY:
621 return () -> handleMastershipResponse(deviceId, MastershipRole.STANDBY);
622 case ROLE_NONE:
623 return () -> handleMastershipResponse(deviceId, MastershipRole.NONE);
624 case NOT_MASTER:
625 return () -> handleNotMaster(deviceId);
626 default:
627 throw new IllegalArgumentException("Unrecognized task type " + taskType);
628 }
629 }
630
631 private void handleConnectionSetup(DeviceId deviceId) {
632 assertConfig(deviceId);
633 // Bind pipeconf (if any and if device is capable).
634 bindPipeconfIfRequired(deviceId);
635 // Get handshaker.
636 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700637 if (handshaker.hasConnection() || handshakersWithListeners.containsKey(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800638 throw new DeviceTaskException("connection already exists");
639 }
640 // Add device agent listener.
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800641 handshakersWithListeners.put(deviceId, handshaker);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700642 handshaker.addDeviceAgentListener(id(), deviceAgentListener);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800643 // Start connection via handshaker.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700644 if (!handshaker.connect()) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800645 // Failed! Remove listeners.
646 handshaker.removeDeviceAgentListener(id());
647 handshakersWithListeners.remove(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700648 // Clean up connection state leftovers.
649 handshaker.disconnect();
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800650 throw new DeviceTaskException("connection failed");
651 }
652 createOrUpdateDevice(deviceId, false);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800653 // From here we expect a CHANNEL_OPEN event to update availability.
654 }
655
656 private void handleConnectionUpdate(DeviceId deviceId) {
657 assertConfig(deviceId);
658 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700659 if (!handshaker.hasConnection()) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800660 // If driver reports that a connection still exists, perhaps the
661 // part of the netcfg that changed does not affect the connection.
662 // Otherwise, remove any previous connection state from the old
663 // netcfg and create a new one.
664 log.warn("Detected change of connection endpoints for {}, will " +
665 "tear down existing connection and set up a new one...",
666 deviceId);
667 handleConnectionTeardown(deviceId);
668 handleConnectionSetup(deviceId);
669 }
670 }
671
672 private void createOrUpdateDevice(DeviceId deviceId, boolean available) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800673 assertConfig(deviceId);
pierventreb4120ff2021-04-09 14:32:11 +0200674
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700675 if (available) {
Carmelo Casconed51a5552019-04-13 01:22:25 -0700676 // Push port descriptions. If marking online, make sure to update
677 // ports before other subsystems pick up the device event.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700678 final List<PortDescription> ports = getPortDetails(deviceId);
679 providerService.updatePorts(deviceId, ports);
680 }
pierventreb4120ff2021-04-09 14:32:11 +0200681
pierventree1f80102021-10-01 22:01:22 +0200682 DeviceDescription deviceDescription = getDeviceDescription(deviceId, available);
683 DeviceDescription storeDescription = providerService.getDeviceDescription(deviceId);
684 if (deviceService.getDevice(deviceId) != null &&
685 deviceService.isAvailable(deviceId) == available &&
686 storeDescription != null) {
687 /*
688 * FIXME SDFAB-650 rethink the APIs and abstractions around the DeviceStore.
689 * Device registration is a two-step process for the GDP. Initially, the device is
690 * registered with default avail. to false. Later, the checkup task will update the
691 * description with the default avail to true in order to mark it available. Today,
692 * there is only one API to mark online a device from the device provider which is
693 * deviceConnected which assumes an update on the device description. The device provider
694 * is the only one able to update the device description and we have to make sure that
695 * the default avail. is flipped to true as it is used to mark as online the device when
696 * it is created or updated. Otherwise, if an ONOS instance fails and restarts, when re-joining
697 * the cluster, it will get the device marked as offline and will not be able to update
698 * its status until it become the master. This process concurs with the markOnline done
699 * by the background thread in the DeviceManager and its the reason why we cannot just check
700 * the device availability but we need to compare also the desc. Checking here the equality,
701 * as in general we may want to upgrade the device description at run time.
702 */
703 DeviceDescription testDeviceDescription = DefaultDeviceDescription.copyReplacingAnnotation(
704 deviceDescription, storeDescription.annotations());
705 if (testDeviceDescription.equals(storeDescription)) {
706 return;
707 }
pierventreb4120ff2021-04-09 14:32:11 +0200708 }
pierventree1f80102021-10-01 22:01:22 +0200709
710 providerService.deviceConnected(deviceId, deviceDescription);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800711 }
712
713 private boolean probeAvailability(DeviceHandshaker handshaker) {
Carmelo Cascone61469462019-03-05 23:59:11 -0800714 return Futures.getUnchecked(handshaker.probeAvailability());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800715 }
716
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800717 private void markOfflineIfNeeded(DeviceId deviceId) {
718 assertDeviceRegistered(deviceId);
719 if (deviceService.isAvailable(deviceId)) {
720 providerService.deviceDisconnected(deviceId);
721 }
722 }
723
Carmelo Cascone4b616312019-04-17 14:15:45 -0700724 private void doCheckupAndRepair(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800725
Carmelo Cascone4b616312019-04-17 14:15:45 -0700726 // This task should be invoked periodically for each device known by
727 // this provider, or as a consequence of events signaling potential
728 // availability changes of the device. We check that everything is in
729 // order, repair what's wrong, and eventually mark the the device as
730 // available (or not) in the core.
731
732 if (!configIsPresent(deviceId)) {
733 // We should have a connection only for devices in the config.
734 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
735 return;
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800736 }
737
Carmelo Cascone4b616312019-04-17 14:15:45 -0700738 final DeviceHandshaker handshaker = handshakersWithListeners.get(deviceId);
739 if (handshaker == null) {
740 // Device in config but we have not initiated a connection.
741 // Perhaps we missed the config event?
742 submitTask(deviceId, TaskType.CONNECTION_SETUP);
743 return;
744 }
745
746 // If here, we have a handshaker meaning we already connected once to
747 // the device...
748 if (!handshaker.hasConnection()) {
749 // ... but now the driver reports there is NOT a connection.
750 // Perhaps the netcfg changed and we didn't pick the event?
751 log.warn("Re-establishing lost connection to {}", deviceId);
752 submitTask(deviceId, TaskType.CONNECTION_TEARDOWN);
753 submitTask(deviceId, TaskType.CONNECTION_SETUP);
754 return;
755 }
756
757 // If here, device should be registered in the core.
758 assertDeviceRegistered(deviceId);
759
pierventred7cae132022-02-03 21:34:04 +0100760 if (!handshaker.isReachable() || !probeReachabilitySync(deviceId)) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700761 // Device appears to be offline.
762 markOfflineIfNeeded(deviceId);
pierventred7cae132022-02-03 21:34:04 +0100763 // We expect the protocol layer to implement some sort of
Carmelo Cascone4b616312019-04-17 14:15:45 -0700764 // connection backoff mechanism and to signal availability via
pierventred7cae132022-02-03 21:34:04 +0100765 // CHANNEL_OPEN events.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700766 return;
767 }
768
769 // If here, device is reachable. Now do mastership and availability
770 // checkups. To avoid overload of checkup tasks which might involve
771 // sending messages over the network and triggering mastership
772 // elections. We require a minimum interval of 1/3 of the configured
773 // checkupInterval between consecutive checkup tasks when the device is
774 // known to be available.
775
776 final Long lastCheckup = lastCheckups.get(deviceId);
777 final boolean isAvailable = deviceService.isAvailable(deviceId);
778 if (isAvailable && lastCheckup != null &&
779 (currentTimeMillis() - lastCheckup) < (checkupInterval * 1000 / 3)) {
780 if (log.isDebugEnabled()) {
781 log.debug("Dropping checkup task for {} as it happened recently",
782 deviceId);
783 }
784 return;
785 }
786 lastCheckups.put(deviceId, currentTimeMillis());
787
788 // Make sure device has a valid mastership role.
789 final MastershipRole expectedRole = mastershipService.getLocalRole(deviceId);
790 if (expectedRole == MastershipRole.NONE) {
791 log.debug("Detected invalid role ({}) for {}, waiting for mastership " +
792 "service to fix this...",
793 expectedRole, deviceId);
794 // Gentle nudge to fix things...
795 mastershipService.requestRoleForSync(deviceId);
796 return;
797 }
798
799 final MastershipRole deviceRole = handshaker.getRole();
pierventred510b7d2022-02-03 21:56:37 +0100800 // FIXME: we should be checking the mastership term as well.
Carmelo Cascone4b616312019-04-17 14:15:45 -0700801 if (expectedRole != deviceRole) {
pierventred510b7d2022-02-03 21:56:37 +0100802 // Let's be greedy, if the role is NONE likely is due to the lazy channel
803 if (deviceRole == MastershipRole.NONE) {
804 log.warn("Detected role mismatch for {}, core expects {}, " +
805 "but device reports {}, reassert the role... ",
806 deviceId, expectedRole, deviceRole);
807 /* If we are experience a severe issue, eventually
808 the DeviceManager will move the mastership */
809 roleChanged(deviceId, expectedRole);
810 } else {
811 log.debug("Detected role mismatch for {}, core expects {}, " +
812 "but device reports {}, waiting for mastership " +
813 "service to fix this...",
814 deviceId, expectedRole, deviceRole);
815 // Gentle nudge to fix things...
816 providerService.receivedRoleReply(deviceId, deviceRole);
817 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700818 return;
819 }
820
821 // Check and update availability, which differently from reachability
822 // describes the ability of the device to forward packets.
823 if (probeAvailability(handshaker)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800824 // Device ready to do its job.
825 createOrUpdateDevice(deviceId, true);
826 } else {
827 markOfflineIfNeeded(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700828 if (isPipelineProgrammable(deviceId)) {
829 // If reachable, but not available, and pipeline programmable,
830 // there is a high chance it's because the pipeline is not READY
831 // (independently from what the pipeconf watchdog reports, as
832 // the status there might be outdated). Encourage pipeconf
833 // watchdog to perform a pipeline probe ASAP.
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800834 pipeconfWatchdogService.triggerProbe(deviceId);
835 }
836 }
837 }
838
839 private void handleMastershipResponse(DeviceId deviceId, MastershipRole response) {
840 assertDeviceRegistered(deviceId);
841 log.debug("Device {} asserted role {}", deviceId, response);
842 providerService.receivedRoleReply(deviceId, response);
843 }
844
845 private void handleNotMaster(DeviceId deviceId) {
846 assertDeviceRegistered(deviceId);
Carmelo Cascone4b616312019-04-17 14:15:45 -0700847 handleMastershipResponse(deviceId, handshakerOrFail(deviceId).getRole());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800848 }
849
850 private void assertDeviceRegistered(DeviceId deviceId) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700851 if (!deviceIsRegistered(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800852 throw new DeviceTaskException("device not registered in the core");
853 }
854 }
855
Carmelo Cascone4b616312019-04-17 14:15:45 -0700856 private boolean deviceIsRegistered(DeviceId deviceId) {
857 return deviceService.getDevice(deviceId) != null;
858 }
859
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800860 private void handleConnectionTeardown(DeviceId deviceId) {
861 if (deviceService.getDevice(deviceId) != null
862 && deviceService.isAvailable(deviceId)) {
863 providerService.deviceDisconnected(deviceId);
864 }
865 final DeviceHandshaker handshaker = handshakerOrFail(deviceId);
866 handshaker.removeDeviceAgentListener(id());
867 handshakersWithListeners.remove(deviceId);
868 handshaker.disconnect();
Carmelo Cascone4b616312019-04-17 14:15:45 -0700869 lastCheckups.remove(deviceId);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800870 }
871
872 private void bindPipeconfIfRequired(DeviceId deviceId) {
873 if (pipeconfService.getPipeconf(deviceId).isPresent()
874 || !isPipelineProgrammable(deviceId)) {
875 // Nothing to do.
876 // Device has already a pipeconf or is not programmable.
877 return;
878 }
879 // Get pipeconf from netcfg or driver (default one).
880 final PiPipelineProgrammable pipelineProg = getBehaviour(
881 deviceId, PiPipelineProgrammable.class);
882 final PiPipeconfId pipeconfId = getPipeconfId(deviceId, pipelineProg);
883 if (pipeconfId == null) {
884 throw new DeviceTaskException("unable to find pipeconf");
885 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700886 if (!pipeconfService.getPipeconf(pipeconfId).isPresent()) {
887 throw new DeviceTaskException(format(
888 "pipeconf %s not registered", pipeconfId));
889 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800890 // Store binding in pipeconf service.
891 pipeconfService.bindToDevice(pipeconfId, deviceId);
892 }
893
894 private PiPipeconfId getPipeconfId(DeviceId deviceId, PiPipelineProgrammable pipelineProg) {
895 // Places to look for a pipeconf ID (in priority order)):
896 // 1) netcfg
897 // 2) device driver (default one)
898 final PiPipeconfId pipeconfId = getPipeconfFromCfg(deviceId);
899 if (pipeconfId != null && !pipeconfId.id().isEmpty()) {
900 return pipeconfId;
901 }
902 if (pipelineProg != null
903 && pipelineProg.getDefaultPipeconf().isPresent()) {
904 final PiPipeconf defaultPipeconf = pipelineProg.getDefaultPipeconf().get();
905 log.info("Using default pipeconf {} for {}", defaultPipeconf.id(), deviceId);
906 return defaultPipeconf.id();
907 }
908 return null;
909 }
910
911 private PiPipeconfId getPipeconfFromCfg(DeviceId deviceId) {
912 BasicDeviceConfig config = cfgService.getConfig(
913 deviceId, BasicDeviceConfig.class);
914 if (config == null) {
915 return null;
916 }
917 return config.pipeconf() != null
918 ? new PiPipeconfId(config.pipeconf()) : null;
919 }
920
921 private DeviceHandshaker handshakerOrFail(DeviceId deviceId) {
922 final DeviceHandshaker handshaker = getBehaviour(
923 deviceId, DeviceHandshaker.class);
924 if (handshaker == null) {
925 throw new DeviceTaskException("missing handshaker behavior");
926 }
927 return handshaker;
928 }
929
Carmelo Cascone4b616312019-04-17 14:15:45 -0700930 private boolean configIsPresent(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800931 final BasicDeviceConfig basicDeviceCfg = cfgService.getConfig(
932 deviceId, BasicDeviceConfig.class);
933 return basicDeviceCfg != null && !isNullOrEmpty(basicDeviceCfg.driver());
934 }
935
936 private void assertConfig(DeviceId deviceId) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700937 if (!configIsPresent(deviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800938 throw new DeviceTaskException("configuration is not complete");
939 }
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700940 }
941
Andrea Campanella241896c2017-05-10 13:11:04 -0700942 private Driver getDriver(DeviceId deviceId) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700943 try {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200944 // DriverManager checks first using basic device config.
945 return driverService.getDriver(deviceId);
Andrea Campanella241896c2017-05-10 13:11:04 -0700946 } catch (ItemNotFoundException e) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200947 log.error("Driver not found for {}", deviceId);
948 return null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700949 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700950 }
951
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700952 private <T extends Behaviour> T getBehaviour(DeviceId deviceId, Class<T> type) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700953 Driver driver = getDriver(deviceId);
954 if (driver == null) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700955 return null;
956 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700957 if (!driver.hasBehaviour(type)) {
958 return null;
959 }
960 final DriverData data = new DefaultDriverData(driver, deviceId);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700961 final DefaultDriverHandler handler = new DefaultDriverHandler(data);
962 return driver.createBehaviour(handler, type);
Andrea Campanella241896c2017-05-10 13:11:04 -0700963 }
964
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800965 private boolean hasBehaviour(DeviceId deviceId, Class<? extends Behaviour> type) {
966 Driver driver = getDriver(deviceId);
967 if (driver == null) {
968 return false;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200969 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800970 return driver.hasBehaviour(type);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700971 }
972
973 private DeviceDescription getDeviceDescription(
974 DeviceId deviceId, boolean defaultAvailable) {
975 // Get one from driver or forge.
976 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
977 deviceId, DeviceDescriptionDiscovery.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700978 if (deviceDiscovery == null) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700979 return forgeDeviceDescription(deviceId, defaultAvailable);
980 }
Yi Tsengd7716482018-10-31 15:34:30 -0700981
982 final DeviceDescription d = deviceDiscovery.discoverDeviceDetails();
983 if (d == null) {
984 return forgeDeviceDescription(deviceId, defaultAvailable);
985 }
986 // Enforce defaultAvailable flag over the one obtained from driver.
987 return new DefaultDeviceDescription(d, defaultAvailable, d.annotations());
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700988 }
989
990 private List<PortDescription> getPortDetails(DeviceId deviceId) {
991 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
992 deviceId, DeviceDescriptionDiscovery.class);
993 if (deviceDiscovery != null) {
994 return deviceDiscovery.discoverPortDetails();
995 } else {
996 return Collections.emptyList();
997 }
998 }
999
1000 private DeviceDescription forgeDeviceDescription(
1001 DeviceId deviceId, boolean defaultAvailable) {
1002 // Uses handshaker and provider config to get driver data.
1003 final DeviceHandshaker handshaker = getBehaviour(
1004 deviceId, DeviceHandshaker.class);
1005 final Driver driver = handshaker != null
1006 ? handshaker.handler().driver() : null;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001007 return new DefaultDeviceDescription(
1008 deviceId.uri(),
1009 Device.Type.SWITCH,
1010 driver != null ? driver.manufacturer() : UNKNOWN,
1011 driver != null ? driver.hwVersion() : UNKNOWN,
1012 driver != null ? driver.swVersion() : UNKNOWN,
1013 UNKNOWN,
1014 new ChassisId(),
1015 defaultAvailable,
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001016 DefaultAnnotations.EMPTY);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001017 }
1018
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001019 static boolean myScheme(DeviceId deviceId) {
1020 return deviceId.uri().getScheme().equals(URI_SCHEME);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +02001021 }
1022
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001023 private boolean isPipelineProgrammable(DeviceId deviceId) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -08001024 return hasBehaviour(deviceId, PiPipelineProgrammable.class);
Carmelo Casconede3b6842018-09-05 17:45:10 -07001025 }
Thomas Vachuska1b3cf362019-08-29 17:11:18 -07001026
1027 private class InternalDeviceListener implements DeviceListener {
1028 @Override
1029 public void event(DeviceEvent event) {
1030 log.info("Triggering disconnect for device {}", event.subject().id());
1031 triggerDisconnect(event.subject().id());
1032 }
1033
1034 @Override
1035 public boolean isRelevant(DeviceEvent event) {
Thomas Vachuska24c0ca42019-08-29 18:19:16 -07001036 return DeviceEvent.Type.DEVICE_REMOVED == event.type() && myScheme(event.subject().id());
Thomas Vachuska1b3cf362019-08-29 17:11:18 -07001037 }
1038 }
Andrea Campanella241896c2017-05-10 13:11:04 -07001039}