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