blob: 69d9a432980adb47502c7427d48baa2e329d9188 [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 Cascone3bb71c12016-04-06 21:30:44 -070026import org.onlab.packet.ChassisId;
Carmelo Cascone442a9622016-05-03 11:16:20 -070027import org.onlab.util.HexString;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070028import org.onlab.util.Timer;
29import org.onosproject.bmv2.api.runtime.Bmv2ControlPlaneServer;
30import org.onosproject.bmv2.api.runtime.Bmv2Device;
Carmelo Cascone442a9622016-05-03 11:16:20 -070031import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
32import org.onosproject.bmv2.ctl.Bmv2ThriftClient;
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;
37import org.onosproject.net.AnnotationKeys;
38import org.onosproject.net.DefaultAnnotations;
39import org.onosproject.net.Device;
40import org.onosproject.net.DeviceId;
41import org.onosproject.net.MastershipRole;
42import org.onosproject.net.PortNumber;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070043import org.onosproject.net.behaviour.PortDiscovery;
44import org.onosproject.net.config.ConfigFactory;
45import org.onosproject.net.config.NetworkConfigEvent;
46import org.onosproject.net.config.NetworkConfigListener;
47import org.onosproject.net.config.NetworkConfigRegistry;
48import org.onosproject.net.device.DefaultDeviceDescription;
49import org.onosproject.net.device.DeviceDescription;
50import org.onosproject.net.device.DeviceService;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070051import org.onosproject.net.device.PortDescription;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070052import org.onosproject.net.provider.ProviderId;
53import org.slf4j.Logger;
54
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070055import java.util.List;
56import java.util.concurrent.ConcurrentMap;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070057import java.util.concurrent.ExecutorService;
58import java.util.concurrent.Executors;
59import java.util.concurrent.TimeUnit;
60
61import static org.onlab.util.Tools.groupedThreads;
Carmelo Cascone442a9622016-05-03 11:16:20 -070062import static org.onosproject.bmv2.api.runtime.Bmv2Device.*;
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070063import static org.onosproject.bmv2.ctl.Bmv2ThriftClient.forceDisconnectOf;
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070064import static org.onosproject.bmv2.ctl.Bmv2ThriftClient.ping;
65import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
66import static org.slf4j.LoggerFactory.getLogger;
67
68/**
69 * BMv2 device provider.
70 */
71@Component(immediate = true)
72public class Bmv2DeviceProvider extends AbstractDeviceProvider {
73
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070074 private static final Logger LOG = getLogger(Bmv2DeviceProvider.class);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070075
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070076 private static final String APP_NAME = "org.onosproject.bmv2";
77 private static final String UNKNOWN = "unknown";
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070078 private static final int POLL_INTERVAL = 5; // seconds
Carmelo Cascone3bb71c12016-04-06 21:30:44 -070079
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected NetworkConfigRegistry netCfgService;
82
83 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected CoreService coreService;
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected DeviceService deviceService;
88
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070089 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected Bmv2ControlPlaneServer controlPlaneServer;
91
92 private final ExecutorService deviceDiscoveryExecutor = Executors
93 .newFixedThreadPool(5, groupedThreads("onos/bmv2", "device-discovery", LOG));
94
95 private final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
96 private final ConfigFactory cfgFactory = new InternalConfigFactory();
97 private final ConcurrentMap<DeviceId, Boolean> activeDevices = Maps.newConcurrentMap();
98 private final DevicePoller devicePoller = new DevicePoller();
99 private final InternalHelloListener helloListener = new InternalHelloListener();
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700100 private ApplicationId appId;
101
102 /**
103 * Creates a Bmv2 device provider with the supplied identifier.
104 */
105 public Bmv2DeviceProvider() {
106 super(new ProviderId("bmv2", "org.onosproject.provider.device"));
107 }
108
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700109 @Override
110 protected void activate() {
111 appId = coreService.registerApplication(APP_NAME);
112 netCfgService.registerConfigFactory(cfgFactory);
113 netCfgService.addListener(cfgListener);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700114 controlPlaneServer.addHelloListener(helloListener);
115 devicePoller.start();
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700116 super.activate();
117 }
118
119 @Override
120 protected void deactivate() {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700121 devicePoller.stop();
122 controlPlaneServer.removeHelloListener(helloListener);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700123 try {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700124 activeDevices.forEach((did, value) -> {
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700125 deviceDiscoveryExecutor.execute(() -> disconnectDevice(did));
126 });
127 deviceDiscoveryExecutor.awaitTermination(1000, TimeUnit.MILLISECONDS);
128 } catch (InterruptedException e) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700129 LOG.error("Device discovery threads did not terminate");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700130 }
131 deviceDiscoveryExecutor.shutdownNow();
132 netCfgService.unregisterConfigFactory(cfgFactory);
133 netCfgService.removeListener(cfgListener);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700134 super.deactivate();
135 }
136
137 @Override
138 public void triggerProbe(DeviceId deviceId) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700139 // Asynchronously trigger probe task.
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700140 deviceDiscoveryExecutor.execute(() -> executeProbe(deviceId));
141 }
142
143 private void executeProbe(DeviceId did) {
144 boolean reachable = isReachable(did);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700145 LOG.debug("Probed device: id={}, reachable={}",
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700146 did.toString(),
147 reachable);
148 if (reachable) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700149 discoverDevice(did);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700150 } else {
151 disconnectDevice(did);
152 }
153 }
154
155 @Override
156 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700157 LOG.debug("roleChanged() is not yet implemented");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700158 // TODO: implement mastership handling
159 }
160
161 @Override
162 public boolean isReachable(DeviceId deviceId) {
163 return ping(deviceId);
164 }
165
166 @Override
167 public void changePortState(DeviceId deviceId, PortNumber portNumber, boolean enable) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700168 LOG.debug("changePortState() is not yet implemented");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700169 // TODO: implement port handling
170 }
171
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700172 private void discoverDevice(DeviceId did) {
173 LOG.debug("Starting device discovery... deviceId={}", did);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700174
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700175 // Atomically notify device to core and update port information.
176 activeDevices.compute(did, (k, v) -> {
177 if (!deviceService.isAvailable(did)) {
178 // Device not available in the core, connect it now.
Carmelo Cascone442a9622016-05-03 11:16:20 -0700179 DefaultAnnotations.Builder annotationsBuilder = DefaultAnnotations.builder()
180 .set(AnnotationKeys.PROTOCOL, SCHEME);
181 dumpJsonConfigToAnnotations(did, annotationsBuilder);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700182 DeviceDescription descr = new DefaultDeviceDescription(
183 did.uri(), Device.Type.SWITCH, MANUFACTURER, HW_VERSION,
Carmelo Cascone442a9622016-05-03 11:16:20 -0700184 UNKNOWN, UNKNOWN, new ChassisId(), annotationsBuilder.build());
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700185 // Reset device state (cleanup entries, etc.)
186 resetDeviceState(did);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700187 providerService.deviceConnected(did, descr);
188 }
Carmelo Cascone442a9622016-05-03 11:16:20 -0700189 updatePorts(did);
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700190 return true;
191 });
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700192 }
193
Carmelo Cascone442a9622016-05-03 11:16:20 -0700194 private void dumpJsonConfigToAnnotations(DeviceId did, DefaultAnnotations.Builder builder) {
195 // TODO: store json config string somewhere else, possibly in a Bmv2Controller (see ONOS-4419)
196 try {
197 String md5 = Bmv2ThriftClient.of(did).getJsonConfigMd5();
198 // Convert to hex string for readability.
199 md5 = HexString.toHexString(md5.getBytes());
200 String jsonString = Bmv2ThriftClient.of(did).dumpJsonConfig();
201 builder.set("bmv2JsonConfigMd5", md5);
202 builder.set("bmv2JsonConfigValue", jsonString);
203 } catch (Bmv2RuntimeException e) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700204 LOG.warn("Unable to dump device JSON config from device {}: {}", did, e.toString());
205 }
206 }
207
208 private void resetDeviceState(DeviceId did) {
209 try {
210 Bmv2ThriftClient.of(did).resetState();
211 } catch (Bmv2RuntimeException e) {
212 LOG.warn("Unable to reset {}: {}", did, e.toString());
Carmelo Cascone442a9622016-05-03 11:16:20 -0700213 }
214 }
215
216 private void updatePorts(DeviceId did) {
217 Device device = deviceService.getDevice(did);
218 if (device.is(PortDiscovery.class)) {
219 PortDiscovery portConfig = device.as(PortDiscovery.class);
220 List<PortDescription> portDescriptions = portConfig.getPorts();
221 providerService.updatePorts(did, portDescriptions);
222 } else {
223 LOG.warn("No PortDiscovery behavior for device {}", did);
224 }
225 }
226
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700227 private void disconnectDevice(DeviceId did) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700228 LOG.debug("Trying to disconnect device from core... deviceId={}", did);
229
230 // Atomically disconnect device.
231 activeDevices.compute(did, (k, v) -> {
232 if (deviceService.isAvailable(did)) {
233 providerService.deviceDisconnected(did);
Carmelo Cascone442a9622016-05-03 11:16:20 -0700234 // Make sure to close the transport session with device. Do we really need this?
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700235 forceDisconnectOf(did);
236 }
237 return null;
238 });
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700239 }
240
241 /**
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700242 * Internal net-cfg config factory.
243 */
244 private class InternalConfigFactory extends ConfigFactory<ApplicationId, Bmv2ProviderConfig> {
245
246 InternalConfigFactory() {
247 super(APP_SUBJECT_FACTORY, Bmv2ProviderConfig.class, "devices", true);
248 }
249
250 @Override
251 public Bmv2ProviderConfig createConfig() {
252 return new Bmv2ProviderConfig();
253 }
254 }
255
256 /**
257 * Internal net-cfg event listener.
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700258 */
259 private class InternalNetworkConfigListener implements NetworkConfigListener {
260
261 @Override
262 public void event(NetworkConfigEvent event) {
263 Bmv2ProviderConfig cfg = netCfgService.getConfig(appId, Bmv2ProviderConfig.class);
264 if (cfg != null) {
265 try {
266 cfg.getDevicesInfo().stream().forEach(info -> {
Carmelo Casconec0fbbee2016-04-27 18:03:36 -0700267 // TODO: require also bmv2 internal device id from net-cfg (now is default 0)
268 Bmv2Device bmv2Device = new Bmv2Device(info.ip().toString(), info.port(), 0);
269 triggerProbe(bmv2Device.asDeviceId());
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700270 });
271 } catch (ConfigException e) {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700272 LOG.error("Unable to read config: " + e);
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700273 }
274 } else {
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700275 LOG.error("Unable to read config (was null)");
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700276 }
277 }
278
279 @Override
280 public boolean isRelevant(NetworkConfigEvent event) {
281 return event.configClass().equals(Bmv2ProviderConfig.class) &&
282 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
283 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
284 }
285 }
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700286
287 /**
288 * Listener triggered by Bmv2ControlPlaneServer each time a hello message is received.
289 */
290 private class InternalHelloListener implements Bmv2ControlPlaneServer.HelloListener {
291 @Override
292 public void handleHello(Bmv2Device device) {
293 log.debug("Received hello from {}", device);
Carmelo Casconec0fbbee2016-04-27 18:03:36 -0700294 triggerProbe(device.asDeviceId());
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700295 }
296 }
297
298 /**
299 * Task that periodically trigger device probes.
300 */
301 private class DevicePoller implements TimerTask {
302
303 private final HashedWheelTimer timer = Timer.getTimer();
304 private Timeout timeout;
305
306 @Override
307 public void run(Timeout timeout) throws Exception {
308 if (timeout.isCancelled()) {
309 return;
310 }
311 log.debug("Executing polling on {} devices...", activeDevices.size());
312 activeDevices.forEach((did, value) -> triggerProbe(did));
313 timeout.getTimer().newTimeout(this, POLL_INTERVAL, TimeUnit.SECONDS);
314 }
315
316 /**
317 * Starts the collector.
318 */
319 synchronized void start() {
320 LOG.info("Starting device poller...");
321 timeout = timer.newTimeout(this, 1, TimeUnit.SECONDS);
322 }
323
324 /**
325 * Stops the collector.
326 */
327 synchronized void stop() {
328 LOG.info("Stopping device poller...");
329 timeout.cancel();
330 }
331 }
Carmelo Cascone3bb71c12016-04-06 21:30:44 -0700332}