blob: 21c6ae719129459128c11f3d4a44e9dbc71137fb [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
Carmelo Cascone158b8c42018-07-04 19:42:37 +020019import com.fasterxml.jackson.databind.JsonNode;
20import com.fasterxml.jackson.databind.ObjectMapper;
21import com.fasterxml.jackson.databind.node.ObjectNode;
Andrea Campanella241896c2017-05-10 13:11:04 -070022import com.google.common.annotations.Beta;
Andrea Campanellabc112a92017-06-26 19:06:43 +020023import com.google.common.collect.ImmutableSet;
24import com.google.common.collect.Maps;
Carmelo Cascone158b8c42018-07-04 19:42:37 +020025import com.google.common.util.concurrent.Striped;
Andrea Campanella241896c2017-05-10 13:11:04 -070026import org.apache.felix.scr.annotations.Activate;
27import org.apache.felix.scr.annotations.Component;
28import org.apache.felix.scr.annotations.Deactivate;
Andrea Campanella19090322017-08-22 10:31:37 +020029import org.apache.felix.scr.annotations.Modified;
30import org.apache.felix.scr.annotations.Property;
Andrea Campanella241896c2017-05-10 13:11:04 -070031import org.apache.felix.scr.annotations.Reference;
32import org.apache.felix.scr.annotations.ReferenceCardinality;
33import org.onlab.packet.ChassisId;
34import org.onlab.util.ItemNotFoundException;
Andrea Campanella19090322017-08-22 10:31:37 +020035import org.onlab.util.Tools;
Andrea Campanella4929a812017-10-09 18:38:23 +020036import org.onosproject.cfg.ComponentConfigService;
Andrea Campanella14e196d2017-07-24 18:11:36 +020037import org.onosproject.cluster.ClusterService;
38import org.onosproject.cluster.LeadershipService;
Andrea Campanella241896c2017-05-10 13:11:04 -070039import org.onosproject.core.CoreService;
Andrea Campanella14e196d2017-07-24 18:11:36 +020040import org.onosproject.mastership.MastershipService;
Andrea Campanella241896c2017-05-10 13:11:04 -070041import org.onosproject.net.AnnotationKeys;
42import org.onosproject.net.DefaultAnnotations;
43import org.onosproject.net.Device;
44import org.onosproject.net.DeviceId;
45import org.onosproject.net.MastershipRole;
46import org.onosproject.net.PortNumber;
Carmelo Cascone87892e22017-11-13 16:01:29 -080047import org.onosproject.net.behaviour.PiPipelineProgrammable;
Andrea Campanella241896c2017-05-10 13:11:04 -070048import org.onosproject.net.behaviour.PortAdmin;
49import org.onosproject.net.config.ConfigFactory;
50import org.onosproject.net.config.NetworkConfigEvent;
51import org.onosproject.net.config.NetworkConfigListener;
52import org.onosproject.net.config.NetworkConfigRegistry;
53import org.onosproject.net.config.basics.BasicDeviceConfig;
54import org.onosproject.net.config.basics.SubjectFactories;
55import org.onosproject.net.device.DefaultDeviceDescription;
Carmelo Casconee5b28722018-06-22 17:28:28 +020056import org.onosproject.net.device.DeviceAgentEvent;
57import org.onosproject.net.device.DeviceAgentListener;
Andrea Campanella241896c2017-05-10 13:11:04 -070058import org.onosproject.net.device.DeviceDescription;
59import org.onosproject.net.device.DeviceDescriptionDiscovery;
60import org.onosproject.net.device.DeviceEvent;
61import org.onosproject.net.device.DeviceHandshaker;
62import org.onosproject.net.device.DeviceListener;
63import org.onosproject.net.device.DeviceProvider;
64import org.onosproject.net.device.DeviceProviderRegistry;
65import org.onosproject.net.device.DeviceProviderService;
66import org.onosproject.net.device.DeviceService;
67import org.onosproject.net.device.PortDescription;
68import org.onosproject.net.device.PortStatistics;
69import org.onosproject.net.device.PortStatisticsDiscovery;
70import org.onosproject.net.driver.Behaviour;
71import org.onosproject.net.driver.DefaultDriverData;
72import org.onosproject.net.driver.DefaultDriverHandler;
73import org.onosproject.net.driver.Driver;
74import org.onosproject.net.driver.DriverData;
75import org.onosproject.net.driver.DriverService;
Carmelo Cascone59f57de2017-07-11 19:55:09 -040076import org.onosproject.net.pi.model.PiPipeconf;
Andrea Campanellabc112a92017-06-26 19:06:43 +020077import org.onosproject.net.pi.model.PiPipeconfId;
Carmelo Cascone39c28ca2017-11-15 13:03:57 -080078import org.onosproject.net.pi.service.PiPipeconfConfig;
79import org.onosproject.net.pi.service.PiPipeconfService;
Andrea Campanella241896c2017-05-10 13:11:04 -070080import org.onosproject.net.provider.AbstractProvider;
81import org.onosproject.net.provider.ProviderId;
82import org.onosproject.provider.general.device.api.GeneralProviderDeviceConfig;
Andrea Campanella19090322017-08-22 10:31:37 +020083import org.osgi.service.component.ComponentContext;
Andrea Campanella241896c2017-05-10 13:11:04 -070084import org.slf4j.Logger;
85
Ray Milkeyb68bbbc2017-12-18 10:05:49 -080086import java.security.SecureRandom;
Andrea Campanella241896c2017-05-10 13:11:04 -070087import java.util.ArrayList;
88import java.util.Collection;
Andrea Campanellabc112a92017-06-26 19:06:43 +020089import java.util.Collections;
Andrea Campanella19090322017-08-22 10:31:37 +020090import java.util.Dictionary;
Andrea Campanella241896c2017-05-10 13:11:04 -070091import java.util.List;
Thomas Vachuska5b38dc02018-05-10 15:24:40 -070092import java.util.Map;
Andrea Campanellabc112a92017-06-26 19:06:43 +020093import java.util.Set;
Andrea Campanella241896c2017-05-10 13:11:04 -070094import java.util.concurrent.CompletableFuture;
Andrea Campanella19090322017-08-22 10:31:37 +020095import java.util.concurrent.ConcurrentHashMap;
Andrea Campanellabc112a92017-06-26 19:06:43 +020096import java.util.concurrent.ConcurrentMap;
97import java.util.concurrent.CopyOnWriteArraySet;
Andrea Campanella241896c2017-05-10 13:11:04 -070098import java.util.concurrent.ExecutionException;
Carmelo Casconee5b28722018-06-22 17:28:28 +020099import java.util.concurrent.ExecutorService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700100import java.util.concurrent.ScheduledExecutorService;
Andrea Campanella19090322017-08-22 10:31:37 +0200101import java.util.concurrent.ScheduledFuture;
Andrea Campanella241896c2017-05-10 13:11:04 -0700102import java.util.concurrent.TimeUnit;
103import java.util.concurrent.TimeoutException;
Andrea Campanellabc112a92017-06-26 19:06:43 +0200104import java.util.concurrent.locks.Lock;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200105import java.util.function.Supplier;
Andrea Campanella241896c2017-05-10 13:11:04 -0700106
Carmelo Casconee5b28722018-06-22 17:28:28 +0200107import static java.util.concurrent.Executors.newFixedThreadPool;
Andrea Campanella241896c2017-05-10 13:11:04 -0700108import static java.util.concurrent.Executors.newScheduledThreadPool;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200109import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
Andrea Campanella241896c2017-05-10 13:11:04 -0700110import static org.onlab.util.Tools.groupedThreads;
111import static org.onosproject.net.device.DeviceEvent.Type;
112import static org.slf4j.LoggerFactory.getLogger;
113
114/**
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200115 * Provider which uses drivers to detect device and do initial handshake and
116 * channel establishment with devices. Any other provider specific operation is
117 * also delegated to the DeviceHandshaker driver.
Andrea Campanella241896c2017-05-10 13:11:04 -0700118 */
119@Beta
120@Component(immediate = true)
121public class GeneralDeviceProvider extends AbstractProvider
122 implements DeviceProvider {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200123
Carmelo Casconee5b28722018-06-22 17:28:28 +0200124 private static final String DRIVER = "driver";
Andrea Campanella14e196d2017-07-24 18:11:36 +0200125
Andrea Campanella241896c2017-05-10 13:11:04 -0700126 private final Logger log = getLogger(getClass());
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200129 private DeviceProviderRegistry providerRegistry;
Andrea Campanella241896c2017-05-10 13:11:04 -0700130
131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200132 private ComponentConfigService componentConfigService;
Andrea Campanella4929a812017-10-09 18:38:23 +0200133
134 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200135 private NetworkConfigRegistry cfgService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700136
137 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200138 private CoreService coreService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700139
140 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200141 private DeviceService deviceService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700142
143 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200144 private DriverService driverService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700145
146 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200147 private MastershipService mastershipService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200148
149 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200150 private PiPipeconfService piPipeconfService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700151
Andrea Campanella14e196d2017-07-24 18:11:36 +0200152 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200153 private ClusterService clusterService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200154
155 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200156 private LeadershipService leadershipService;
Andrea Campanella14e196d2017-07-24 18:11:36 +0200157
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200158 private static final String STATS_POLL_FREQUENCY = "deviceStatsPollFrequency";
159 private static final int DEFAULT_STATS_POLL_FREQUENCY = 10;
160 @Property(name = STATS_POLL_FREQUENCY, intValue = DEFAULT_STATS_POLL_FREQUENCY,
Andrea Campanella19090322017-08-22 10:31:37 +0200161 label = "Configure poll frequency for port status and statistics; " +
162 "default is 10 sec")
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200163 private int statsPollFrequency = DEFAULT_STATS_POLL_FREQUENCY;
Andrea Campanella19090322017-08-22 10:31:37 +0200164
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200165 private static final String PROBE_FREQUENCY = "deviceProbeFrequency";
166 private static final int DEFAULT_PROBE_FREQUENCY = 10;
167 @Property(name = PROBE_FREQUENCY, intValue = DEFAULT_PROBE_FREQUENCY,
168 label = "Configure probe frequency for checking device availability; " +
Andrea Campanella1e573442018-05-17 17:07:13 +0200169 "default is 10 sec")
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200170 private int probeFrequency = DEFAULT_PROBE_FREQUENCY;
171
172 private static final String OP_TIMEOUT_SHORT = "deviceOperationTimeoutShort";
173 private static final int DEFAULT_OP_TIMEOUT_SHORT = 10;
174 @Property(name = OP_TIMEOUT_SHORT, intValue = DEFAULT_OP_TIMEOUT_SHORT,
175 label = "Configure timeout in seconds for device operations " +
176 "that are supposed to take a short time " +
177 "(e.g. checking device reachability); default is 10 seconds")
178 private int opTimeoutShort = DEFAULT_OP_TIMEOUT_SHORT;
179
180 private static final String OP_TIMEOUT_LONG = "deviceOperationTimeoutLong";
181 private static final int DEFAULT_OP_TIMEOUT_LONG = 60;
182 @Property(name = OP_TIMEOUT_LONG, intValue = DEFAULT_OP_TIMEOUT_LONG,
183 label = "Configure timeout in seconds for device operations " +
184 "that are supposed to take a relatively long time " +
185 "(e.g. pushing a large pipeline configuration with slow " +
186 "network); default is 60 seconds")
187 private int opTimeoutLong = DEFAULT_OP_TIMEOUT_LONG;
Andrea Campanella1e573442018-05-17 17:07:13 +0200188
Carmelo Casconee5b28722018-06-22 17:28:28 +0200189 private static final String APP_NAME = "org.onosproject.generaldeviceprovider";
190 private static final String URI_SCHEME = "device";
191 private static final String CFG_SCHEME = "generalprovider";
Andrea Campanella241896c2017-05-10 13:11:04 -0700192 private static final String DEVICE_PROVIDER_PACKAGE = "org.onosproject.general.provider.device";
193 private static final int CORE_POOL_SIZE = 10;
194 private static final String UNKNOWN = "unknown";
Andrea Campanella19090322017-08-22 10:31:37 +0200195
Andrea Campanellabc112a92017-06-26 19:06:43 +0200196 //FIXME this will be removed when the configuration is synced at the source.
197 private static final Set<String> PIPELINE_CONFIGURABLE_PROTOCOLS = ImmutableSet.of("p4runtime");
Andrea Campanellabc112a92017-06-26 19:06:43 +0200198
199 //FIXME to be removed when netcfg will issue device events in a bundle or
200 //ensures all configuration needed is present
201 private Set<DeviceId> deviceConfigured = new CopyOnWriteArraySet<>();
202 private Set<DeviceId> driverConfigured = new CopyOnWriteArraySet<>();
203 private Set<DeviceId> pipelineConfigured = new CopyOnWriteArraySet<>();
Andrea Campanella241896c2017-05-10 13:11:04 -0700204
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700205 private final Map<DeviceId, DeviceHandshaker> handshakers = Maps.newConcurrentMap();
Carmelo Casconee5b28722018-06-22 17:28:28 +0200206 private final Map<DeviceId, MastershipRole> requestedRoles = Maps.newConcurrentMap();
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200207 private final Striped<Lock> deviceLocks = Striped.lock(30);
Andrea Campanella241896c2017-05-10 13:11:04 -0700208
Carmelo Casconee5b28722018-06-22 17:28:28 +0200209 private ExecutorService connectionExecutor
210 = newFixedThreadPool(CORE_POOL_SIZE, groupedThreads(
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200211 "onos/generaldeviceprovider-device-connect", "%d", log));
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200212 private ScheduledExecutorService statsExecutor
Carmelo Casconee5b28722018-06-22 17:28:28 +0200213 = newScheduledThreadPool(CORE_POOL_SIZE, groupedThreads(
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200214 "onos/generaldeviceprovider-stats-poll", "%d", log));
215 private ConcurrentMap<DeviceId, ScheduledFuture<?>> statsPollingTasks = new ConcurrentHashMap<>();
216 private ScheduledExecutorService probeExecutor
217 = newSingleThreadScheduledExecutor(groupedThreads(
218 "onos/generaldeviceprovider-probe-", "%d", log));
219 private ScheduledFuture<?> probeTask = null;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200220
221 private DeviceProviderService providerService;
Andrea Campanella241896c2017-05-10 13:11:04 -0700222 private InternalDeviceListener deviceListener = new InternalDeviceListener();
223
Carmelo Casconee5b28722018-06-22 17:28:28 +0200224 private final ConfigFactory factory =
Andrea Campanella241896c2017-05-10 13:11:04 -0700225 new ConfigFactory<DeviceId, GeneralProviderDeviceConfig>(
226 SubjectFactories.DEVICE_SUBJECT_FACTORY,
227 GeneralProviderDeviceConfig.class, CFG_SCHEME) {
228 @Override
229 public GeneralProviderDeviceConfig createConfig() {
230 return new GeneralProviderDeviceConfig();
231 }
232 };
233
Carmelo Casconee5b28722018-06-22 17:28:28 +0200234 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
235 private final DeviceAgentListener deviceAgentListener = new InternalDeviceAgentListener();
Andrea Campanella241896c2017-05-10 13:11:04 -0700236
237
238 @Activate
Andrea Campanella1e573442018-05-17 17:07:13 +0200239 public void activate(ComponentContext context) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700240 providerService = providerRegistry.register(this);
Andrea Campanella4929a812017-10-09 18:38:23 +0200241 componentConfigService.registerProperties(getClass());
Andrea Campanella241896c2017-05-10 13:11:04 -0700242 coreService.registerApplication(APP_NAME);
243 cfgService.registerConfigFactory(factory);
244 cfgService.addListener(cfgListener);
245 deviceService.addListener(deviceListener);
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700246 handshakers.clear();
Andrea Campanella241896c2017-05-10 13:11:04 -0700247 //This will fail if ONOS has CFG and drivers which depend on this provider
248 // are activated, failing due to not finding the driver.
249 cfgService.getSubjects(DeviceId.class, GeneralProviderDeviceConfig.class)
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200250 .forEach(this::triggerConnect);
Andrea Campanella1e573442018-05-17 17:07:13 +0200251 //Initiating a periodic check to see if any device is available again and reconnect it.
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200252 rescheduleProbeTask();
Andrea Campanella1e573442018-05-17 17:07:13 +0200253 modified(context);
Andrea Campanella241896c2017-05-10 13:11:04 -0700254 log.info("Started");
255 }
256
Andrea Campanella19090322017-08-22 10:31:37 +0200257 @Modified
258 public void modified(ComponentContext context) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200259 if (context == null) {
260 return;
Andrea Campanella19090322017-08-22 10:31:37 +0200261 }
262
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200263 Dictionary<?, ?> properties = context.getProperties();
264 final int oldStatsPollFrequency = statsPollFrequency;
265 statsPollFrequency = Tools.getIntegerProperty(
266 properties, STATS_POLL_FREQUENCY, DEFAULT_STATS_POLL_FREQUENCY);
267 log.info("Configured. {} is configured to {} seconds",
268 STATS_POLL_FREQUENCY, statsPollFrequency);
269 final int oldProbeFrequency = probeFrequency;
270 probeFrequency = Tools.getIntegerProperty(
271 properties, PROBE_FREQUENCY, DEFAULT_PROBE_FREQUENCY);
272 log.info("Configured. {} is configured to {} seconds",
273 PROBE_FREQUENCY, probeFrequency);
274 opTimeoutShort = Tools.getIntegerProperty(
275 properties, OP_TIMEOUT_SHORT, DEFAULT_OP_TIMEOUT_SHORT);
276 log.info("Configured. {} is configured to {} seconds",
277 OP_TIMEOUT_SHORT, opTimeoutShort);
278 opTimeoutLong = Tools.getIntegerProperty(
279 properties, OP_TIMEOUT_LONG, DEFAULT_OP_TIMEOUT_LONG);
280 log.info("Configured. {} is configured to {} seconds",
281 OP_TIMEOUT_LONG, opTimeoutLong);
282
283 if (oldStatsPollFrequency != statsPollFrequency) {
284 rescheduleStatsPollingTasks();
Andrea Campanella19090322017-08-22 10:31:37 +0200285 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200286
287 if (oldProbeFrequency != probeFrequency) {
288 rescheduleProbeTask();
289 }
290 }
291
292 private synchronized void rescheduleProbeTask() {
293 if (probeTask != null) {
294 probeTask.cancel(false);
295 }
296 probeTask = probeExecutor.scheduleAtFixedRate(
297 this::triggerProbeAllDevices, probeFrequency,
298 probeFrequency, TimeUnit.SECONDS);
Andrea Campanella19090322017-08-22 10:31:37 +0200299 }
300
Andrea Campanella241896c2017-05-10 13:11:04 -0700301 @Deactivate
302 public void deactivate() {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200303 statsExecutor.shutdown();
304 probeExecutor.shutdown();
Andrea Campanella4929a812017-10-09 18:38:23 +0200305 componentConfigService.unregisterProperties(getClass(), false);
Andrea Campanella241896c2017-05-10 13:11:04 -0700306 cfgService.removeListener(cfgListener);
307 //Not Removing the device so they can still be used from other driver providers
308 //cfgService.getSubjects(DeviceId.class, GeneralProviderDeviceConfig.class)
309 // .forEach(did -> connectionExecutor.execute(() -> disconnectDevice(did)));
310 connectionExecutor.shutdown();
311 deviceService.removeListener(deviceListener);
312 providerRegistry.unregister(this);
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700313 handshakers.clear();
Andrea Campanella241896c2017-05-10 13:11:04 -0700314 providerService = null;
315 cfgService.unregisterConfigFactory(factory);
316 log.info("Stopped");
317 }
318
319 public GeneralDeviceProvider() {
320 super(new ProviderId(URI_SCHEME, DEVICE_PROVIDER_PACKAGE));
321 }
322
323
324 @Override
325 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200326 connectionExecutor.execute(withDeviceLock(
327 () -> doDeviceProbe(deviceId), deviceId));
Andrea Campanella241896c2017-05-10 13:11:04 -0700328 }
329
330 @Override
331 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Andrea Campanella14e196d2017-07-24 18:11:36 +0200332 log.info("Received role {} for device {}", newRole, deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200333 requestedRoles.put(deviceId, newRole);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200334 connectionExecutor.execute(() -> doRoleChanged(deviceId, newRole));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200335 }
336
337 private void doRoleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200338 final DeviceHandshaker handshaker = getHandshaker(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200339 if (handshaker == null) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200340 log.error("Null handshaker. Unable to notify new role {} to {}",
341 newRole, deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200342 return;
343 }
344 handshaker.roleChanged(newRole);
Andrea Campanella241896c2017-05-10 13:11:04 -0700345 }
346
347 @Override
348 public boolean isReachable(DeviceId deviceId) {
Andrea Campanella14e196d2017-07-24 18:11:36 +0200349 log.debug("Testing reachability for device {}", deviceId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200350 final DeviceHandshaker handshaker = getHandshaker(deviceId);
Andrea Campanellac1ecdd02018-01-12 12:48:24 +0100351 if (handshaker == null) {
352 return false;
353 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200354 return getFutureWithDeadline(
355 handshaker.isReachable(), "checking reachability",
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200356 deviceId, false, opTimeoutShort);
Andrea Campanella241896c2017-05-10 13:11:04 -0700357 }
358
359 @Override
360 public void changePortState(DeviceId deviceId, PortNumber portNumber,
361 boolean enable) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200362 connectionExecutor.execute(
363 () -> doChangePortState(deviceId, portNumber, enable));
364 }
365
366 private void doChangePortState(DeviceId deviceId, PortNumber portNumber,
367 boolean enable) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200368 if (!deviceService.getDevice(deviceId).is(PortAdmin.class)) {
369 log.warn("Missing PortAdmin behaviour on {}, aborting port state change",
370 deviceId);
371 return;
Andrea Campanella241896c2017-05-10 13:11:04 -0700372 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200373 final PortAdmin portAdmin = deviceService.getDevice(deviceId)
374 .as(PortAdmin.class);
375 final CompletableFuture<Boolean> modifyTask = enable
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200376 ? portAdmin.enable(portNumber)
377 : portAdmin.disable(portNumber);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200378 final String descr = (enable ? "enabling" : "disabling") + " port " + portNumber;
379 getFutureWithDeadline(
380 modifyTask, descr, deviceId, null, opTimeoutShort);
Andrea Campanella241896c2017-05-10 13:11:04 -0700381 }
382
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700383 @Override
384 public void triggerDisconnect(DeviceId deviceId) {
Andrea Campanella1e573442018-05-17 17:07:13 +0200385 log.debug("Triggering disconnection of device {}", deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200386 connectionExecutor.execute(withDeviceLock(
387 () -> doDisconnectDevice(deviceId), deviceId));
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700388 }
389
Andrea Campanella241896c2017-05-10 13:11:04 -0700390 private DeviceHandshaker getHandshaker(DeviceId deviceId) {
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700391 return handshakers.computeIfAbsent(deviceId, id -> {
392 Driver driver = getDriver(deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200393 return driver == null ? null : getBehaviour(
394 driver, DeviceHandshaker.class,
395 new DefaultDriverData(driver, deviceId));
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700396 });
Andrea Campanella241896c2017-05-10 13:11:04 -0700397 }
398
Andrea Campanella241896c2017-05-10 13:11:04 -0700399 private Driver getDriver(DeviceId deviceId) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700400 try {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200401 // DriverManager checks first using basic device config.
402 return driverService.getDriver(deviceId);
Andrea Campanella241896c2017-05-10 13:11:04 -0700403 } catch (ItemNotFoundException e) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200404 log.error("Driver not found for {}", deviceId);
405 return null;
Andrea Campanella241896c2017-05-10 13:11:04 -0700406 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700407 }
408
Andrea Campanella241896c2017-05-10 13:11:04 -0700409 private <T extends Behaviour> T getBehaviour(Driver driver, Class<T> type,
410 DriverData data) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200411 // Allows obtaining behavior implementations before the device is pushed
412 // to the core.
Andrea Campanellac1ecdd02018-01-12 12:48:24 +0100413 if (driver != null && driver.hasBehaviour(type)) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700414 DefaultDriverHandler handler = new DefaultDriverHandler(data);
415 return driver.createBehaviour(handler, type);
416 } else {
417 return null;
418 }
419 }
420
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200421 private void doConnectDevice(DeviceId deviceId) {
422 // Some operations can be performed by one node only.
423 final boolean isLocalLeader = leadershipService.runForLeadership(
424 GeneralProviderDeviceConfig.class.getName() + deviceId)
425 .leader().nodeId().equals(clusterService.getLocalNode().id());
426
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200427 if (deviceService.getDevice(deviceId) != null
428 && deviceService.isAvailable(deviceId)) {
429 log.info("Device {} is already connected to ONOS and is available",
430 deviceId);
431 return;
432 }
433 // Retrieve config
434 final GeneralProviderDeviceConfig providerConfig = cfgService.getConfig(
435 deviceId, GeneralProviderDeviceConfig.class);
436 final BasicDeviceConfig basicDeviceConfig = cfgService.getConfig(
437 deviceId, BasicDeviceConfig.class);
Andrea Campanella241896c2017-05-10 13:11:04 -0700438 if (providerConfig == null || basicDeviceConfig == null) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200439 log.error("Configuration missing, cannot connect to {}. " +
440 "basicDeviceConfig={}, generalProvider={}",
441 deviceId, basicDeviceConfig, providerConfig);
442 return;
443 }
444 log.info("Initiating connection to device {} with driver {} ... asMaster={}",
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200445 deviceId, basicDeviceConfig.driver(), isLocalLeader);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200446 // Get handshaker, driver and driver data.
447 final DeviceHandshaker handshaker = getHandshaker(deviceId);
448 if (handshaker == null) {
449 log.error("Missing DeviceHandshaker behavior for {}, aborting connection",
450 deviceId);
451 return;
452 }
453 final Driver driver = handshaker.handler().driver();
454 // Enhance driver data with info in GDP config.
455 augmentConfigData(providerConfig, handshaker.data());
456 final DriverData driverData = handshaker.data();
457 // Start connection via handshaker.
458 final Boolean connectSuccess = getFutureWithDeadline(
459 handshaker.connect(), "initiating connection",
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200460 deviceId, null, opTimeoutShort);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200461 if (connectSuccess == null) {
462 // Error logged by getFutureWithDeadline().
463 return;
464 } else if (!connectSuccess) {
465 log.warn("Unable to connect to {}", deviceId);
466 return;
467 }
468 // Handle pipeconf (if device is capable)
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200469 if (!handlePipeconf(deviceId, driver, driverData, isLocalLeader)) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200470 // We already logged the error.
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200471 getFutureWithDeadline(
472 handshaker.disconnect(), "performing disconnection",
473 deviceId, null, opTimeoutShort);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200474 return;
475 }
476 // Add device agent listener.
477 handshaker.addDeviceAgentListener(deviceAgentListener);
478 // All good. Notify core (if master).
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200479 if (isLocalLeader) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200480 advertiseDevice(deviceId, driver, providerConfig, driverData);
Andrea Campanella241896c2017-05-10 13:11:04 -0700481 }
482 }
483
Andrea Campanella14e196d2017-07-24 18:11:36 +0200484
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200485 private void advertiseDevice(DeviceId deviceId, Driver driver,
486 GeneralProviderDeviceConfig providerConfig,
487 DriverData driverData) {
488 // Obtain device and port description and advertise device to core.
489 DeviceDescription description = null;
490 final List<PortDescription> ports;
491
492 final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
493 driver, DeviceDescriptionDiscovery.class, driverData);
494
495 if (deviceDiscovery != null) {
496 description = deviceDiscovery.discoverDeviceDetails();
497 ports = deviceDiscovery.discoverPortDetails();
498 } else {
499 log.warn("Missing DeviceDescriptionDiscovery behavior for {}, " +
500 "no update for description or ports.", deviceId);
501 ports = new ArrayList<>();
Andrea Campanella14e196d2017-07-24 18:11:36 +0200502 }
Andrea Campanella14e196d2017-07-24 18:11:36 +0200503
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200504 if (description == null) {
505 // Generate one here.
506 // FIXME: a behavior impl should not return a null description
507 // (e.g. as GnmiDeviceDescriptionDiscovery). This case should apply
508 // only if a the behavior is not available.
509 description = new DefaultDeviceDescription(
510 deviceId.uri(), Device.Type.SWITCH,
511 driver.manufacturer(), driver.hwVersion(),
512 driver.swVersion(), UNKNOWN,
513 new ChassisId(), true,
514 DefaultAnnotations.builder()
515 .set(AnnotationKeys.PROTOCOL,
516 providerConfig.protocolsInfo().keySet().toString())
517 .build());
518 }
519
520 providerService.deviceConnected(deviceId, description);
521 providerService.updatePorts(deviceId, ports);
Andrea Campanella14e196d2017-07-24 18:11:36 +0200522 }
523
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400524 /**
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200525 * Handles the case of a device that is pipeline programmable. Returns true
526 * if the operation wa successful and the device can be registered to the
527 * core, false otherwise.
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400528 */
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200529 private boolean handlePipeconf(DeviceId deviceId, Driver driver,
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200530 DriverData driverData, boolean asMaster) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200531 final PiPipelineProgrammable pipelineProg = getBehaviour(
532 driver, PiPipelineProgrammable.class, driverData);
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400533 if (pipelineProg == null) {
534 // Device is not pipeline programmable.
535 return true;
536 }
537
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200538 final PiPipeconf pipeconf = getPipeconf(deviceId, pipelineProg);
539 if (pipeconf == null) {
540 return false;
541 }
542 final PiPipeconfId pipeconfId = pipeconf.id();
Andrea Campanella14e196d2017-07-24 18:11:36 +0200543
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700544 // To be removed in change #19606
545 // final String mergedDriverName = piPipeconfService.mergeDriver(
546 // deviceId, pipeconfId);
547 // if (mergedDriverName == null) {
548 // log.error("Unable to get merged driver for {} and {}, aborting device discovery",
549 // deviceId, pipeconfId);
550 // return false;
551 // }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200552
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200553 if (!asMaster) {
554 // From now one only the master.
555 return true;
556 }
557
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700558 // if (!setDriverViaCfg(deviceId, mergedDriverName)) {
559 // return false;
560 // }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200561
562 // FIXME: we just introduced a race condition as it might happen that a
563 // node does not receive the new cfg (with the merged driver) before the
564 // device is advertised to the core. Perhaps we should be waiting for a
565 // NetworkConfig event signaling that the driver has been updated on all
566 // nodes? The effect is mitigated by deploying the pipeconf (slow
567 // operation), after calling setDriverViaCfg().
568
569 piPipeconfService.bindToDevice(pipeconfId, deviceId);
570
571 final Boolean deploySuccess = getFutureWithDeadline(
572 pipelineProg.deployPipeconf(pipeconf),
573 "deploying pipeconf", deviceId, null,
574 opTimeoutLong);
575 if (deploySuccess == null) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200576 // Error logged by getFutureWithDeadline().
577 return false;
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200578 } else if (!deploySuccess) {
579 log.error("Unable to deploy pipeconf {} to {}, aborting device discovery",
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200580 pipeconfId, deviceId);
Andrea Campanella14e196d2017-07-24 18:11:36 +0200581 return false;
582 }
583
584 return true;
585 }
586
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200587 private boolean setDriverViaCfg(DeviceId deviceId, String driverName) {
588 BasicDeviceConfig cfg = cfgService.getConfig(deviceId, BasicDeviceConfig.class);
589 if (cfg == null) {
590 log.error("Unable to get basic device config for {}, aborting device discovery",
591 deviceId);
592 return false;
593 }
594 ObjectNode newCfg = (ObjectNode) cfg.node();
595 newCfg = newCfg.put(DRIVER, driverName);
596 ObjectMapper mapper = new ObjectMapper();
597 JsonNode newCfgNode = mapper.convertValue(newCfg, JsonNode.class);
598 cfgService.applyConfig(deviceId, BasicDeviceConfig.class, newCfgNode);
599 return true;
600 }
601
Andrea Campanella14e196d2017-07-24 18:11:36 +0200602 private PiPipeconf getPipeconf(DeviceId deviceId, PiPipelineProgrammable pipelineProg) {
Carmelo Cascone92044522018-06-29 19:00:59 +0200603 PiPipeconfId pipeconfId = getPipeconfFromCfg(deviceId);
604 if (pipeconfId == null || pipeconfId.id().isEmpty()) {
605 // No pipeconf has been provided in the cfg.
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400606 // Check if device driver provides a default one.
607 if (pipelineProg.getDefaultPipeconf().isPresent()) {
Carmelo Cascone92044522018-06-29 19:00:59 +0200608 final PiPipeconf defaultPipeconf = pipelineProg.getDefaultPipeconf().get();
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400609 log.info("Using default pipeconf {} for {}", defaultPipeconf.id(), deviceId);
Carmelo Cascone92044522018-06-29 19:00:59 +0200610 pipeconfId = defaultPipeconf.id();
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400611 } else {
Carmelo Cascone92044522018-06-29 19:00:59 +0200612 log.warn("Device {} is pipeline programmable but no pipeconf can be associated to it", deviceId);
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400613 return null;
614 }
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200615 }
Carmelo Cascone92044522018-06-29 19:00:59 +0200616 // Check if registered
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200617 if (!piPipeconfService.getPipeconf(pipeconfId).isPresent()) {
618 log.warn("Pipeconf {} is not registered", pipeconfId);
Andrea Campanella14e196d2017-07-24 18:11:36 +0200619 return null;
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400620 }
Andrea Campanella14e196d2017-07-24 18:11:36 +0200621 return piPipeconfService.getPipeconf(pipeconfId).get();
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400622 }
623
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200624 private void doDisconnectDevice(DeviceId deviceId) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200625 log.debug("Initiating disconnection from {}...", deviceId);
626 final DeviceHandshaker handshaker = handshakers.remove(deviceId);
627 final boolean isAvailable = deviceService.isAvailable(deviceId);
628 // Signal disconnection to core (if master).
629 if (isAvailable && mastershipService.isLocalMaster(deviceId)) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200630 providerService.deviceDisconnected(deviceId);
631 }
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200632 // Cancel tasks.
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200633 cancelStatsPolling(deviceId);
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200634 // Disconnect device.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200635 if (handshaker == null) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200636 if (isAvailable) {
637 // If not available don't bother logging. We are probably
638 // invoking this method multiple times for the same device.
639 log.warn("Missing DeviceHandshaker behavior for {}, " +
640 "no guarantees of complete disconnection",
641 deviceId);
642 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200643 return;
Andrea Campanella241896c2017-05-10 13:11:04 -0700644 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200645 handshaker.removeDeviceAgentListener(deviceAgentListener);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200646 final boolean disconnectSuccess = getFutureWithDeadline(
647 handshaker.disconnect(), "performing disconnection",
648 deviceId, false, opTimeoutShort);
649 if (!disconnectSuccess) {
650 log.warn("Unable to disconnect from {}", deviceId);
651 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700652 }
653
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200654 // Needed to catch the exception in the executors since are not rethrown otherwise.
Andrea Campanella241896c2017-05-10 13:11:04 -0700655 private Runnable exceptionSafe(Runnable runnable) {
656 return () -> {
657 try {
658 runnable.run();
659 } catch (Exception e) {
660 log.error("Unhandled Exception", e);
661 }
662 };
663 }
664
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200665 private <U> U withDeviceLock(Supplier<U> task, DeviceId deviceId) {
666 final Lock lock = deviceLocks.get(deviceId);
667 lock.lock();
668 try {
669 return task.get();
670 } finally {
671 lock.unlock();
672 }
673 }
674
675 private Runnable withDeviceLock(Runnable task, DeviceId deviceId) {
676 // Wrapper of withDeviceLock(Supplier, ...) for void tasks.
677 return () -> withDeviceLock(() -> {
678 task.run();
679 return null;
680 }, deviceId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200681 }
682
Andrea Campanella241896c2017-05-10 13:11:04 -0700683 private void updatePortStatistics(DeviceId deviceId) {
Andrea Campanellace111932017-09-18 16:59:56 +0900684 Device device = deviceService.getDevice(deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200685 if (device != null && deviceService.isAvailable(deviceId) &&
Andrea Campanellace111932017-09-18 16:59:56 +0900686 device.is(PortStatisticsDiscovery.class)) {
687 Collection<PortStatistics> statistics = device.as(PortStatisticsDiscovery.class)
688 .discoverPortStatistics();
689 //updating statistcs only if not empty
690 if (!statistics.isEmpty()) {
691 providerService.updatePortStatistics(deviceId, statistics);
692 }
693 } else {
694 log.debug("Can't update port statistics for device {}", deviceId);
Andrea Campanella19090322017-08-22 10:31:37 +0200695 }
696 }
697
Carmelo Casconee5b28722018-06-22 17:28:28 +0200698 private boolean notMyScheme(DeviceId deviceId) {
699 return !deviceId.uri().getScheme().equals(URI_SCHEME);
Andrea Campanella241896c2017-05-10 13:11:04 -0700700 }
701
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200702 private void triggerConnect(DeviceId deviceId) {
703 connectionExecutor.execute(withDeviceLock(
704 () -> doConnectDevice(deviceId), deviceId));
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200705 }
706
Andrea Campanella241896c2017-05-10 13:11:04 -0700707 /**
708 * Listener for configuration events.
709 */
710 private class InternalNetworkConfigListener implements NetworkConfigListener {
711
Andrea Campanella241896c2017-05-10 13:11:04 -0700712 @Override
713 public void event(NetworkConfigEvent event) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200714 connectionExecutor.execute(() -> consumeConfigEvent(event));
715 }
716
717 @Override
718 public boolean isRelevant(NetworkConfigEvent event) {
719 return (event.configClass().equals(GeneralProviderDeviceConfig.class) ||
720 event.configClass().equals(BasicDeviceConfig.class) ||
721 event.configClass().equals(PiPipeconfConfig.class)) &&
722 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
723 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
724 }
725
726 private void consumeConfigEvent(NetworkConfigEvent event) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700727 DeviceId deviceId = (DeviceId) event.subject();
728 //Assuming that the deviceId comes with uri 'device:'
Carmelo Casconee5b28722018-06-22 17:28:28 +0200729 if (notMyScheme(deviceId)) {
Andrea Campanella241896c2017-05-10 13:11:04 -0700730 // not under my scheme, skipping
731 log.debug("{} is not my scheme, skipping", deviceId);
732 return;
733 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200734 final boolean configComplete = withDeviceLock(
735 () -> isDeviceConfigComplete(event, deviceId), deviceId);
736 if (!configComplete) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200737 // Still waiting for some configuration.
Andrea Campanellabc112a92017-06-26 19:06:43 +0200738 return;
739 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200740 // Good to go.
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200741 triggerConnect(deviceId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200742 cleanUpConfigInfo(deviceId);
Andrea Campanella14e196d2017-07-24 18:11:36 +0200743 }
744
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200745 private boolean isDeviceConfigComplete(NetworkConfigEvent event, DeviceId deviceId) {
746 // FIXME to be removed when netcfg will issue device events in a bundle or
Andrea Campanellabc112a92017-06-26 19:06:43 +0200747 // ensure all configuration needed is present
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200748 if (event.configClass().equals(GeneralProviderDeviceConfig.class)) {
749 //FIXME we currently assume that p4runtime devices are pipeline configurable.
750 //If we want to connect a p4runtime device with no pipeline
751 if (event.config().isPresent()) {
752 deviceConfigured.add(deviceId);
753 final boolean isNotPipelineConfigurable = Collections.disjoint(
754 ImmutableSet.copyOf(event.config().get().node().fieldNames()),
755 PIPELINE_CONFIGURABLE_PROTOCOLS);
756 if (isNotPipelineConfigurable) {
757 // Skip waiting for a pipeline if we can't support it.
Andrea Campanellabc112a92017-06-26 19:06:43 +0200758 pipelineConfigured.add(deviceId);
759 }
760 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200761 } else if (event.configClass().equals(BasicDeviceConfig.class)) {
762 if (event.config().isPresent() && event.config().get().node().has(DRIVER)) {
763 driverConfigured.add(deviceId);
Andrea Campanellabc112a92017-06-26 19:06:43 +0200764 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200765 } else if (event.configClass().equals(PiPipeconfConfig.class)) {
766 if (event.config().isPresent()
767 && event.config().get().node().has(PiPipeconfConfig.PIPIPECONFID)) {
768 pipelineConfigured.add(deviceId);
769 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700770 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200771
772 if (deviceConfigured.contains(deviceId)
773 && driverConfigured.contains(deviceId)
774 && pipelineConfigured.contains(deviceId)) {
775 return true;
776 } else {
777 if (deviceConfigured.contains(deviceId) && driverConfigured.contains(deviceId)) {
778 log.debug("Waiting for pipeline configuration for device {}", deviceId);
779 } else if (pipelineConfigured.contains(deviceId) && driverConfigured.contains(deviceId)) {
780 log.debug("Waiting for device configuration for device {}", deviceId);
781 } else if (pipelineConfigured.contains(deviceId) && deviceConfigured.contains(deviceId)) {
782 log.debug("Waiting for driver configuration for device {}", deviceId);
783 } else if (driverConfigured.contains(deviceId)) {
784 log.debug("Only driver configuration for device {}", deviceId);
785 } else if (deviceConfigured.contains(deviceId)) {
786 log.debug("Only device configuration for device {}", deviceId);
787 }
788 }
789 return false;
Andrea Campanella241896c2017-05-10 13:11:04 -0700790 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700791 }
792
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200793 private void augmentConfigData(GeneralProviderDeviceConfig providerConfig, DriverData driverData) {
Andrea Campanella14e196d2017-07-24 18:11:36 +0200794 //Storing deviceKeyId and all other config values
795 // as data in the driver with protocol_<info>
796 // name as the key. e.g protocol_ip
797 providerConfig.protocolsInfo()
798 .forEach((protocol, deviceInfoConfig) -> {
799 deviceInfoConfig.configValues()
800 .forEach((k, v) -> driverData.set(protocol + "_" + k, v));
801 driverData.set(protocol + "_key", deviceInfoConfig.deviceKeyId());
802 });
803 }
804
805 private void cleanUpConfigInfo(DeviceId deviceId) {
Andrea Campanellabc112a92017-06-26 19:06:43 +0200806 deviceConfigured.remove(deviceId);
807 driverConfigured.remove(deviceId);
808 pipelineConfigured.remove(deviceId);
Andrea Campanellabc112a92017-06-26 19:06:43 +0200809 }
810
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200811 private void startStatsPolling(DeviceId deviceId, boolean withRandomDelay) {
812 statsPollingTasks.compute(deviceId, (did, oldTask) -> {
813 if (oldTask != null) {
814 oldTask.cancel(false);
815 }
816 final int delay = withRandomDelay
817 ? new SecureRandom().nextInt(10) : 0;
818 return statsExecutor.scheduleAtFixedRate(
819 exceptionSafe(() -> updatePortStatistics(deviceId)),
820 delay, statsPollFrequency, TimeUnit.SECONDS);
821 });
Andrea Campanella19090322017-08-22 10:31:37 +0200822 }
823
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200824 private void cancelStatsPolling(DeviceId deviceId) {
825 statsPollingTasks.computeIfPresent(deviceId, (did, task) -> {
826 task.cancel(false);
827 return null;
828 });
829 }
830
831 private void rescheduleStatsPollingTasks() {
832 statsPollingTasks.keySet().forEach(deviceId -> {
833 // startStatsPolling cancels old one if present.
834 startStatsPolling(deviceId, true);
835 });
836 }
837
838 private void triggerProbeAllDevices() {
839 // Async trigger a task for all devices in the cfg.
840 cfgService.getSubjects(DeviceId.class, GeneralProviderDeviceConfig.class)
841 .forEach(deviceId -> connectionExecutor.execute(withDeviceLock(
842 () -> doDeviceProbe(deviceId), deviceId)));
Andrea Campanella1e573442018-05-17 17:07:13 +0200843 }
844
Carmelo Cascone92044522018-06-29 19:00:59 +0200845 private PiPipeconfId getPipeconfFromCfg(DeviceId deviceId) {
846 PiPipeconfConfig config = cfgService.getConfig(
847 deviceId, PiPipeconfConfig.class);
848 if (config == null) {
849 return null;
850 }
851 return config.piPipeconfId();
852 }
853
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200854 private void doDeviceProbe(DeviceId deviceId) {
855 if (!configIsPresent(deviceId)) {
856 return;
857 }
858 final boolean isAvailable = deviceService.getDevice(deviceId) != null
859 && deviceService.isAvailable(deviceId);
860 final boolean isLocalMaster = mastershipService.isLocalMaster(deviceId);
861 if (isAvailable) {
862 if (!isLocalMaster) {
863 return;
864 }
865 if (!isReachable(deviceId)) {
866 log.info("Disconnecting available but unreachable device {}...",
867 deviceId);
868 triggerDisconnect(deviceId);
869 }
870 } else {
871 // We do not check for reachability using isReachable()
872 // since the behaviour of this method can vary depending on protocol
873 // nuances. We leave this check to the device handshaker at later
874 // stages of the connection process.
875 triggerConnect(deviceId);
Andrea Campanella1e573442018-05-17 17:07:13 +0200876 }
877 }
878
879 private boolean configIsPresent(DeviceId deviceId) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200880 final boolean present =
881 cfgService.getConfig(deviceId, GeneralProviderDeviceConfig.class) != null
882 && cfgService.getConfig(deviceId, BasicDeviceConfig.class) != null;
Andrea Campanella1e573442018-05-17 17:07:13 +0200883 if (!present) {
884 log.warn("Configuration for device {} is not complete", deviceId);
885 }
886 return present;
887 }
888
Carmelo Casconee5b28722018-06-22 17:28:28 +0200889 private void handleChannelClosed(DeviceId deviceId) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200890 log.info("Disconnecting device {}, due to channel closed event",
891 deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200892 triggerDisconnect(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200893 }
894
895 private void handleMastershipResponse(DeviceId deviceId, MastershipRole response) {
896 //Notify core about response.
897 if (!requestedRoles.containsKey(deviceId)) {
898 return;
899 }
900 providerService.receivedRoleReply(deviceId, requestedRoles.get(deviceId), response);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200901 if (response.equals(MastershipRole.MASTER)) {
902 startStatsPolling(deviceId, false);
903 } else {
904 cancelStatsPolling(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200905 }
906 }
907
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200908 private <U> U getFutureWithDeadline(CompletableFuture<U> future, String opDescription,
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200909 DeviceId deviceId, U defaultValue, int timeout) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200910 try {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200911 return future.get(timeout, TimeUnit.SECONDS);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200912 } catch (InterruptedException e) {
913 log.error("Thread interrupted while {} on {}", opDescription, deviceId);
914 Thread.currentThread().interrupt();
915 } catch (ExecutionException e) {
916 log.error("Exception while {} on {}", opDescription, deviceId, e.getCause());
917 } catch (TimeoutException e) {
918 log.error("Operation TIMEOUT while {} on {}", opDescription, deviceId);
919 }
920 return defaultValue;
921 }
922
Andrea Campanella241896c2017-05-10 13:11:04 -0700923 /**
924 * Listener for core device events.
925 */
926 private class InternalDeviceListener implements DeviceListener {
927 @Override
928 public void event(DeviceEvent event) {
Andrea Campanellace111932017-09-18 16:59:56 +0900929 DeviceId deviceId = event.subject().id();
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700930 // For now this is scheduled periodically, when streaming API will
931 // be available we check and base it on the streaming API (e.g. gNMI)
932 if (mastershipService.isLocalMaster(deviceId)) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200933 startStatsPolling(deviceId, true);
Andrea Campanella241896c2017-05-10 13:11:04 -0700934 }
935 }
936
937 @Override
938 public boolean isRelevant(DeviceEvent event) {
Thomas Vachuska5b38dc02018-05-10 15:24:40 -0700939 return event.type() == Type.DEVICE_ADDED &&
940 event.subject().id().toString().startsWith(URI_SCHEME.toLowerCase());
Andrea Campanella241896c2017-05-10 13:11:04 -0700941 }
942 }
Andrea Campanella1e573442018-05-17 17:07:13 +0200943
944 /**
Carmelo Casconee5b28722018-06-22 17:28:28 +0200945 * Listener for device agent events.
Andrea Campanella1e573442018-05-17 17:07:13 +0200946 */
Carmelo Casconee5b28722018-06-22 17:28:28 +0200947 private class InternalDeviceAgentListener implements DeviceAgentListener {
Andrea Campanella1e573442018-05-17 17:07:13 +0200948
949 @Override
Carmelo Casconee5b28722018-06-22 17:28:28 +0200950 public void event(DeviceAgentEvent event) {
Andrea Campanella1e573442018-05-17 17:07:13 +0200951 DeviceId deviceId = event.subject();
Carmelo Casconee5b28722018-06-22 17:28:28 +0200952 switch (event.type()) {
953 case CHANNEL_OPEN:
954 // Ignore.
955 break;
956 case CHANNEL_CLOSED:
957 handleChannelClosed(deviceId);
958 break;
959 case CHANNEL_ERROR:
960 // TODO evaluate other reaction to channel error.
961 log.warn("Received CHANNEL_ERROR from {}. Is the channel still open?",
962 deviceId);
Carmelo Casconee15773f2018-08-21 18:31:42 -0700963 handleChannelClosed(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200964 break;
965 case ROLE_MASTER:
966 handleMastershipResponse(deviceId, MastershipRole.MASTER);
967 break;
968 case ROLE_STANDBY:
969 handleMastershipResponse(deviceId, MastershipRole.STANDBY);
970 break;
971 case ROLE_NONE:
972 handleMastershipResponse(deviceId, MastershipRole.NONE);
973 break;
974 default:
975 log.warn("Unrecognized device agent event {}", event.type());
Andrea Campanella1e573442018-05-17 17:07:13 +0200976 }
Andrea Campanella1e573442018-05-17 17:07:13 +0200977 }
978
979 }
Andrea Campanella241896c2017-05-10 13:11:04 -0700980}