blob: bbf71f28acd34a494f0e47153f19ede21f70d909 [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
19import com.google.common.annotations.Beta;
Andrea Campanellabc112a92017-06-26 19:06:43 +020020import com.google.common.collect.ImmutableSet;
21import com.google.common.collect.Maps;
Carmelo Cascone158b8c42018-07-04 19:42:37 +020022import com.google.common.util.concurrent.Striped;
Andrea Campanella241896c2017-05-10 13:11:04 -070023import org.onlab.packet.ChassisId;
24import org.onlab.util.ItemNotFoundException;
Andrea Campanella19090322017-08-22 10:31:37 +020025import org.onlab.util.Tools;
Andrea Campanella4929a812017-10-09 18:38:23 +020026import org.onosproject.cfg.ComponentConfigService;
Andrea Campanella241896c2017-05-10 13:11:04 -070027import org.onosproject.core.CoreService;
Yi Tsenge616d752018-11-27 10:53:27 -080028import org.onosproject.gnmi.api.GnmiController;
Andrea Campanella14e196d2017-07-24 18:11:36 +020029import org.onosproject.mastership.MastershipService;
Andrea Campanella241896c2017-05-10 13:11:04 -070030import org.onosproject.net.AnnotationKeys;
31import org.onosproject.net.DefaultAnnotations;
32import org.onosproject.net.Device;
33import org.onosproject.net.DeviceId;
34import org.onosproject.net.MastershipRole;
35import org.onosproject.net.PortNumber;
Carmelo Cascone87892e22017-11-13 16:01:29 -080036import org.onosproject.net.behaviour.PiPipelineProgrammable;
Andrea Campanella241896c2017-05-10 13:11:04 -070037import org.onosproject.net.behaviour.PortAdmin;
38import org.onosproject.net.config.ConfigFactory;
39import org.onosproject.net.config.NetworkConfigEvent;
40import org.onosproject.net.config.NetworkConfigListener;
41import org.onosproject.net.config.NetworkConfigRegistry;
42import org.onosproject.net.config.basics.BasicDeviceConfig;
43import org.onosproject.net.config.basics.SubjectFactories;
44import org.onosproject.net.device.DefaultDeviceDescription;
Carmelo Casconee5b28722018-06-22 17:28:28 +020045import org.onosproject.net.device.DeviceAgentEvent;
46import org.onosproject.net.device.DeviceAgentListener;
Andrea Campanella241896c2017-05-10 13:11:04 -070047import org.onosproject.net.device.DeviceDescription;
48import org.onosproject.net.device.DeviceDescriptionDiscovery;
49import org.onosproject.net.device.DeviceEvent;
50import org.onosproject.net.device.DeviceHandshaker;
51import org.onosproject.net.device.DeviceListener;
52import org.onosproject.net.device.DeviceProvider;
53import org.onosproject.net.device.DeviceProviderRegistry;
54import org.onosproject.net.device.DeviceProviderService;
55import org.onosproject.net.device.DeviceService;
56import org.onosproject.net.device.PortDescription;
57import org.onosproject.net.device.PortStatistics;
58import org.onosproject.net.device.PortStatisticsDiscovery;
59import org.onosproject.net.driver.Behaviour;
60import org.onosproject.net.driver.DefaultDriverData;
61import org.onosproject.net.driver.DefaultDriverHandler;
62import org.onosproject.net.driver.Driver;
63import org.onosproject.net.driver.DriverData;
64import org.onosproject.net.driver.DriverService;
Carmelo Cascone59f57de2017-07-11 19:55:09 -040065import org.onosproject.net.pi.model.PiPipeconf;
Andrea Campanellabc112a92017-06-26 19:06:43 +020066import org.onosproject.net.pi.model.PiPipeconfId;
Carmelo Cascone39c28ca2017-11-15 13:03:57 -080067import org.onosproject.net.pi.service.PiPipeconfConfig;
68import org.onosproject.net.pi.service.PiPipeconfService;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070069import org.onosproject.net.pi.service.PiPipeconfWatchdogEvent;
70import org.onosproject.net.pi.service.PiPipeconfWatchdogListener;
71import org.onosproject.net.pi.service.PiPipeconfWatchdogService;
Andrea Campanella241896c2017-05-10 13:11:04 -070072import org.onosproject.net.provider.AbstractProvider;
73import org.onosproject.net.provider.ProviderId;
74import org.onosproject.provider.general.device.api.GeneralProviderDeviceConfig;
Andrea Campanella19090322017-08-22 10:31:37 +020075import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070076import org.osgi.service.component.annotations.Activate;
77import org.osgi.service.component.annotations.Component;
78import org.osgi.service.component.annotations.Deactivate;
79import org.osgi.service.component.annotations.Modified;
80import org.osgi.service.component.annotations.Reference;
81import org.osgi.service.component.annotations.ReferenceCardinality;
Andrea Campanella241896c2017-05-10 13:11:04 -070082import org.slf4j.Logger;
83
Ray Milkeyb68bbbc2017-12-18 10:05:49 -080084import java.security.SecureRandom;
Andrea Campanella241896c2017-05-10 13:11:04 -070085import java.util.Collection;
Andrea Campanellabc112a92017-06-26 19:06:43 +020086import java.util.Collections;
Andrea Campanella19090322017-08-22 10:31:37 +020087import java.util.Dictionary;
Andrea Campanella241896c2017-05-10 13:11:04 -070088import java.util.List;
Thomas Vachuska5b38dc02018-05-10 15:24:40 -070089import java.util.Map;
Andrea Campanellabc112a92017-06-26 19:06:43 +020090import java.util.Set;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070091import java.util.StringJoiner;
Andrea Campanella241896c2017-05-10 13:11:04 -070092import java.util.concurrent.CompletableFuture;
Andrea Campanella19090322017-08-22 10:31:37 +020093import java.util.concurrent.ConcurrentHashMap;
Andrea Campanellabc112a92017-06-26 19:06:43 +020094import java.util.concurrent.ConcurrentMap;
95import java.util.concurrent.CopyOnWriteArraySet;
Andrea Campanella241896c2017-05-10 13:11:04 -070096import java.util.concurrent.ExecutionException;
Carmelo Casconee5b28722018-06-22 17:28:28 +020097import java.util.concurrent.ExecutorService;
Andrea Campanella241896c2017-05-10 13:11:04 -070098import java.util.concurrent.ScheduledExecutorService;
Andrea Campanella19090322017-08-22 10:31:37 +020099import java.util.concurrent.ScheduledFuture;
Andrea Campanella241896c2017-05-10 13:11:04 -0700100import java.util.concurrent.TimeUnit;
101import java.util.concurrent.TimeoutException;
Andrea Campanellabc112a92017-06-26 19:06:43 +0200102import java.util.concurrent.locks.Lock;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200103import java.util.function.Supplier;
Andrea Campanella241896c2017-05-10 13:11:04 -0700104
Carmelo Casconee5b28722018-06-22 17:28:28 +0200105import static java.util.concurrent.Executors.newFixedThreadPool;
Andrea Campanella241896c2017-05-10 13:11:04 -0700106import static java.util.concurrent.Executors.newScheduledThreadPool;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200107import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
Andrea Campanella241896c2017-05-10 13:11:04 -0700108import static org.onlab.util.Tools.groupedThreads;
109import static org.onosproject.net.device.DeviceEvent.Type;
Yi Tsengd7716482018-10-31 15:34:30 -0700110import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.OP_TIMEOUT_SHORT;
111import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.OP_TIMEOUT_SHORT_DEFAULT;
112import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.PROBE_FREQUENCY;
113import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.PROBE_FREQUENCY_DEFAULT;
114import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_FREQUENCY;
115import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_FREQUENCY_DEFAULT;
Andrea Campanella241896c2017-05-10 13:11:04 -0700116import static org.slf4j.LoggerFactory.getLogger;
117
118/**
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200119 * Provider which uses drivers to detect device and do initial handshake and
120 * channel establishment with devices. Any other provider specific operation is
121 * also delegated to the DeviceHandshaker driver.
Andrea Campanella241896c2017-05-10 13:11:04 -0700122 */
123@Beta
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700124@Component(immediate = true,
125 property = {
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700126 STATS_POLL_FREQUENCY + ":Integer=" + STATS_POLL_FREQUENCY_DEFAULT,
127 PROBE_FREQUENCY + ":Integer=" + PROBE_FREQUENCY_DEFAULT,
128 OP_TIMEOUT_SHORT + ":Integer=" + OP_TIMEOUT_SHORT_DEFAULT,
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700129 })
Andrea Campanella241896c2017-05-10 13:11:04 -0700130public class GeneralDeviceProvider extends AbstractProvider
131 implements DeviceProvider {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200132
Andrea Campanella241896c2017-05-10 13:11:04 -0700133 private final Logger log = getLogger(getClass());
134
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700135 private static final String APP_NAME = "org.onosproject.gdp";
136 private static final String URI_SCHEME = "device";
137 private static final String CFG_SCHEME = "generalprovider";
138 private static final String DEVICE_PROVIDER_PACKAGE =
139 "org.onosproject.general.provider.device";
140 private static final int CORE_POOL_SIZE = 10;
141 private static final String UNKNOWN = "unknown";
142 private static final String DRIVER = "driver";
143 private static final Set<String> PIPELINE_CONFIGURABLE_PROTOCOLS =
144 ImmutableSet.of("p4runtime");
145
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700146 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200147 private DeviceProviderRegistry providerRegistry;
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 ComponentConfigService componentConfigService;
Andrea Campanella4929a812017-10-09 18:38:23 +0200151
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700152 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200153 private NetworkConfigRegistry cfgService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700154
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700155 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200156 private CoreService coreService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700157
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700158 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200159 private DeviceService deviceService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700160
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700161 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200162 private DriverService driverService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700163
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700164 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200165 private MastershipService mastershipService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200166
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700167 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700168 private PiPipeconfService pipeconfService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700169
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700170 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700171 private PiPipeconfWatchdogService pipeconfWatchdogService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200172
Yi Tsenge616d752018-11-27 10:53:27 -0800173 // FIXME: no longer general if we add a dependency to a protocol-specific
174 // service. Possible solutions are: rename this provider to
175 // StratumDeviceProvider, find a way to allow this provider to register for
176 // protocol specific events (e.g. port events) via drivers (similar to
177 // DeviceAgentListener).
178 @Reference(cardinality = ReferenceCardinality.MANDATORY)
179 private GnmiController gnmiController;
180
181 private GnmiDeviceStateSubscriber gnmiDeviceStateSubscriber;
182
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700183 /**
184 * Configure poll frequency for port status and statistics; default is 10 sec.
185 */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700186 private int statsPollFrequency = STATS_POLL_FREQUENCY_DEFAULT;
Andrea Campanella19090322017-08-22 10:31:37 +0200187
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700188 /**
189 * Configure probe frequency for checking device availability; default is 10 sec.
190 */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700191 private int probeFrequency = PROBE_FREQUENCY_DEFAULT;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200192
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700193 /**
194 * Configure timeout in seconds for device operations that are supposed to take a short time
195 * (e.g. checking device reachability); default is 10 seconds.
196 */
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700197 private int opTimeoutShort = OP_TIMEOUT_SHORT_DEFAULT;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200198
Andrea Campanellabc112a92017-06-26 19:06:43 +0200199 //FIXME to be removed when netcfg will issue device events in a bundle or
200 //ensures all configuration needed is present
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700201 private final Set<DeviceId> deviceConfigured = new CopyOnWriteArraySet<>();
202 private final Set<DeviceId> driverConfigured = new CopyOnWriteArraySet<>();
203 private final Set<DeviceId> pipelineConfigured = new CopyOnWriteArraySet<>();
Andrea Campanella241896c2017-05-10 13:11:04 -0700204
Carmelo Casconee5b28722018-06-22 17:28:28 +0200205 private final Map<DeviceId, MastershipRole> requestedRoles = Maps.newConcurrentMap();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700206 private final ConcurrentMap<DeviceId, ScheduledFuture<?>> statsPollingTasks = new ConcurrentHashMap<>();
207 private final Map<DeviceId, DeviceHandshaker> handshakersWithListeners = Maps.newConcurrentMap();
208 private final InternalDeviceListener deviceListener = new InternalDeviceListener();
209 private final InternalPipeconfWatchdogListener pipeconfWatchdogListener = new InternalPipeconfWatchdogListener();
Carmelo Casconee5b28722018-06-22 17:28:28 +0200210 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
211 private final DeviceAgentListener deviceAgentListener = new InternalDeviceAgentListener();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700212 private final ConfigFactory factory = new InternalConfigFactory();
213 private final Striped<Lock> deviceLocks = Striped.lock(30);
Andrea Campanella241896c2017-05-10 13:11:04 -0700214
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700215 private ExecutorService connectionExecutor;
216 private ScheduledExecutorService statsExecutor;
217 private ScheduledExecutorService probeExecutor;
218 private ScheduledFuture<?> probeTask;
219 private DeviceProviderService providerService;
220
221 public GeneralDeviceProvider() {
222 super(new ProviderId(URI_SCHEME, DEVICE_PROVIDER_PACKAGE));
223 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700224
225 @Activate
Andrea Campanella1e573442018-05-17 17:07:13 +0200226 public void activate(ComponentContext context) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700227 connectionExecutor = newFixedThreadPool(CORE_POOL_SIZE, groupedThreads(
228 "onos/gdp-connect", "%d", log));
229 statsExecutor = newScheduledThreadPool(CORE_POOL_SIZE, groupedThreads(
230 "onos/gdp-stats", "%d", log));
231 probeExecutor = newSingleThreadScheduledExecutor(groupedThreads(
232 "onos/gdp-probe", "%d", log));
Andrea Campanella241896c2017-05-10 13:11:04 -0700233 providerService = providerRegistry.register(this);
Andrea Campanella4929a812017-10-09 18:38:23 +0200234 componentConfigService.registerProperties(getClass());
Andrea Campanella241896c2017-05-10 13:11:04 -0700235 coreService.registerApplication(APP_NAME);
236 cfgService.registerConfigFactory(factory);
237 cfgService.addListener(cfgListener);
238 deviceService.addListener(deviceListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700239 pipeconfWatchdogService.addListener(pipeconfWatchdogListener);
240 rescheduleProbeTask(false);
Andrea Campanella1e573442018-05-17 17:07:13 +0200241 modified(context);
Yi Tsenge616d752018-11-27 10:53:27 -0800242 gnmiDeviceStateSubscriber = new GnmiDeviceStateSubscriber(gnmiController,
243 deviceService, mastershipService, providerService);
244 gnmiDeviceStateSubscriber.activate();
Andrea Campanella241896c2017-05-10 13:11:04 -0700245 log.info("Started");
246 }
247
Andrea Campanella19090322017-08-22 10:31:37 +0200248 @Modified
249 public void modified(ComponentContext context) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200250 if (context == null) {
251 return;
Andrea Campanella19090322017-08-22 10:31:37 +0200252 }
253
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200254 Dictionary<?, ?> properties = context.getProperties();
255 final int oldStatsPollFrequency = statsPollFrequency;
256 statsPollFrequency = Tools.getIntegerProperty(
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700257 properties, STATS_POLL_FREQUENCY, STATS_POLL_FREQUENCY_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200258 log.info("Configured. {} is configured to {} seconds",
259 STATS_POLL_FREQUENCY, statsPollFrequency);
260 final int oldProbeFrequency = probeFrequency;
261 probeFrequency = Tools.getIntegerProperty(
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700262 properties, PROBE_FREQUENCY, PROBE_FREQUENCY_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200263 log.info("Configured. {} is configured to {} seconds",
264 PROBE_FREQUENCY, probeFrequency);
265 opTimeoutShort = Tools.getIntegerProperty(
Thomas Vachuska4167c3f2018-10-16 07:16:31 -0700266 properties, OP_TIMEOUT_SHORT, OP_TIMEOUT_SHORT_DEFAULT);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200267 log.info("Configured. {} is configured to {} seconds",
268 OP_TIMEOUT_SHORT, opTimeoutShort);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200269
270 if (oldStatsPollFrequency != statsPollFrequency) {
271 rescheduleStatsPollingTasks();
Andrea Campanella19090322017-08-22 10:31:37 +0200272 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200273
274 if (oldProbeFrequency != probeFrequency) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700275 rescheduleProbeTask(true);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200276 }
277 }
278
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700279 private void rescheduleProbeTask(boolean deelay) {
280 synchronized (this) {
281 if (probeTask != null) {
282 probeTask.cancel(false);
283 }
284 probeTask = probeExecutor.scheduleAtFixedRate(
285 this::triggerProbeAllDevices,
286 deelay ? probeFrequency : 0,
287 probeFrequency,
288 TimeUnit.SECONDS);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200289 }
Andrea Campanella19090322017-08-22 10:31:37 +0200290 }
291
Andrea Campanella241896c2017-05-10 13:11:04 -0700292 @Deactivate
293 public void deactivate() {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700294 // Shutdown stats polling tasks.
295 statsPollingTasks.keySet().forEach(this::cancelStatsPolling);
296 statsPollingTasks.clear();
297 statsExecutor.shutdownNow();
298 try {
299 statsExecutor.awaitTermination(5, TimeUnit.SECONDS);
300 } catch (InterruptedException e) {
301 log.warn("statsExecutor not terminated properly");
302 }
303 statsExecutor = null;
304 // Shutdown probe executor.
305 probeTask.cancel(true);
306 probeTask = null;
307 probeExecutor.shutdownNow();
308 try {
309 probeExecutor.awaitTermination(5, TimeUnit.SECONDS);
310 } catch (InterruptedException e) {
311 log.warn("probeExecutor not terminated properly");
312 }
313 probeExecutor = null;
314 // Shutdown connection executor.
315 connectionExecutor.shutdownNow();
316 try {
317 connectionExecutor.awaitTermination(5, TimeUnit.SECONDS);
318 } catch (InterruptedException e) {
319 log.warn("connectionExecutor not terminated properly");
320 }
321 connectionExecutor = null;
322 // Remove all device agent listeners
323 handshakersWithListeners.values().forEach(h -> h.removeDeviceAgentListener(id()));
324 handshakersWithListeners.clear();
325 // Other cleanup.
Andrea Campanella4929a812017-10-09 18:38:23 +0200326 componentConfigService.unregisterProperties(getClass(), false);
Andrea Campanella241896c2017-05-10 13:11:04 -0700327 cfgService.removeListener(cfgListener);
Andrea Campanella241896c2017-05-10 13:11:04 -0700328 deviceService.removeListener(deviceListener);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700329 pipeconfWatchdogService.removeListener(pipeconfWatchdogListener);
Andrea Campanella241896c2017-05-10 13:11:04 -0700330 providerRegistry.unregister(this);
331 providerService = null;
332 cfgService.unregisterConfigFactory(factory);
Yi Tsenge616d752018-11-27 10:53:27 -0800333 gnmiDeviceStateSubscriber.deactivate();
334 gnmiDeviceStateSubscriber = null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700335 log.info("Stopped");
336 }
337
Andrea Campanella241896c2017-05-10 13:11:04 -0700338
339 @Override
340 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200341 connectionExecutor.execute(withDeviceLock(
342 () -> doDeviceProbe(deviceId), deviceId));
Andrea Campanella241896c2017-05-10 13:11:04 -0700343 }
344
345 @Override
346 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700347 log.info("Notifying role {} to device {}", newRole, deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200348 requestedRoles.put(deviceId, newRole);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200349 connectionExecutor.execute(() -> doRoleChanged(deviceId, newRole));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200350 }
351
352 private void doRoleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700353 final DeviceHandshaker handshaker = getBehaviour(
354 deviceId, DeviceHandshaker.class);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200355 if (handshaker == null) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200356 log.error("Null handshaker. Unable to notify new role {} to {}",
357 newRole, deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200358 return;
359 }
360 handshaker.roleChanged(newRole);
Andrea Campanella241896c2017-05-10 13:11:04 -0700361 }
362
363 @Override
364 public boolean isReachable(DeviceId deviceId) {
Andrea Campanella14e196d2017-07-24 18:11:36 +0200365 log.debug("Testing reachability for device {}", deviceId);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700366 final DeviceHandshaker handshaker = getBehaviour(
367 deviceId, DeviceHandshaker.class);
Andrea Campanellac1ecdd02018-01-12 12:48:24 +0100368 if (handshaker == null) {
369 return false;
370 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200371 return getFutureWithDeadline(
372 handshaker.isReachable(), "checking reachability",
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200373 deviceId, false, opTimeoutShort);
Andrea Campanella241896c2017-05-10 13:11:04 -0700374 }
375
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700376 private boolean isConnected(DeviceId deviceId) {
377 log.debug("Testing connection to device {}", deviceId);
378 final DeviceHandshaker handshaker = getBehaviour(
379 deviceId, DeviceHandshaker.class);
380 if (handshaker == null) {
381 return false;
382 }
383 return handshaker.isConnected();
384 }
385
Andrea Campanella241896c2017-05-10 13:11:04 -0700386 @Override
387 public void changePortState(DeviceId deviceId, PortNumber portNumber,
388 boolean enable) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200389 connectionExecutor.execute(
390 () -> doChangePortState(deviceId, portNumber, enable));
391 }
392
393 private void doChangePortState(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 Cascone158b8c42018-07-04 19:42:37 +0200405 final String descr = (enable ? "enabling" : "disabling") + " port " + portNumber;
406 getFutureWithDeadline(
407 modifyTask, descr, deviceId, null, opTimeoutShort);
Andrea Campanella241896c2017-05-10 13:11:04 -0700408 }
409
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700410 @Override
411 public void triggerDisconnect(DeviceId deviceId) {
Andrea Campanella1e573442018-05-17 17:07:13 +0200412 log.debug("Triggering disconnection of device {}", deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200413 connectionExecutor.execute(withDeviceLock(
414 () -> doDisconnectDevice(deviceId), deviceId));
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700415 }
416
Andrea Campanella241896c2017-05-10 13:11:04 -0700417 private Driver getDriver(DeviceId deviceId) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700418 try {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200419 // DriverManager checks first using basic device config.
420 return driverService.getDriver(deviceId);
Andrea Campanella241896c2017-05-10 13:11:04 -0700421 } catch (ItemNotFoundException e) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200422 log.error("Driver not found for {}", deviceId);
423 return null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700424 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700425 }
426
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700427 private <T extends Behaviour> T getBehaviour(DeviceId deviceId, Class<T> type) {
428 // Get handshaker.
429
430 Driver driver = getDriver(deviceId);
431 if (driver == null) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700432 return null;
433 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700434 if (!driver.hasBehaviour(type)) {
435 return null;
436 }
437 final DriverData data = new DefaultDriverData(driver, deviceId);
438 // Storing deviceKeyId and all other config values as data in the driver
439 // with protocol_<info> name as the key. e.g protocol_ip.
440 final GeneralProviderDeviceConfig providerConfig = cfgService.getConfig(
441 deviceId, GeneralProviderDeviceConfig.class);
442 if (providerConfig != null) {
443 providerConfig.protocolsInfo().forEach((protocol, info) -> {
444 info.configValues().forEach(
445 (k, v) -> data.set(protocol + "_" + k, v));
446 data.set(protocol + "_key", info.deviceKeyId());
447 });
448 }
449 final DefaultDriverHandler handler = new DefaultDriverHandler(data);
450 return driver.createBehaviour(handler, type);
Andrea Campanella241896c2017-05-10 13:11:04 -0700451 }
452
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200453 private void doConnectDevice(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700454 log.debug("Initiating connection to device {}...", deviceId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200455 // Retrieve config
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700456 if (configIsMissing(deviceId)) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200457 return;
458 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700459 // Bind pipeconf (if any and if device is capable).
460 if (!bindPipeconfIfRequired(deviceId)) {
461 // We already logged the error.
462 return;
463 }
464 // Get handshaker.
465 final DeviceHandshaker handshaker = getBehaviour(
466 deviceId, DeviceHandshaker.class);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200467 if (handshaker == null) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700468 log.error("Missing handshaker behavior for {}, aborting connection",
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200469 deviceId);
470 return;
471 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700472 // Add device agent listener.
473 handshaker.addDeviceAgentListener(id(), deviceAgentListener);
474 handshakersWithListeners.put(deviceId, handshaker);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200475 // Start connection via handshaker.
476 final Boolean connectSuccess = getFutureWithDeadline(
477 handshaker.connect(), "initiating connection",
Carmelo Casconede3b6842018-09-05 17:45:10 -0700478 deviceId, false, opTimeoutShort);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700479 if (!connectSuccess) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200480 log.warn("Unable to connect to {}", deviceId);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700481 }
482 }
483
484 private void triggerAdvertiseDevice(DeviceId deviceId) {
485 connectionExecutor.execute(withDeviceLock(
486 () -> doAdvertiseDevice(deviceId), deviceId));
487 }
488
489 private void doAdvertiseDevice(DeviceId deviceId) {
490 // Retrieve config
491 if (configIsMissing(deviceId)) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200492 return;
493 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700494 // Obtain device and port description.
495 final boolean isPipelineReady = isPipelineReady(deviceId);
496 final DeviceDescription description = getDeviceDescription(
497 deviceId, isPipelineReady);
498 final List<PortDescription> ports = getPortDetails(deviceId);
499 // Advertise to core.
500 if (deviceService.getDevice(deviceId) == null ||
501 (description.isDefaultAvailable() &&
502 !deviceService.isAvailable(deviceId))) {
503 if (!isPipelineReady) {
504 log.info("Advertising device to core with available={} as " +
505 "device pipeline is not ready yet",
506 description.isDefaultAvailable());
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400507 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700508 providerService.deviceConnected(deviceId, description);
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200509 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700510 providerService.updatePorts(deviceId, ports);
511 // If pipeline is not ready, encourage watchdog to perform probe ASAP.
512 if (!isPipelineReady) {
513 pipeconfWatchdogService.triggerProbe(deviceId);
514 }
515 }
516
517 private DeviceDescription getDeviceDescription(
518 DeviceId deviceId, boolean defaultAvailable) {
519 // Get one from driver or forge.
520 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
521 deviceId, DeviceDescriptionDiscovery.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700522 if (deviceDiscovery == null) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700523 return forgeDeviceDescription(deviceId, defaultAvailable);
524 }
Yi Tsengd7716482018-10-31 15:34:30 -0700525
526 final DeviceDescription d = deviceDiscovery.discoverDeviceDetails();
527 if (d == null) {
528 return forgeDeviceDescription(deviceId, defaultAvailable);
529 }
530 // Enforce defaultAvailable flag over the one obtained from driver.
531 return new DefaultDeviceDescription(d, defaultAvailable, d.annotations());
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700532 }
533
534 private List<PortDescription> getPortDetails(DeviceId deviceId) {
535 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
536 deviceId, DeviceDescriptionDiscovery.class);
537 if (deviceDiscovery != null) {
538 return deviceDiscovery.discoverPortDetails();
539 } else {
540 return Collections.emptyList();
541 }
542 }
543
544 private DeviceDescription forgeDeviceDescription(
545 DeviceId deviceId, boolean defaultAvailable) {
546 // Uses handshaker and provider config to get driver data.
547 final DeviceHandshaker handshaker = getBehaviour(
548 deviceId, DeviceHandshaker.class);
549 final Driver driver = handshaker != null
550 ? handshaker.handler().driver() : null;
551 final GeneralProviderDeviceConfig cfg = cfgService.getConfig(
552 deviceId, GeneralProviderDeviceConfig.class);
553 final DefaultAnnotations.Builder annBuilder = DefaultAnnotations.builder();
554 // If device is pipeline programmable, let this provider decide when the
555 // device can be marked online.
556 annBuilder.set(AnnotationKeys.PROVIDER_MARK_ONLINE,
557 String.valueOf(isPipelineProgrammable(deviceId)));
558 if (cfg != null) {
559 StringJoiner protoStringBuilder = new StringJoiner(", ");
560 cfg.protocolsInfo().keySet().forEach(protoStringBuilder::add);
561 annBuilder.set(AnnotationKeys.PROTOCOL, protoStringBuilder.toString());
562 }
563 return new DefaultDeviceDescription(
564 deviceId.uri(),
565 Device.Type.SWITCH,
566 driver != null ? driver.manufacturer() : UNKNOWN,
567 driver != null ? driver.hwVersion() : UNKNOWN,
568 driver != null ? driver.swVersion() : UNKNOWN,
569 UNKNOWN,
570 new ChassisId(),
571 defaultAvailable,
572 annBuilder.build());
573 }
574
575 private void triggerMarkAvailable(DeviceId deviceId) {
576 connectionExecutor.execute(withDeviceLock(
577 () -> doMarkAvailable(deviceId), deviceId));
578 }
579
580 private void doMarkAvailable(DeviceId deviceId) {
581 if (deviceService.isAvailable(deviceId)) {
582 return;
583 }
584 final DeviceDescription descr = getDeviceDescription(deviceId, true);
585 // It has been observed that devices that were marked offline (e.g.
586 // after device disconnection) might end up with no master. Here we
587 // trigger a new master election (if device has no master).
588 mastershipService.requestRoleForSync(deviceId);
589 providerService.deviceConnected(deviceId, descr);
590 }
591
592 private boolean bindPipeconfIfRequired(DeviceId deviceId) {
593 if (pipeconfService.ofDevice(deviceId).isPresent()
594 || !isPipelineProgrammable(deviceId)) {
595 // Nothing to do, all good.
596 return true;
597 }
598 // Get pipeconf from netcfg or driver (default one).
599 final PiPipelineProgrammable pipelineProg = getBehaviour(
600 deviceId, PiPipelineProgrammable.class);
601 final PiPipeconfId pipeconfId = getPipeconfId(deviceId, pipelineProg);
602 if (pipeconfId == null) {
603 return false;
604 }
605 // Store binding in pipeconf service.
606 pipeconfService.bindToDevice(pipeconfId, deviceId);
607 return true;
608 }
609
610 private PiPipeconfId getPipeconfId(DeviceId deviceId, PiPipelineProgrammable pipelineProg) {
611 // Places to look for a pipeconf ID (in priority order)):
612 // 1) netcfg
613 // 2) device driver (default one)
614 final PiPipeconfId pipeconfId = getPipeconfFromCfg(deviceId);
615 if (pipeconfId != null && !pipeconfId.id().isEmpty()) {
616 return pipeconfId;
617 }
618 if (pipelineProg != null
619 && pipelineProg.getDefaultPipeconf().isPresent()) {
620 final PiPipeconf defaultPipeconf = pipelineProg.getDefaultPipeconf().get();
621 log.info("Using default pipeconf {} for {}", defaultPipeconf.id(), deviceId);
622 return defaultPipeconf.id();
623 } else {
624 log.warn("Unable to associate a pipeconf to {}", deviceId);
Andrea Campanella14e196d2017-07-24 18:11:36 +0200625 return null;
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400626 }
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400627 }
628
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200629 private void doDisconnectDevice(DeviceId deviceId) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200630 log.debug("Initiating disconnection from {}...", deviceId);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700631 final DeviceHandshaker handshaker = getBehaviour(
632 deviceId, DeviceHandshaker.class);
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200633 final boolean isAvailable = deviceService.isAvailable(deviceId);
634 // Signal disconnection to core (if master).
635 if (isAvailable && mastershipService.isLocalMaster(deviceId)) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200636 providerService.deviceDisconnected(deviceId);
637 }
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200638 // Cancel tasks.
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200639 cancelStatsPolling(deviceId);
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200640 // Disconnect device.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200641 if (handshaker == null) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200642 if (isAvailable) {
643 // If not available don't bother logging. We are probably
644 // invoking this method multiple times for the same device.
645 log.warn("Missing DeviceHandshaker behavior for {}, " +
646 "no guarantees of complete disconnection",
647 deviceId);
648 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200649 return;
Andrea Campanella241896c2017-05-10 13:11:04 -0700650 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700651 handshaker.removeDeviceAgentListener(id());
652 handshakersWithListeners.remove(deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200653 final boolean disconnectSuccess = getFutureWithDeadline(
654 handshaker.disconnect(), "performing disconnection",
655 deviceId, false, opTimeoutShort);
656 if (!disconnectSuccess) {
657 log.warn("Unable to disconnect from {}", deviceId);
658 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700659 }
660
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200661 // Needed to catch the exception in the executors since are not rethrown otherwise.
Andrea Campanella241896c2017-05-10 13:11:04 -0700662 private Runnable exceptionSafe(Runnable runnable) {
663 return () -> {
664 try {
665 runnable.run();
666 } catch (Exception e) {
667 log.error("Unhandled Exception", e);
668 }
669 };
670 }
671
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200672 private <U> U withDeviceLock(Supplier<U> task, DeviceId deviceId) {
673 final Lock lock = deviceLocks.get(deviceId);
674 lock.lock();
675 try {
676 return task.get();
677 } finally {
678 lock.unlock();
679 }
680 }
681
682 private Runnable withDeviceLock(Runnable task, DeviceId deviceId) {
683 // Wrapper of withDeviceLock(Supplier, ...) for void tasks.
684 return () -> withDeviceLock(() -> {
685 task.run();
686 return null;
687 }, deviceId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200688 }
689
Andrea Campanella241896c2017-05-10 13:11:04 -0700690 private void updatePortStatistics(DeviceId deviceId) {
Andrea Campanellace111932017-09-18 16:59:56 +0900691 Device device = deviceService.getDevice(deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200692 if (device != null && deviceService.isAvailable(deviceId) &&
Andrea Campanellace111932017-09-18 16:59:56 +0900693 device.is(PortStatisticsDiscovery.class)) {
694 Collection<PortStatistics> statistics = device.as(PortStatisticsDiscovery.class)
695 .discoverPortStatistics();
696 //updating statistcs only if not empty
697 if (!statistics.isEmpty()) {
698 providerService.updatePortStatistics(deviceId, statistics);
699 }
700 } else {
701 log.debug("Can't update port statistics for device {}", deviceId);
Andrea Campanella19090322017-08-22 10:31:37 +0200702 }
703 }
704
Carmelo Casconee5b28722018-06-22 17:28:28 +0200705 private boolean notMyScheme(DeviceId deviceId) {
706 return !deviceId.uri().getScheme().equals(URI_SCHEME);
Andrea Campanella241896c2017-05-10 13:11:04 -0700707 }
708
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200709 private void triggerConnect(DeviceId deviceId) {
710 connectionExecutor.execute(withDeviceLock(
711 () -> doConnectDevice(deviceId), deviceId));
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200712 }
713
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700714 private boolean isPipelineProgrammable(DeviceId deviceId) {
715 final GeneralProviderDeviceConfig providerConfig = cfgService.getConfig(
716 deviceId, GeneralProviderDeviceConfig.class);
717 if (providerConfig == null) {
718 return false;
719 }
720 return !Collections.disjoint(
721 ImmutableSet.copyOf(providerConfig.node().fieldNames()),
722 PIPELINE_CONFIGURABLE_PROTOCOLS);
723 }
724
Andrea Campanella241896c2017-05-10 13:11:04 -0700725 /**
726 * Listener for configuration events.
727 */
728 private class InternalNetworkConfigListener implements NetworkConfigListener {
729
Andrea Campanella241896c2017-05-10 13:11:04 -0700730 @Override
731 public void event(NetworkConfigEvent event) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200732 connectionExecutor.execute(() -> consumeConfigEvent(event));
733 }
734
735 @Override
736 public boolean isRelevant(NetworkConfigEvent event) {
737 return (event.configClass().equals(GeneralProviderDeviceConfig.class) ||
738 event.configClass().equals(BasicDeviceConfig.class) ||
739 event.configClass().equals(PiPipeconfConfig.class)) &&
740 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
741 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
742 }
743
744 private void consumeConfigEvent(NetworkConfigEvent event) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700745 DeviceId deviceId = (DeviceId) event.subject();
746 //Assuming that the deviceId comes with uri 'device:'
Carmelo Casconee5b28722018-06-22 17:28:28 +0200747 if (notMyScheme(deviceId)) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700748 // not under my scheme, skipping
749 log.debug("{} is not my scheme, skipping", deviceId);
750 return;
751 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200752 final boolean configComplete = withDeviceLock(
753 () -> isDeviceConfigComplete(event, deviceId), deviceId);
754 if (!configComplete) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200755 // Still waiting for some configuration.
Andrea Campanellabc112a92017-06-26 19:06:43 +0200756 return;
757 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200758 // Good to go.
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200759 triggerConnect(deviceId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200760 cleanUpConfigInfo(deviceId);
Andrea Campanella14e196d2017-07-24 18:11:36 +0200761 }
762
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200763 private boolean isDeviceConfigComplete(NetworkConfigEvent event, DeviceId deviceId) {
764 // FIXME to be removed when netcfg will issue device events in a bundle or
Andrea Campanellabc112a92017-06-26 19:06:43 +0200765 // ensure all configuration needed is present
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200766 if (event.configClass().equals(GeneralProviderDeviceConfig.class)) {
767 //FIXME we currently assume that p4runtime devices are pipeline configurable.
768 //If we want to connect a p4runtime device with no pipeline
769 if (event.config().isPresent()) {
770 deviceConfigured.add(deviceId);
771 final boolean isNotPipelineConfigurable = Collections.disjoint(
772 ImmutableSet.copyOf(event.config().get().node().fieldNames()),
773 PIPELINE_CONFIGURABLE_PROTOCOLS);
774 if (isNotPipelineConfigurable) {
775 // Skip waiting for a pipeline if we can't support it.
Andrea Campanellabc112a92017-06-26 19:06:43 +0200776 pipelineConfigured.add(deviceId);
777 }
778 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200779 } else if (event.configClass().equals(BasicDeviceConfig.class)) {
780 if (event.config().isPresent() && event.config().get().node().has(DRIVER)) {
781 driverConfigured.add(deviceId);
Andrea Campanellabc112a92017-06-26 19:06:43 +0200782 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200783 } else if (event.configClass().equals(PiPipeconfConfig.class)) {
784 if (event.config().isPresent()
785 && event.config().get().node().has(PiPipeconfConfig.PIPIPECONFID)) {
786 pipelineConfigured.add(deviceId);
787 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700788 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200789
790 if (deviceConfigured.contains(deviceId)
791 && driverConfigured.contains(deviceId)
792 && pipelineConfigured.contains(deviceId)) {
793 return true;
794 } else {
795 if (deviceConfigured.contains(deviceId) && driverConfigured.contains(deviceId)) {
796 log.debug("Waiting for pipeline configuration for device {}", deviceId);
797 } else if (pipelineConfigured.contains(deviceId) && driverConfigured.contains(deviceId)) {
798 log.debug("Waiting for device configuration for device {}", deviceId);
799 } else if (pipelineConfigured.contains(deviceId) && deviceConfigured.contains(deviceId)) {
800 log.debug("Waiting for driver configuration for device {}", deviceId);
801 } else if (driverConfigured.contains(deviceId)) {
802 log.debug("Only driver configuration for device {}", deviceId);
803 } else if (deviceConfigured.contains(deviceId)) {
804 log.debug("Only device configuration for device {}", deviceId);
805 }
806 }
807 return false;
Andrea Campanella241896c2017-05-10 13:11:04 -0700808 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700809 }
810
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700811 private boolean isPipelineReady(DeviceId deviceId) {
812 final boolean isPipelineProg = isPipelineProgrammable(deviceId);
813 final boolean isPipeconfReady = pipeconfWatchdogService
814 .getStatus(deviceId)
815 .equals(PiPipeconfWatchdogService.PipelineStatus.READY);
816 return !isPipelineProg || isPipeconfReady;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200817 }
818
819 private void cleanUpConfigInfo(DeviceId deviceId) {
Andrea Campanellabc112a92017-06-26 19:06:43 +0200820 deviceConfigured.remove(deviceId);
821 driverConfigured.remove(deviceId);
822 pipelineConfigured.remove(deviceId);
Andrea Campanellabc112a92017-06-26 19:06:43 +0200823 }
824
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200825 private void startStatsPolling(DeviceId deviceId, boolean withRandomDelay) {
826 statsPollingTasks.compute(deviceId, (did, oldTask) -> {
827 if (oldTask != null) {
828 oldTask.cancel(false);
829 }
830 final int delay = withRandomDelay
831 ? new SecureRandom().nextInt(10) : 0;
832 return statsExecutor.scheduleAtFixedRate(
833 exceptionSafe(() -> updatePortStatistics(deviceId)),
834 delay, statsPollFrequency, TimeUnit.SECONDS);
835 });
Andrea Campanella19090322017-08-22 10:31:37 +0200836 }
837
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200838 private void cancelStatsPolling(DeviceId deviceId) {
839 statsPollingTasks.computeIfPresent(deviceId, (did, task) -> {
840 task.cancel(false);
841 return null;
842 });
843 }
844
845 private void rescheduleStatsPollingTasks() {
846 statsPollingTasks.keySet().forEach(deviceId -> {
847 // startStatsPolling cancels old one if present.
848 startStatsPolling(deviceId, true);
849 });
850 }
851
852 private void triggerProbeAllDevices() {
853 // Async trigger a task for all devices in the cfg.
854 cfgService.getSubjects(DeviceId.class, GeneralProviderDeviceConfig.class)
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700855 .forEach(this::triggerDeviceProbe);
Andrea Campanella1e573442018-05-17 17:07:13 +0200856 }
857
Carmelo Cascone92044522018-06-29 19:00:59 +0200858 private PiPipeconfId getPipeconfFromCfg(DeviceId deviceId) {
859 PiPipeconfConfig config = cfgService.getConfig(
860 deviceId, PiPipeconfConfig.class);
861 if (config == null) {
862 return null;
863 }
864 return config.piPipeconfId();
865 }
866
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700867 private void triggerDeviceProbe(DeviceId deviceId) {
868 connectionExecutor.execute(withDeviceLock(
869 () -> doDeviceProbe(deviceId), deviceId));
870 }
871
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200872 private void doDeviceProbe(DeviceId deviceId) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700873 log.debug("Probing device {}...", deviceId);
874 if (configIsMissing(deviceId)) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200875 return;
876 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700877 if (!isConnected(deviceId)) {
878 if (deviceService.isAvailable(deviceId)) {
879 providerService.deviceDisconnected(deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200880 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200881 triggerConnect(deviceId);
Andrea Campanella1e573442018-05-17 17:07:13 +0200882 }
883 }
884
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700885 private boolean configIsMissing(DeviceId deviceId) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200886 final boolean present =
887 cfgService.getConfig(deviceId, GeneralProviderDeviceConfig.class) != null
888 && cfgService.getConfig(deviceId, BasicDeviceConfig.class) != null;
Andrea Campanella1e573442018-05-17 17:07:13 +0200889 if (!present) {
890 log.warn("Configuration for device {} is not complete", deviceId);
891 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700892 return !present;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200893 }
894
895 private void handleMastershipResponse(DeviceId deviceId, MastershipRole response) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700896 // Notify core about mastership response.
897 final MastershipRole request = requestedRoles.get(deviceId);
898 final boolean isAvailable = deviceService.isAvailable(deviceId);
899 if (request == null || !isAvailable) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200900 return;
901 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700902 log.debug("Device {} asserted role {} (requested was {})",
903 deviceId, response, request);
904 providerService.receivedRoleReply(deviceId, request, response);
905 // FIXME: this should be based on assigned mastership, not what returned by device
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200906 if (response.equals(MastershipRole.MASTER)) {
907 startStatsPolling(deviceId, false);
908 } else {
909 cancelStatsPolling(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200910 }
911 }
912
Carmelo Casconede3b6842018-09-05 17:45:10 -0700913 private void handleNotMaster(DeviceId deviceId) {
914 log.warn("Device {} notified that this node is not master, " +
915 "relinquishing mastership...", deviceId);
916 mastershipService.relinquishMastership(deviceId);
917 }
918
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200919 private <U> U getFutureWithDeadline(CompletableFuture<U> future, String opDescription,
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200920 DeviceId deviceId, U defaultValue, int timeout) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200921 try {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200922 return future.get(timeout, TimeUnit.SECONDS);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200923 } catch (InterruptedException e) {
924 log.error("Thread interrupted while {} on {}", opDescription, deviceId);
925 Thread.currentThread().interrupt();
926 } catch (ExecutionException e) {
927 log.error("Exception while {} on {}", opDescription, deviceId, e.getCause());
928 } catch (TimeoutException e) {
929 log.error("Operation TIMEOUT while {} on {}", opDescription, deviceId);
930 }
931 return defaultValue;
932 }
933
Andrea Campanella241896c2017-05-10 13:11:04 -0700934 /**
935 * Listener for core device events.
936 */
937 private class InternalDeviceListener implements DeviceListener {
938 @Override
939 public void event(DeviceEvent event) {
Andrea Campanellace111932017-09-18 16:59:56 +0900940 DeviceId deviceId = event.subject().id();
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700941 // For now this is scheduled periodically, when streaming API will
942 // be available we check and base it on the streaming API (e.g. gNMI)
943 if (mastershipService.isLocalMaster(deviceId)) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200944 startStatsPolling(deviceId, true);
Andrea Campanella241896c2017-05-10 13:11:04 -0700945 }
946 }
947
948 @Override
949 public boolean isRelevant(DeviceEvent event) {
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700950 return event.type() == Type.DEVICE_ADDED &&
951 event.subject().id().toString().startsWith(URI_SCHEME.toLowerCase());
Andrea Campanella241896c2017-05-10 13:11:04 -0700952 }
953 }
Andrea Campanella1e573442018-05-17 17:07:13 +0200954
955 /**
Carmelo Casconee5b28722018-06-22 17:28:28 +0200956 * Listener for device agent events.
Andrea Campanella1e573442018-05-17 17:07:13 +0200957 */
Carmelo Casconee5b28722018-06-22 17:28:28 +0200958 private class InternalDeviceAgentListener implements DeviceAgentListener {
Andrea Campanella1e573442018-05-17 17:07:13 +0200959
960 @Override
Carmelo Casconee5b28722018-06-22 17:28:28 +0200961 public void event(DeviceAgentEvent event) {
Andrea Campanella1e573442018-05-17 17:07:13 +0200962 DeviceId deviceId = event.subject();
Carmelo Casconee5b28722018-06-22 17:28:28 +0200963 switch (event.type()) {
964 case CHANNEL_OPEN:
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700965 triggerAdvertiseDevice(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200966 break;
967 case CHANNEL_CLOSED:
Carmelo Casconee5b28722018-06-22 17:28:28 +0200968 case CHANNEL_ERROR:
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700969 triggerDeviceProbe(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200970 break;
971 case ROLE_MASTER:
972 handleMastershipResponse(deviceId, MastershipRole.MASTER);
973 break;
974 case ROLE_STANDBY:
975 handleMastershipResponse(deviceId, MastershipRole.STANDBY);
976 break;
977 case ROLE_NONE:
978 handleMastershipResponse(deviceId, MastershipRole.NONE);
979 break;
Carmelo Casconede3b6842018-09-05 17:45:10 -0700980 case NOT_MASTER:
981 handleNotMaster(deviceId);
982 break;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200983 default:
984 log.warn("Unrecognized device agent event {}", event.type());
Andrea Campanella1e573442018-05-17 17:07:13 +0200985 }
Andrea Campanella1e573442018-05-17 17:07:13 +0200986 }
987
988 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700989
990 private class InternalPipeconfWatchdogListener implements PiPipeconfWatchdogListener {
991 @Override
992 public void event(PiPipeconfWatchdogEvent event) {
993 triggerMarkAvailable(event.subject());
994 }
995
996 @Override
997 public boolean isRelevant(PiPipeconfWatchdogEvent event) {
998 return event.type().equals(PiPipeconfWatchdogEvent.Type.PIPELINE_READY);
999 }
1000 }
1001
1002 private class InternalConfigFactory
1003 extends ConfigFactory<DeviceId, GeneralProviderDeviceConfig> {
1004
1005 InternalConfigFactory() {
1006 super(SubjectFactories.DEVICE_SUBJECT_FACTORY,
1007 GeneralProviderDeviceConfig.class, CFG_SCHEME);
1008 }
1009
1010 @Override
1011 public GeneralProviderDeviceConfig createConfig() {
1012 return new GeneralProviderDeviceConfig();
1013 }
1014 }
Andrea Campanella241896c2017-05-10 13:11:04 -07001015}