blob: 56e3b6a727da6c1a7a3e5b4278b75ea4cbfc0042 [file] [log] [blame]
kmcpeakeb172d5f2015-12-10 11:30:43 +00001/*
2 * Copyright 2015 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 */
16package org.onosproject.provider.snmp.device.impl;
17
18import com.btisystems.pronx.ems.core.snmp.DefaultSnmpConfigurationFactory;
19import com.btisystems.pronx.ems.core.snmp.ISnmpConfiguration;
20import com.btisystems.pronx.ems.core.snmp.ISnmpConfigurationFactory;
21import com.btisystems.pronx.ems.core.snmp.ISnmpSession;
22import com.btisystems.pronx.ems.core.snmp.ISnmpSessionFactory;
23import com.btisystems.pronx.ems.core.snmp.SnmpSessionFactory;
24import com.btisystems.pronx.ems.core.snmp.V2cSnmpConfiguration;
kmcpeakeb172d5f2015-12-10 11:30:43 +000025import org.apache.felix.scr.annotations.Activate;
26import org.apache.felix.scr.annotations.Component;
27import org.apache.felix.scr.annotations.Deactivate;
28import org.apache.felix.scr.annotations.Modified;
29import org.apache.felix.scr.annotations.Property;
30import org.apache.felix.scr.annotations.Reference;
31import org.apache.felix.scr.annotations.ReferenceCardinality;
32import org.onlab.packet.ChassisId;
33import org.onosproject.cfg.ComponentConfigService;
34import org.onosproject.cluster.ClusterService;
Marc De Leenheerc662d322016-02-18 16:05:10 -080035import org.onosproject.net.AnnotationKeys;
36import org.onosproject.net.DefaultAnnotations;
kmcpeakeb172d5f2015-12-10 11:30:43 +000037import org.onosproject.net.Device;
38import org.onosproject.net.DeviceId;
39import org.onosproject.net.MastershipRole;
Marc De Leenheerc662d322016-02-18 16:05:10 -080040import org.onosproject.net.SparseAnnotations;
41import org.onosproject.net.behaviour.PortDiscovery;
kmcpeakeb172d5f2015-12-10 11:30:43 +000042import org.onosproject.net.device.DefaultDeviceDescription;
43import org.onosproject.net.device.DeviceDescription;
44import org.onosproject.net.device.DeviceProvider;
45import org.onosproject.net.device.DeviceProviderRegistry;
46import org.onosproject.net.device.DeviceProviderService;
47import org.onosproject.net.device.DeviceService;
48import org.onosproject.net.provider.AbstractProvider;
49import org.onosproject.net.provider.ProviderId;
50import org.osgi.service.component.ComponentContext;
51import org.slf4j.Logger;
52
Jonathan Hart51539b82015-10-29 09:53:04 -070053import java.io.IOException;
54import java.net.URI;
55import java.net.URISyntaxException;
56import java.util.Dictionary;
57import java.util.HashMap;
58import java.util.Map;
59import java.util.concurrent.ConcurrentHashMap;
60import java.util.concurrent.ExecutorService;
61import java.util.concurrent.Executors;
62import java.util.concurrent.TimeUnit;
63
64import static com.google.common.base.Strings.isNullOrEmpty;
65import static org.onlab.util.Tools.delay;
66import static org.onlab.util.Tools.get;
67import static org.onlab.util.Tools.groupedThreads;
68import static org.slf4j.LoggerFactory.getLogger;
69
kmcpeakeb172d5f2015-12-10 11:30:43 +000070/**
71 * Provider which will try to fetch the details of SNMP devices from the core and run a capability discovery on each of
72 * the device.
73 */
74@Component(immediate = true)
75public class SnmpDeviceProvider extends AbstractProvider
76 implements DeviceProvider {
77
78 private final Logger log = getLogger(SnmpDeviceProvider.class);
79
80 private static final String UNKNOWN = "unknown";
81
82 protected Map<DeviceId, SnmpDevice> snmpDeviceMap = new ConcurrentHashMap<>();
83
84 private DeviceProviderService providerService;
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected DeviceProviderRegistry providerRegistry;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected DeviceService deviceService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected ClusterService clusterService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected ComponentConfigService cfgService;
97
98 private final ExecutorService deviceBuilder = Executors
Andrea Campanella90f044f2016-03-02 09:14:57 -080099 .newFixedThreadPool(1, groupedThreads("onos/snmp", "device-creator", log));
kmcpeakeb172d5f2015-12-10 11:30:43 +0000100
101 // Delay between events in ms.
102 private static final int EVENTINTERVAL = 5;
103
104 private static final String SCHEME = "snmp";
105
106 @Property(name = "devConfigs", value = "", label = "Instance-specific configurations")
107 private String devConfigs = null;
108
109 @Property(name = "devPasswords", value = "", label = "Instance-specific password")
110 private String devPasswords = null;
111
112 //TODO Could be replaced with a service lookup, and bundles per device variant.
113 Map<String, SnmpDeviceDescriptionProvider> providers = new HashMap<>();
114
115 private final ISnmpSessionFactory sessionFactory;
116
117 /**
118 * Creates a provider with the supplier identifier.
119 */
120 public SnmpDeviceProvider() {
121 super(new ProviderId("snmp", "org.onosproject.provider.device"));
122 sessionFactory = new SnmpSessionFactory(
123 new DefaultSnmpConfigurationFactory(new V2cSnmpConfiguration()));
Andrea Campanella3eca4a82016-02-10 17:35:14 -0800124 //TODO refactor, no hardcoding in provider, device information should be in drivers
kmcpeakeb172d5f2015-12-10 11:30:43 +0000125 providers.put("1.3.6.1.4.1.18070.2.2", new Bti7000DeviceDescriptionProvider());
126 providers.put("1.3.6.1.4.1.20408", new NetSnmpDeviceDescriptionProvider());
Marc De Leenheerc662d322016-02-18 16:05:10 -0800127 providers.put("1.3.6.1.4.562.73.6", new LumentumDeviceDescriptionProvider());
kmcpeakeb172d5f2015-12-10 11:30:43 +0000128 }
129
130 @Activate
131 public void activate(ComponentContext context) {
132 log.info("activating for snmp devices ...");
133 cfgService.registerProperties(getClass());
134 providerService = providerRegistry.register(this);
135 modified(context);
136 log.info("activated ok");
137 }
138
139 @Deactivate
140 public void deactivate(ComponentContext context) {
141
142 log.info("deactivating for snmp devices ...");
143
144 cfgService.unregisterProperties(getClass(), false);
145 try {
146 snmpDeviceMap
147 .entrySet().stream().forEach((deviceEntry) -> {
Andrea Campanella90f044f2016-03-02 09:14:57 -0800148 deviceBuilder.execute(new DeviceCreator(deviceEntry.getValue(), false));
kmcpeakeb172d5f2015-12-10 11:30:43 +0000149 });
150 deviceBuilder.awaitTermination(1000, TimeUnit.MILLISECONDS);
151 } catch (InterruptedException e) {
152 log.error("Device builder did not terminate");
153 }
154 deviceBuilder.shutdownNow();
155 snmpDeviceMap.clear();
156 providerRegistry.unregister(this);
157 providerService = null;
158 log.info("Stopped");
159 }
160
161 @Modified
162 public void modified(ComponentContext context) {
163 log.info("modified ...");
164
165 if (context == null) {
166 log.info("No configuration file");
167 return;
168 }
169 Dictionary<?, ?> properties = context.getProperties();
170
171 log.info("properties={}", context.getProperties());
172
173 String deviceCfgValue = get(properties, "devConfigs");
174 log.info("Settings: devConfigs={}", deviceCfgValue);
175 if (!isNullOrEmpty(deviceCfgValue)) {
176 addOrRemoveDevicesConfig(deviceCfgValue);
177 }
178 log.info("... modified");
179
180 }
181
182 private void addOrRemoveDevicesConfig(String deviceConfig) {
183 for (String deviceEntry : deviceConfig.split(",")) {
184 SnmpDevice device = processDeviceEntry(deviceEntry);
185 if (device != null) {
186 log.info("Device Detail:host={}, port={}, state={}",
187 new Object[]{device.getSnmpHost(),
188 device.getSnmpPort(),
189 device.getDeviceState().name()}
190 );
191 if (device.isActive()) {
Andrea Campanella90f044f2016-03-02 09:14:57 -0800192 deviceBuilder.execute(new DeviceCreator(device, true));
kmcpeakeb172d5f2015-12-10 11:30:43 +0000193 } else {
Andrea Campanella90f044f2016-03-02 09:14:57 -0800194 deviceBuilder.execute(new DeviceCreator(device, false));
kmcpeakeb172d5f2015-12-10 11:30:43 +0000195 }
196 }
197 }
198 }
199
200 private SnmpDevice processDeviceEntry(String deviceEntry) {
201 if (deviceEntry == null) {
202 log.info("No content for Device Entry, so cannot proceed further.");
203 return null;
204 }
205 log.info("Trying to convert {} to a SNMP Device Object", deviceEntry);
206 SnmpDevice device = null;
207 try {
208 String userInfo = deviceEntry.substring(0, deviceEntry
209 .lastIndexOf('@'));
210 String hostInfo = deviceEntry.substring(deviceEntry
211 .lastIndexOf('@') + 1);
212 String[] infoSplit = userInfo.split(":");
213 String username = infoSplit[0];
214 String password = infoSplit[1];
215 infoSplit = hostInfo.split(":");
216 String hostIp = infoSplit[0];
217 Integer hostPort;
218 try {
219 hostPort = Integer.parseInt(infoSplit[1]);
220 } catch (NumberFormatException nfe) {
221 log.error("Bad Configuration Data: Failed to parse host port number string: "
222 + infoSplit[1]);
223 throw nfe;
224 }
225 String deviceState = infoSplit[2];
226 if (isNullOrEmpty(username) || isNullOrEmpty(password)
227 || isNullOrEmpty(hostIp) || hostPort == 0) {
228 log.warn("Bad Configuration Data: both user and device information parts of Configuration "
229 + deviceEntry + " should be non-nullable");
230 } else {
231 device = new SnmpDevice(hostIp, hostPort, password);
232 if (!isNullOrEmpty(deviceState)) {
233 if (deviceState.toUpperCase().equals(DeviceState.ACTIVE.name())) {
234 device.setDeviceState(DeviceState.ACTIVE);
235 } else if (deviceState.toUpperCase()
236 .equals(DeviceState.INACTIVE.name())) {
237 device.setDeviceState(DeviceState.INACTIVE);
238 } else {
239 log.warn("Device State Information can not be empty, so marking the state as INVALID");
240 device.setDeviceState(DeviceState.INVALID);
241 }
242 } else {
243 log.warn("The device entry do not specify state information, so marking the state as INVALID");
244 device.setDeviceState(DeviceState.INVALID);
245 }
246 }
247 } catch (ArrayIndexOutOfBoundsException aie) {
248 log.error("Error while reading config infromation from the config file: "
249 + "The user, host and device state infomation should be "
250 + "in the order 'userInfo@hostInfo:deviceState'"
251 + deviceEntry, aie);
252 } catch (Exception e) {
253 log.error("Error while parsing config information for the device entry: "
254 + deviceEntry, e);
255 }
256 return device;
257 }
258
259 @Override
260 public void triggerProbe(DeviceId deviceId) {
261 // TODO SNMP devices should be polled at scheduled intervals to retrieve their
262 // reachability status and other details e.g.swVersion, serialNumber,chassis,
263 }
264
265 @Override
266 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
267
268 }
269
270 @Override
271 public boolean isReachable(DeviceId deviceId) {
272 SnmpDevice snmpDevice = snmpDeviceMap.get(deviceId);
273 if (snmpDevice == null) {
274 log.warn("BAD REQUEST: the requested device id: "
275 + deviceId.toString()
276 + " is not associated to any SNMP Device");
277 return false;
278 }
279 return snmpDevice.isReachable();
280 }
281
282 /**
283 * This class is intended to add or remove Configured SNMP Devices. Functionality relies on 'createFlag' and
284 * 'SnmpDevice' content. The functionality runs as a thread and depending on the 'createFlag' value it will create
285 * or remove Device entry from the core.
286 */
287 private class DeviceCreator implements Runnable {
288
289 private SnmpDevice device;
290 private boolean createFlag;
291
292 public DeviceCreator(SnmpDevice device, boolean createFlag) {
293 this.device = device;
294 this.createFlag = createFlag;
295 }
296
297 @Override
298 public void run() {
299 if (createFlag) {
300 log.info("Trying to create Device Info on ONOS core");
301 advertiseDevices();
302 } else {
303 log.info("Trying to remove Device Info on ONOS core");
304 removeDevices();
305 }
306 }
307
308 /**
309 * For each SNMP Device, remove the entry from the device store.
310 */
311 private void removeDevices() {
312 if (device == null) {
313 log.warn("The Request SNMP Device is null, cannot proceed further");
314 return;
315 }
316 try {
317 DeviceId did = getDeviceId();
318 if (!snmpDeviceMap.containsKey(did)) {
319 log.error("BAD Request: 'Currently device is not discovered, "
320 + "so cannot remove/disconnect the device: "
321 + device.deviceInfo() + "'");
322 return;
323 }
324 providerService.deviceDisconnected(did);
325 device.disconnect();
326 snmpDeviceMap.remove(did);
327 delay(EVENTINTERVAL);
328 } catch (URISyntaxException uriSyntaxExcpetion) {
329 log.error("Syntax Error while creating URI for the device: "
330 + device.deviceInfo()
331 + " couldn't remove the device from the store",
332 uriSyntaxExcpetion);
333 }
334 }
335
336 /**
337 * Initialize SNMP Device object, and notify core saying device connected.
338 */
339 private void advertiseDevices() {
340 try {
341 if (device == null) {
342 log.warn("The Request SNMP Device is null, cannot proceed further");
343 return;
344 }
345 device.init();
346 DeviceId did = getDeviceId();
347 ChassisId cid = new ChassisId();
348
Marc De Leenheerc662d322016-02-18 16:05:10 -0800349 SparseAnnotations annotations = DefaultAnnotations.builder()
350 .set(AnnotationKeys.PROTOCOL, SCHEME.toUpperCase())
351 .build();
kmcpeakeb172d5f2015-12-10 11:30:43 +0000352
353 DeviceDescription desc = new DefaultDeviceDescription(
Marc De Leenheerc662d322016-02-18 16:05:10 -0800354 did.uri(), Device.Type.OTHER, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, cid, annotations);
kmcpeakeb172d5f2015-12-10 11:30:43 +0000355
356 desc = populateDescriptionFromDevice(did, desc);
357
358 log.info("Persisting Device " + did.uri().toString());
359
360 snmpDeviceMap.put(did, device);
361 providerService.deviceConnected(did, desc);
362 log.info("Done with Device Info Creation on ONOS core. Device Info: "
363 + device.deviceInfo() + " " + did.uri().toString());
Marc De Leenheerc662d322016-02-18 16:05:10 -0800364
365 // Do port discovery if driver supports it
366 Device d = deviceService.getDevice(did);
367 if (d.is(PortDiscovery.class)) {
368 PortDiscovery portConfig = d.as(PortDiscovery.class);
369 if (portConfig != null) {
370 providerService.updatePorts(did, portConfig.getPorts());
371 }
372 } else {
373 log.warn("No port discovery behaviour for device {}", did);
374 }
375
kmcpeakeb172d5f2015-12-10 11:30:43 +0000376 delay(EVENTINTERVAL);
377 } catch (URISyntaxException e) {
378 log.error("Syntax Error while creating URI for the device: "
379 + device.deviceInfo()
380 + " couldn't persist the device onto the store", e);
381 } catch (Exception e) {
382 log.error("Error while initializing session for the device: "
383 + (device != null ? device.deviceInfo() : null), e);
384 }
385 }
Andrea Campanella3eca4a82016-02-10 17:35:14 -0800386 /**
387 * @deprecated 1.5.0 Falcon, not compliant with ONOS SB and driver architecture.
388 */
389 @Deprecated
kmcpeakeb172d5f2015-12-10 11:30:43 +0000390 private DeviceDescription populateDescriptionFromDevice(DeviceId did, DeviceDescription desc) {
391 String[] deviceComponents = did.toString().split(":");
392 if (deviceComponents.length > 1) {
393 String ipAddress = deviceComponents[1];
394 String port = deviceComponents[2];
395
Marc De Leenheerc662d322016-02-18 16:05:10 -0800396 ISnmpConfiguration config = new V2cSnmpConfiguration();
397 config.setPort(Integer.parseInt(port));
kmcpeakeb172d5f2015-12-10 11:30:43 +0000398
399 try (ISnmpSession session = sessionFactory.createSession(config, ipAddress)) {
400 // Each session will be auto-closed.
Jonathan Hart51539b82015-10-29 09:53:04 -0700401 String deviceOid = session.identifyDevice();
kmcpeakeb172d5f2015-12-10 11:30:43 +0000402
Jonathan Hart51539b82015-10-29 09:53:04 -0700403 if (providers.containsKey(deviceOid)) {
404 desc = providers.get(deviceOid).populateDescription(session, desc);
kmcpeakeb172d5f2015-12-10 11:30:43 +0000405 }
406
407 } catch (IOException | RuntimeException ex) {
408 log.error("Failed to walk device.", ex.getMessage());
409 log.debug("Detailed problem was ", ex);
410 }
411 }
412 return desc;
413 }
414
415 /**
416 * This will build a device id for the device.
417 */
418 private DeviceId getDeviceId() throws URISyntaxException {
Jonathan Hart51539b82015-10-29 09:53:04 -0700419 String additionalSsp = new StringBuilder(
kmcpeakeb172d5f2015-12-10 11:30:43 +0000420 device.getSnmpHost()).append(":")
421 .append(device.getSnmpPort()).toString();
Jonathan Hart51539b82015-10-29 09:53:04 -0700422 return DeviceId.deviceId(new URI(SCHEME, additionalSsp,
kmcpeakeb172d5f2015-12-10 11:30:43 +0000423 null));
424 }
425 }
426
427 protected ISnmpSessionFactory getSessionFactory(ISnmpConfigurationFactory configurationFactory) {
428 return new SnmpSessionFactory(configurationFactory);
429 }
430}