blob: d4ab7407788b652fb78658e76ef68dc307d43f3f [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 Cascone62f1e1e2016-06-22 01:43:49 -070023import org.onlab.util.SharedScheduledExecutors;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070024import org.onosproject.bmv2.api.runtime.Bmv2Device;
Carmelo Cascone442a9622016-05-03 11:16:20 -070025import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070026import org.onosproject.bmv2.api.service.Bmv2Controller;
27import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
28import org.onosproject.bmv2.api.service.Bmv2DeviceListener;
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -070029import org.onosproject.bmv2.api.service.Bmv2TableEntryService;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070030import org.onosproject.common.net.AbstractDeviceProvider;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
33import org.onosproject.incubator.net.config.basics.ConfigException;
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070034import org.onosproject.mastership.MastershipService;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070035import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.MastershipRole;
38import org.onosproject.net.PortNumber;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070039import org.onosproject.net.config.ConfigFactory;
40import org.onosproject.net.config.NetworkConfigEvent;
41import org.onosproject.net.config.NetworkConfigListener;
42import org.onosproject.net.config.NetworkConfigRegistry;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070043import org.onosproject.net.device.DeviceDescription;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070044import org.onosproject.net.device.DeviceDescriptionDiscovery;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070045import org.onosproject.net.device.DeviceService;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070046import org.onosproject.net.device.PortDescription;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070047import org.onosproject.net.device.PortStatistics;
48import org.onosproject.net.driver.DefaultDriverData;
49import org.onosproject.net.driver.DefaultDriverHandler;
50import org.onosproject.net.driver.Driver;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070051import org.onosproject.net.provider.ProviderId;
52import org.slf4j.Logger;
53
Carmelo Cascone0831efb2016-05-31 14:50:19 -070054import java.util.Collection;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070055import java.util.List;
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070056import java.util.Map;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070057import java.util.Objects;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070058import java.util.concurrent.ConcurrentMap;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070059import java.util.concurrent.ExecutorService;
60import java.util.concurrent.Executors;
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070061import java.util.concurrent.ScheduledExecutorService;
62import java.util.concurrent.ScheduledFuture;
63import java.util.concurrent.locks.Lock;
64import java.util.concurrent.locks.ReentrantLock;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070065
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070066import static java.util.concurrent.TimeUnit.MILLISECONDS;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070067import static org.onlab.util.Tools.groupedThreads;
Carmelo Cascone442a9622016-05-03 11:16:20 -070068import static org.onosproject.bmv2.api.runtime.Bmv2Device.*;
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070069import static org.onosproject.net.Device.Type.SWITCH;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070070import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070071import static org.onosproject.provider.bmv2.device.impl.Bmv2PortStatisticsGetter.getPortStatistics;
72import static org.onosproject.provider.bmv2.device.impl.Bmv2PortStatisticsGetter.initCounters;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070073import static org.slf4j.LoggerFactory.getLogger;
74
75/**
76 * BMv2 device provider.
77 */
78@Component(immediate = true)
79public class Bmv2DeviceProvider extends AbstractDeviceProvider {
80
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070081 private static final String APP_NAME = "org.onosproject.bmv2";
Carmelo Cascone0831efb2016-05-31 14:50:19 -070082
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070083 private static final int POLL_PERIOD = 5_000; // milliseconds
Carmelo Cascone0831efb2016-05-31 14:50:19 -070084
85 private final Logger log = getLogger(this.getClass());
86
87 private final ExecutorService executorService = Executors
88 .newFixedThreadPool(16, groupedThreads("onos/bmv2", "device-discovery", log));
89
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070090 private final ScheduledExecutorService scheduledExecutorService = SharedScheduledExecutors.getPoolThreadExecutor();
91
Carmelo Cascone0831efb2016-05-31 14:50:19 -070092 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
93
94 private final ConfigFactory cfgFactory = new InternalConfigFactory();
95
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070096 private final Map<DeviceId, DeviceDescription> lastDescriptions = Maps.newHashMap();
Carmelo Cascone0831efb2016-05-31 14:50:19 -070097
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -070098 private final ConcurrentMap<DeviceId, Lock> deviceLocks = Maps.newConcurrentMap();
Carmelo Cascone0831efb2016-05-31 14:50:19 -070099
100 private final InternalDeviceListener deviceListener = new InternalDeviceListener();
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 protected NetworkConfigRegistry netCfgService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected CoreService coreService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected DeviceService deviceService;
110
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700112 protected MastershipService mastershipService;
113
114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700115 protected Bmv2Controller controller;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700116
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 protected Bmv2DeviceContextService contextService;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700119
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
121 protected Bmv2TableEntryService tableEntryService;
122
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700123 private ApplicationId appId;
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700124 private ScheduledFuture<?> poller;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700125
126 /**
127 * Creates a Bmv2 device provider with the supplied identifier.
128 */
129 public Bmv2DeviceProvider() {
130 super(new ProviderId("bmv2", "org.onosproject.provider.device"));
131 }
132
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700133 @Override
134 protected void activate() {
135 appId = coreService.registerApplication(APP_NAME);
136 netCfgService.registerConfigFactory(cfgFactory);
137 netCfgService.addListener(cfgListener);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700138 controller.addDeviceListener(deviceListener);
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700139 if (poller != null) {
140 poller.cancel(false);
141 }
142 poller = scheduledExecutorService.scheduleAtFixedRate(this::pollDevices, 1_000, POLL_PERIOD, MILLISECONDS);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700143 super.activate();
144 }
145
146 @Override
147 protected void deactivate() {
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700148 if (poller != null) {
149 poller.cancel(false);
150 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700151 controller.removeDeviceListener(deviceListener);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700152 try {
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700153 lastDescriptions.forEach((did, value) -> {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700154 executorService.execute(() -> disconnectDevice(did));
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700155 });
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700156 executorService.awaitTermination(1000, MILLISECONDS);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700157 } catch (InterruptedException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700158 log.error("Device discovery threads did not terminate");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700159 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700160 executorService.shutdownNow();
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700161 netCfgService.unregisterConfigFactory(cfgFactory);
162 netCfgService.removeListener(cfgListener);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700163 super.deactivate();
164 }
165
166 @Override
167 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700168 // Asynchronously trigger probe task.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700169 executorService.execute(() -> executeProbe(deviceId));
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700170 }
171
172 private void executeProbe(DeviceId did) {
173 boolean reachable = isReachable(did);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700174 log.debug("Probed device: id={}, reachable={}", did.toString(), reachable);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700175 if (reachable) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700176 discoverDevice(did);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700177 } else {
178 disconnectDevice(did);
179 }
180 }
181
182 @Override
183 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700184 log.debug("roleChanged() is not yet implemented");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700185 // TODO: implement mastership handling
186 }
187
188 @Override
189 public boolean isReachable(DeviceId deviceId) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700190 return controller.isReacheable(deviceId);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700191 }
192
193 @Override
194 public void changePortState(DeviceId deviceId, PortNumber portNumber, boolean enable) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700195 log.warn("changePortState() not supported");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700196 }
197
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700198 private void discoverDevice(DeviceId did) {
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700199 // Serialize discovery for the same device.
200 Lock lock = deviceLocks.computeIfAbsent(did, k -> new ReentrantLock());
201 lock.lock();
202 try {
203 log.debug("Starting device discovery... deviceId={}", did);
204
205 if (contextService.getContext(did) == null) {
206 // Device is a first timer.
207 log.info("Setting DEFAULT context for {}", did);
208 // It is important to do this before creating the device in the core
209 // so other services won't find a null context.
210 contextService.setDefaultContext(did);
211 // Abort discovery, we'll receive a new hello once the swap has been performed.
212 return;
213 }
214
215 DeviceDescription lastDescription = lastDescriptions.get(did);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700216 DeviceDescription thisDescription = getDeviceDescription(did);
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700217
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700218 if (thisDescription != null) {
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700219 boolean descriptionChanged = lastDescription == null ||
220 (!Objects.equals(thisDescription, lastDescription) ||
221 !Objects.equals(thisDescription.annotations(), lastDescription.annotations()));
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700222 if (descriptionChanged || !deviceService.isAvailable(did)) {
Carmelo Casconefbc577b2016-06-17 23:19:09 -0700223 resetDeviceState(did);
224 initPortCounters(did);
225 providerService.deviceConnected(did, thisDescription);
226 updatePortsAndStats(did);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700227 }
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700228 lastDescriptions.put(did, thisDescription);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700229 } else {
230 log.warn("Unable to get device description for {}", did);
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700231 lastDescriptions.put(did, lastDescription);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700232 }
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700233 } finally {
234 lock.unlock();
235 }
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700236 }
237
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700238 private DeviceDescription getDeviceDescription(DeviceId did) {
239 Device device = deviceService.getDevice(did);
240 DeviceDescriptionDiscovery discovery = null;
241 if (device == null) {
242 // Device not yet in the core. Manually get a driver.
243 Driver driver = driverService.getDriver(MANUFACTURER, HW_VERSION, SW_VERSION);
244 if (driver.hasBehaviour(DeviceDescriptionDiscovery.class)) {
245 discovery = driver.createBehaviour(new DefaultDriverHandler(new DefaultDriverData(driver, did)),
246 DeviceDescriptionDiscovery.class);
247 }
248 } else if (device.is(DeviceDescriptionDiscovery.class)) {
249 discovery = device.as(DeviceDescriptionDiscovery.class);
250 }
251 if (discovery == null) {
252 log.warn("No DeviceDescriptionDiscovery behavior for device {}", did);
253 return null;
254 } else {
255 return discovery.discoverDeviceDetails();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700256 }
257 }
258
259 private void resetDeviceState(DeviceId did) {
260 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700261 controller.getAgent(did).resetState();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700262 // Tables emptied. Reset all bindings.
263 tableEntryService.unbindAll(did);
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700264 } catch (Bmv2RuntimeException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700265 log.warn("Unable to reset {}: {}", did, e.toString());
Carmelo Cascone442a9622016-05-03 11:16:20 -0700266 }
267 }
268
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700269 private void initPortCounters(DeviceId did) {
270 try {
271 initCounters(controller.getAgent(did));
272 } catch (Bmv2RuntimeException e) {
273 log.warn("Unable to init counter on {}: {}", did, e.explain());
274 }
275 }
276
277 private void updatePortsAndStats(DeviceId did) {
Carmelo Cascone442a9622016-05-03 11:16:20 -0700278 Device device = deviceService.getDevice(did);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700279 if (device.is(DeviceDescriptionDiscovery.class)) {
280 DeviceDescriptionDiscovery discovery = device.as(DeviceDescriptionDiscovery.class);
281 List<PortDescription> portDescriptions = discovery.discoverPortDetails();
282 if (portDescriptions != null) {
283 providerService.updatePorts(did, portDescriptions);
284 }
Carmelo Cascone442a9622016-05-03 11:16:20 -0700285 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700286 log.warn("No DeviceDescriptionDiscovery behavior for device {}", did);
287 }
288 try {
289 Collection<PortStatistics> portStats = getPortStatistics(controller.getAgent(did),
290 deviceService.getPorts(did));
291 providerService.updatePortStatistics(did, portStats);
292 } catch (Bmv2RuntimeException e) {
293 log.warn("Unable to get port statistics for {}: {}", did, e.explain());
Carmelo Cascone442a9622016-05-03 11:16:20 -0700294 }
295 }
296
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700297 private void disconnectDevice(DeviceId did) {
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700298 log.debug("Disconnecting device from core... deviceId={}", did);
299 providerService.deviceDisconnected(did);
300 lastDescriptions.remove(did);
301 }
302
303 private void pollDevices() {
304 for (Device device: deviceService.getAvailableDevices(SWITCH)) {
305 if (device.id().uri().getScheme().equals(SCHEME) &&
306 mastershipService.isLocalMaster(device.id())) {
307 executorService.execute(() -> pollingTask(device.id()));
308 }
Carmelo Cascone6256d012016-06-17 13:49:52 -0700309 }
Carmelo Cascone62f1e1e2016-06-22 01:43:49 -0700310 }
311
312 private void pollingTask(DeviceId deviceId) {
313 log.debug("Polling device {}...", deviceId);
314 if (isReachable(deviceId)) {
315 updatePortsAndStats(deviceId);
316 } else {
317 disconnectDevice(deviceId);
318 }
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700319 }
320
321 /**
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700322 * Internal net-cfg config factory.
323 */
324 private class InternalConfigFactory extends ConfigFactory<ApplicationId, Bmv2ProviderConfig> {
325
326 InternalConfigFactory() {
327 super(APP_SUBJECT_FACTORY, Bmv2ProviderConfig.class, "devices", true);
328 }
329
330 @Override
331 public Bmv2ProviderConfig createConfig() {
332 return new Bmv2ProviderConfig();
333 }
334 }
335
336 /**
337 * Internal net-cfg event listener.
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700338 */
339 private class InternalNetworkConfigListener implements NetworkConfigListener {
340
341 @Override
342 public void event(NetworkConfigEvent event) {
343 Bmv2ProviderConfig cfg = netCfgService.getConfig(appId, Bmv2ProviderConfig.class);
344 if (cfg != null) {
345 try {
346 cfg.getDevicesInfo().stream().forEach(info -> {
Carmelo Casconec18e82c2016-06-16 14:22:36 -0700347 // FIXME: require also bmv2 internal device id from net-cfg (now is default 0)
Carmelo Casconec0fbbee2016-04-27 18:03:36 -0700348 Bmv2Device bmv2Device = new Bmv2Device(info.ip().toString(), info.port(), 0);
349 triggerProbe(bmv2Device.asDeviceId());
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700350 });
351 } catch (ConfigException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700352 log.error("Unable to read config: " + e);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700353 }
354 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700355 log.error("Unable to read config (was null)");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700356 }
357 }
358
359 @Override
360 public boolean isRelevant(NetworkConfigEvent event) {
361 return event.configClass().equals(Bmv2ProviderConfig.class) &&
362 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
363 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
364 }
365 }
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700366
367 /**
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700368 * Listener triggered by the BMv2 controller each time a hello message is received.
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700369 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700370 private class InternalDeviceListener implements Bmv2DeviceListener {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700371 @Override
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700372 public void handleHello(Bmv2Device device, int instanceId, String jsonConfigMd5) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700373 log.debug("Received hello from {}", device);
Carmelo Casconec0fbbee2016-04-27 18:03:36 -0700374 triggerProbe(device.asDeviceId());
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700375 }
376 }
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700377}