blob: 1a9d4a05f504fb4a6fb0c2e7cc36cc8e512d0bd6 [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 Casconec18e82c2016-06-16 14:22:36 -0700192 if (contextService.getContext(did) == null) {
193 // Device is a first timer.
194 log.info("Setting DEFAULT context for {}", did);
195 contextService.setContext(did, contextService.defaultContext());
196 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700197 resetDeviceState(did);
198 initPortCounters(did);
199 providerService.deviceConnected(did, thisDescription);
200 updatePortsAndStats(did);
201 }
202 }
203 return thisDescription;
204 } else {
205 log.warn("Unable to get device description for {}", did);
206 return lastDescription;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700207 }
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700208 });
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700209 }
210
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700211 private DeviceDescription getDeviceDescription(DeviceId did) {
212 Device device = deviceService.getDevice(did);
213 DeviceDescriptionDiscovery discovery = null;
214 if (device == null) {
215 // Device not yet in the core. Manually get a driver.
216 Driver driver = driverService.getDriver(MANUFACTURER, HW_VERSION, SW_VERSION);
217 if (driver.hasBehaviour(DeviceDescriptionDiscovery.class)) {
218 discovery = driver.createBehaviour(new DefaultDriverHandler(new DefaultDriverData(driver, did)),
219 DeviceDescriptionDiscovery.class);
220 }
221 } else if (device.is(DeviceDescriptionDiscovery.class)) {
222 discovery = device.as(DeviceDescriptionDiscovery.class);
223 }
224 if (discovery == null) {
225 log.warn("No DeviceDescriptionDiscovery behavior for device {}", did);
226 return null;
227 } else {
228 return discovery.discoverDeviceDetails();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700229 }
230 }
231
232 private void resetDeviceState(DeviceId did) {
233 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700234 controller.getAgent(did).resetState();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700235 // Tables emptied. Reset all bindings.
236 tableEntryService.unbindAll(did);
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700237 } catch (Bmv2RuntimeException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700238 log.warn("Unable to reset {}: {}", did, e.toString());
Carmelo Cascone442a9622016-05-03 11:16:20 -0700239 }
240 }
241
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700242 private void initPortCounters(DeviceId did) {
243 try {
244 initCounters(controller.getAgent(did));
245 } catch (Bmv2RuntimeException e) {
246 log.warn("Unable to init counter on {}: {}", did, e.explain());
247 }
248 }
249
250 private void updatePortsAndStats(DeviceId did) {
Carmelo Cascone442a9622016-05-03 11:16:20 -0700251 Device device = deviceService.getDevice(did);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700252 if (device.is(DeviceDescriptionDiscovery.class)) {
253 DeviceDescriptionDiscovery discovery = device.as(DeviceDescriptionDiscovery.class);
254 List<PortDescription> portDescriptions = discovery.discoverPortDetails();
255 if (portDescriptions != null) {
256 providerService.updatePorts(did, portDescriptions);
257 }
Carmelo Cascone442a9622016-05-03 11:16:20 -0700258 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700259 log.warn("No DeviceDescriptionDiscovery behavior for device {}", did);
260 }
261 try {
262 Collection<PortStatistics> portStats = getPortStatistics(controller.getAgent(did),
263 deviceService.getPorts(did));
264 providerService.updatePortStatistics(did, portStats);
265 } catch (Bmv2RuntimeException e) {
266 log.warn("Unable to get port statistics for {}: {}", did, e.explain());
Carmelo Cascone442a9622016-05-03 11:16:20 -0700267 }
268 }
269
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700270 private void disconnectDevice(DeviceId did) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700271 log.debug("Trying to disconnect device from core... deviceId={}", did);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700272 activeDevices.compute(did, (k, v) -> {
273 if (deviceService.isAvailable(did)) {
274 providerService.deviceDisconnected(did);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700275 }
276 return null;
277 });
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700278 }
279
280 /**
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700281 * Internal net-cfg config factory.
282 */
283 private class InternalConfigFactory extends ConfigFactory<ApplicationId, Bmv2ProviderConfig> {
284
285 InternalConfigFactory() {
286 super(APP_SUBJECT_FACTORY, Bmv2ProviderConfig.class, "devices", true);
287 }
288
289 @Override
290 public Bmv2ProviderConfig createConfig() {
291 return new Bmv2ProviderConfig();
292 }
293 }
294
295 /**
296 * Internal net-cfg event listener.
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700297 */
298 private class InternalNetworkConfigListener implements NetworkConfigListener {
299
300 @Override
301 public void event(NetworkConfigEvent event) {
302 Bmv2ProviderConfig cfg = netCfgService.getConfig(appId, Bmv2ProviderConfig.class);
303 if (cfg != null) {
304 try {
305 cfg.getDevicesInfo().stream().forEach(info -> {
Carmelo Casconec18e82c2016-06-16 14:22:36 -0700306 // FIXME: require also bmv2 internal device id from net-cfg (now is default 0)
Carmelo Casconec0fbbee2016-04-27 18:03:36 -0700307 Bmv2Device bmv2Device = new Bmv2Device(info.ip().toString(), info.port(), 0);
308 triggerProbe(bmv2Device.asDeviceId());
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700309 });
310 } catch (ConfigException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700311 log.error("Unable to read config: " + e);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700312 }
313 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700314 log.error("Unable to read config (was null)");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700315 }
316 }
317
318 @Override
319 public boolean isRelevant(NetworkConfigEvent event) {
320 return event.configClass().equals(Bmv2ProviderConfig.class) &&
321 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
322 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
323 }
324 }
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700325
326 /**
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700327 * Listener triggered by the BMv2 controller each time a hello message is received.
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700328 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700329 private class InternalDeviceListener implements Bmv2DeviceListener {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700330 @Override
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700331 public void handleHello(Bmv2Device device, int instanceId, String jsonConfigMd5) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700332 log.debug("Received hello from {}", device);
Carmelo Casconec0fbbee2016-04-27 18:03:36 -0700333 triggerProbe(device.asDeviceId());
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700334 }
335 }
336
337 /**
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700338 * Task that periodically trigger device probes to check for device status and update port informations.
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700339 */
340 private class DevicePoller implements TimerTask {
341
342 private final HashedWheelTimer timer = Timer.getTimer();
343 private Timeout timeout;
344
345 @Override
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700346 public void run(Timeout tout) throws Exception {
347 if (tout.isCancelled()) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700348 return;
349 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700350 activeDevices.keySet()
351 .stream()
352 // Filter out devices not yet created in the core.
353 .filter(did -> deviceService.getDevice(did) != null)
354 .forEach(did -> executorService.execute(() -> pollingTask(did)));
355 tout.getTimer().newTimeout(this, POLL_INTERVAL, TimeUnit.MILLISECONDS);
356 }
357
358 private void pollingTask(DeviceId deviceId) {
359 if (isReachable(deviceId)) {
360 updatePortsAndStats(deviceId);
361 } else {
362 disconnectDevice(deviceId);
363 }
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700364 }
365
366 /**
367 * Starts the collector.
368 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700369 synchronized void start() {
370 log.info("Starting device poller...");
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700371 timeout = timer.newTimeout(this, 1, TimeUnit.SECONDS);
372 }
373
374 /**
375 * Stops the collector.
376 */
377 synchronized void stop() {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700378 log.info("Stopping device poller...");
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700379 timeout.cancel();
380 }
381 }
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700382}