blob: fda7aadef4c56e114014c6e1104cae1b478c75f5 [file] [log] [blame]
Carmelo Cascone3bb71c12016-04-06 21:30:44 -07001/*
2 * Copyright 2014-2016 Open Networking Laboratory
3 *
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.bmv2.device.impl;
18
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070019import com.google.common.collect.Maps;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070020import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Reference;
22import org.apache.felix.scr.annotations.ReferenceCardinality;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070023import org.jboss.netty.util.HashedWheelTimer;
24import org.jboss.netty.util.Timeout;
25import org.jboss.netty.util.TimerTask;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070026import org.onlab.util.Timer;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070027import org.onosproject.bmv2.api.runtime.Bmv2Device;
Carmelo Cascone442a9622016-05-03 11:16:20 -070028import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070029import org.onosproject.bmv2.api.service.Bmv2Controller;
30import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
31import org.onosproject.bmv2.api.service.Bmv2DeviceListener;
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -070032import org.onosproject.bmv2.api.service.Bmv2TableEntryService;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070033import org.onosproject.common.net.AbstractDeviceProvider;
34import org.onosproject.core.ApplicationId;
35import org.onosproject.core.CoreService;
36import org.onosproject.incubator.net.config.basics.ConfigException;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070037import org.onosproject.net.Device;
38import org.onosproject.net.DeviceId;
39import org.onosproject.net.MastershipRole;
40import org.onosproject.net.PortNumber;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070041import org.onosproject.net.config.ConfigFactory;
42import org.onosproject.net.config.NetworkConfigEvent;
43import org.onosproject.net.config.NetworkConfigListener;
44import org.onosproject.net.config.NetworkConfigRegistry;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070045import org.onosproject.net.device.DeviceDescription;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070046import org.onosproject.net.device.DeviceDescriptionDiscovery;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070047import org.onosproject.net.device.DeviceService;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070048import org.onosproject.net.device.PortDescription;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070049import org.onosproject.net.device.PortStatistics;
50import org.onosproject.net.driver.DefaultDriverData;
51import org.onosproject.net.driver.DefaultDriverHandler;
52import org.onosproject.net.driver.Driver;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070053import org.onosproject.net.provider.ProviderId;
54import org.slf4j.Logger;
55
Carmelo Cascone0831efb2016-05-31 14:50:19 -070056import java.util.Collection;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070057import java.util.List;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070058import java.util.Objects;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070059import java.util.concurrent.ConcurrentMap;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070060import java.util.concurrent.ExecutorService;
61import java.util.concurrent.Executors;
62import java.util.concurrent.TimeUnit;
63
64import static org.onlab.util.Tools.groupedThreads;
Carmelo Cascone442a9622016-05-03 11:16:20 -070065import static org.onosproject.bmv2.api.runtime.Bmv2Device.*;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070066import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070067import static org.onosproject.provider.bmv2.device.impl.Bmv2PortStatisticsGetter.getPortStatistics;
68import static org.onosproject.provider.bmv2.device.impl.Bmv2PortStatisticsGetter.initCounters;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070069import static org.slf4j.LoggerFactory.getLogger;
70
71/**
72 * BMv2 device provider.
73 */
74@Component(immediate = true)
75public class Bmv2DeviceProvider extends AbstractDeviceProvider {
76
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070077 private static final String APP_NAME = "org.onosproject.bmv2";
Carmelo Cascone0831efb2016-05-31 14:50:19 -070078
79 private static final int POLL_INTERVAL = 5_000; // milliseconds
80
81 private final Logger log = getLogger(this.getClass());
82
83 private final ExecutorService executorService = Executors
84 .newFixedThreadPool(16, groupedThreads("onos/bmv2", "device-discovery", log));
85
86 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
87
88 private final ConfigFactory cfgFactory = new InternalConfigFactory();
89
90 private final ConcurrentMap<DeviceId, DeviceDescription> activeDevices = Maps.newConcurrentMap();
91
92 private final DevicePoller devicePoller = new DevicePoller();
93
94 private final InternalDeviceListener deviceListener = new InternalDeviceListener();
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070095
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected NetworkConfigRegistry netCfgService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected CoreService coreService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 protected DeviceService deviceService;
104
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700106 protected Bmv2Controller controller;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700107
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected Bmv2DeviceContextService contextService;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700110
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 protected Bmv2TableEntryService tableEntryService;
113
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700114 private ApplicationId appId;
115
116 /**
117 * Creates a Bmv2 device provider with the supplied identifier.
118 */
119 public Bmv2DeviceProvider() {
120 super(new ProviderId("bmv2", "org.onosproject.provider.device"));
121 }
122
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700123 @Override
124 protected void activate() {
125 appId = coreService.registerApplication(APP_NAME);
126 netCfgService.registerConfigFactory(cfgFactory);
127 netCfgService.addListener(cfgListener);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700128 controller.addDeviceListener(deviceListener);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700129 devicePoller.start();
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700130 super.activate();
131 }
132
133 @Override
134 protected void deactivate() {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700135 devicePoller.stop();
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700136 controller.removeDeviceListener(deviceListener);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700137 try {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700138 activeDevices.forEach((did, value) -> {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700139 executorService.execute(() -> disconnectDevice(did));
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700140 });
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700141 executorService.awaitTermination(1000, TimeUnit.MILLISECONDS);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700142 } catch (InterruptedException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700143 log.error("Device discovery threads did not terminate");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700144 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700145 executorService.shutdownNow();
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700146 netCfgService.unregisterConfigFactory(cfgFactory);
147 netCfgService.removeListener(cfgListener);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700148 super.deactivate();
149 }
150
151 @Override
152 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700153 // Asynchronously trigger probe task.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700154 executorService.execute(() -> executeProbe(deviceId));
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700155 }
156
157 private void executeProbe(DeviceId did) {
158 boolean reachable = isReachable(did);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700159 log.debug("Probed device: id={}, reachable={}", did.toString(), reachable);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700160 if (reachable) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700161 discoverDevice(did);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700162 } else {
163 disconnectDevice(did);
164 }
165 }
166
167 @Override
168 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700169 log.debug("roleChanged() is not yet implemented");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700170 // TODO: implement mastership handling
171 }
172
173 @Override
174 public boolean isReachable(DeviceId deviceId) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700175 return controller.isReacheable(deviceId);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700176 }
177
178 @Override
179 public void changePortState(DeviceId deviceId, PortNumber portNumber, boolean enable) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700180 log.warn("changePortState() not supported");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700181 }
182
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700183 private void discoverDevice(DeviceId did) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700184 log.debug("Starting device discovery... deviceId={}", did);
185 activeDevices.compute(did, (k, lastDescription) -> {
186 DeviceDescription thisDescription = getDeviceDescription(did);
187 if (thisDescription != null) {
188 boolean descriptionChanged = lastDescription != null &&
189 (!Objects.equals(thisDescription, lastDescription) ||
190 !Objects.equals(thisDescription.annotations(), lastDescription.annotations()));
191 if (descriptionChanged || !deviceService.isAvailable(did)) {
Carmelo Casconefbc577b2016-06-17 23:19:09 -0700192 if (deviceService.getDevice(did) == null) {
Carmelo Casconec18e82c2016-06-16 14:22:36 -0700193 // Device is a first timer.
194 log.info("Setting DEFAULT context for {}", did);
Carmelo Casconefbc577b2016-06-17 23:19:09 -0700195 // It is important to do this before connecting the device so other
196 // services won't find a null context.
Carmelo Casconec18e82c2016-06-16 14:22:36 -0700197 contextService.setContext(did, contextService.defaultContext());
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700198 }
Carmelo Casconefbc577b2016-06-17 23:19:09 -0700199 resetDeviceState(did);
200 initPortCounters(did);
201 providerService.deviceConnected(did, thisDescription);
202 updatePortsAndStats(did);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700203 }
204 return thisDescription;
205 } else {
206 log.warn("Unable to get device description for {}", did);
207 return lastDescription;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700208 }
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700209 });
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700210 }
211
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700212 private DeviceDescription getDeviceDescription(DeviceId did) {
213 Device device = deviceService.getDevice(did);
214 DeviceDescriptionDiscovery discovery = null;
215 if (device == null) {
216 // Device not yet in the core. Manually get a driver.
217 Driver driver = driverService.getDriver(MANUFACTURER, HW_VERSION, SW_VERSION);
218 if (driver.hasBehaviour(DeviceDescriptionDiscovery.class)) {
219 discovery = driver.createBehaviour(new DefaultDriverHandler(new DefaultDriverData(driver, did)),
220 DeviceDescriptionDiscovery.class);
221 }
222 } else if (device.is(DeviceDescriptionDiscovery.class)) {
223 discovery = device.as(DeviceDescriptionDiscovery.class);
224 }
225 if (discovery == null) {
226 log.warn("No DeviceDescriptionDiscovery behavior for device {}", did);
227 return null;
228 } else {
229 return discovery.discoverDeviceDetails();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700230 }
231 }
232
233 private void resetDeviceState(DeviceId did) {
234 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700235 controller.getAgent(did).resetState();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700236 // Tables emptied. Reset all bindings.
237 tableEntryService.unbindAll(did);
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700238 } catch (Bmv2RuntimeException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700239 log.warn("Unable to reset {}: {}", did, e.toString());
Carmelo Cascone442a9622016-05-03 11:16:20 -0700240 }
241 }
242
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700243 private void initPortCounters(DeviceId did) {
244 try {
245 initCounters(controller.getAgent(did));
246 } catch (Bmv2RuntimeException e) {
247 log.warn("Unable to init counter on {}: {}", did, e.explain());
248 }
249 }
250
251 private void updatePortsAndStats(DeviceId did) {
Carmelo Cascone442a9622016-05-03 11:16:20 -0700252 Device device = deviceService.getDevice(did);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700253 if (device.is(DeviceDescriptionDiscovery.class)) {
254 DeviceDescriptionDiscovery discovery = device.as(DeviceDescriptionDiscovery.class);
255 List<PortDescription> portDescriptions = discovery.discoverPortDetails();
256 if (portDescriptions != null) {
257 providerService.updatePorts(did, portDescriptions);
258 }
Carmelo Cascone442a9622016-05-03 11:16:20 -0700259 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700260 log.warn("No DeviceDescriptionDiscovery behavior for device {}", did);
261 }
262 try {
263 Collection<PortStatistics> portStats = getPortStatistics(controller.getAgent(did),
264 deviceService.getPorts(did));
265 providerService.updatePortStatistics(did, portStats);
266 } catch (Bmv2RuntimeException e) {
267 log.warn("Unable to get port statistics for {}: {}", did, e.explain());
Carmelo Cascone442a9622016-05-03 11:16:20 -0700268 }
269 }
270
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700271 private void disconnectDevice(DeviceId did) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700272 log.debug("Trying to disconnect device from core... deviceId={}", did);
Carmelo Cascone6256d012016-06-17 13:49:52 -0700273 if (deviceService.isAvailable(did)) {
274 providerService.deviceDisconnected(did);
275 }
Carmelo Casconefbc577b2016-06-17 23:19:09 -0700276 activeDevices.remove(did);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700277 }
278
279 /**
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700280 * Internal net-cfg config factory.
281 */
282 private class InternalConfigFactory extends ConfigFactory<ApplicationId, Bmv2ProviderConfig> {
283
284 InternalConfigFactory() {
285 super(APP_SUBJECT_FACTORY, Bmv2ProviderConfig.class, "devices", true);
286 }
287
288 @Override
289 public Bmv2ProviderConfig createConfig() {
290 return new Bmv2ProviderConfig();
291 }
292 }
293
294 /**
295 * Internal net-cfg event listener.
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700296 */
297 private class InternalNetworkConfigListener implements NetworkConfigListener {
298
299 @Override
300 public void event(NetworkConfigEvent event) {
301 Bmv2ProviderConfig cfg = netCfgService.getConfig(appId, Bmv2ProviderConfig.class);
302 if (cfg != null) {
303 try {
304 cfg.getDevicesInfo().stream().forEach(info -> {
Carmelo Casconec18e82c2016-06-16 14:22:36 -0700305 // FIXME: require also bmv2 internal device id from net-cfg (now is default 0)
Carmelo Casconec0fbbee2016-04-27 18:03:36 -0700306 Bmv2Device bmv2Device = new Bmv2Device(info.ip().toString(), info.port(), 0);
307 triggerProbe(bmv2Device.asDeviceId());
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700308 });
309 } catch (ConfigException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700310 log.error("Unable to read config: " + e);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700311 }
312 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700313 log.error("Unable to read config (was null)");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700314 }
315 }
316
317 @Override
318 public boolean isRelevant(NetworkConfigEvent event) {
319 return event.configClass().equals(Bmv2ProviderConfig.class) &&
320 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
321 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
322 }
323 }
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700324
325 /**
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700326 * Listener triggered by the BMv2 controller each time a hello message is received.
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700327 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700328 private class InternalDeviceListener implements Bmv2DeviceListener {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700329 @Override
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700330 public void handleHello(Bmv2Device device, int instanceId, String jsonConfigMd5) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700331 log.debug("Received hello from {}", device);
Carmelo Casconec0fbbee2016-04-27 18:03:36 -0700332 triggerProbe(device.asDeviceId());
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700333 }
334 }
335
336 /**
Carmelo Casconefbc577b2016-06-17 23:19:09 -0700337 * Task that periodically trigger device probes to check for device status and update port information.
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700338 */
339 private class DevicePoller implements TimerTask {
340
341 private final HashedWheelTimer timer = Timer.getTimer();
342 private Timeout timeout;
343
344 @Override
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700345 public void run(Timeout tout) throws Exception {
346 if (tout.isCancelled()) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700347 return;
348 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700349 activeDevices.keySet()
350 .stream()
351 // Filter out devices not yet created in the core.
352 .filter(did -> deviceService.getDevice(did) != null)
353 .forEach(did -> executorService.execute(() -> pollingTask(did)));
354 tout.getTimer().newTimeout(this, POLL_INTERVAL, TimeUnit.MILLISECONDS);
355 }
356
357 private void pollingTask(DeviceId deviceId) {
358 if (isReachable(deviceId)) {
359 updatePortsAndStats(deviceId);
360 } else {
361 disconnectDevice(deviceId);
362 }
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700363 }
364
365 /**
366 * Starts the collector.
367 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700368 synchronized void start() {
369 log.info("Starting device poller...");
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700370 timeout = timer.newTimeout(this, 1, TimeUnit.SECONDS);
371 }
372
373 /**
374 * Stops the collector.
375 */
376 synchronized void stop() {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700377 log.info("Stopping device poller...");
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700378 timeout.cancel();
379 }
380 }
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700381}