blob: f18ae7218f1f4b85bf7970c585703e8e6b0d4d06 [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;
25import static com.google.common.base.Strings.isNullOrEmpty;
26import java.io.IOException;
27import static org.onlab.util.Tools.delay;
28import static org.onlab.util.Tools.get;
29import static org.onlab.util.Tools.groupedThreads;
30import static org.slf4j.LoggerFactory.getLogger;
31
32import java.net.URI;
33import java.net.URISyntaxException;
34import java.util.Dictionary;
35import java.util.HashMap;
36import java.util.Map;
37import java.util.concurrent.ConcurrentHashMap;
38import java.util.concurrent.ExecutorService;
39import java.util.concurrent.Executors;
40import java.util.concurrent.TimeUnit;
41
42import org.apache.felix.scr.annotations.Activate;
43import org.apache.felix.scr.annotations.Component;
44import org.apache.felix.scr.annotations.Deactivate;
45import org.apache.felix.scr.annotations.Modified;
46import org.apache.felix.scr.annotations.Property;
47import org.apache.felix.scr.annotations.Reference;
48import org.apache.felix.scr.annotations.ReferenceCardinality;
49import org.onlab.packet.ChassisId;
50import org.onosproject.cfg.ComponentConfigService;
51import org.onosproject.cluster.ClusterService;
52import org.onosproject.net.Device;
53import org.onosproject.net.DeviceId;
54import org.onosproject.net.MastershipRole;
55import org.onosproject.net.device.DefaultDeviceDescription;
56import org.onosproject.net.device.DeviceDescription;
57import org.onosproject.net.device.DeviceProvider;
58import org.onosproject.net.device.DeviceProviderRegistry;
59import org.onosproject.net.device.DeviceProviderService;
60import org.onosproject.net.device.DeviceService;
61import org.onosproject.net.provider.AbstractProvider;
62import org.onosproject.net.provider.ProviderId;
63import org.osgi.service.component.ComponentContext;
64import org.slf4j.Logger;
65
66/**
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
95 .newFixedThreadPool(1, groupedThreads("onos/snmp", "device-creator"));
96
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()));
120 providers.put("1.3.6.1.4.1.18070.2.2", new Bti7000DeviceDescriptionProvider());
121 providers.put("1.3.6.1.4.1.20408", new NetSnmpDeviceDescriptionProvider());
122 }
123
124 @Activate
125 public void activate(ComponentContext context) {
126 log.info("activating for snmp devices ...");
127 cfgService.registerProperties(getClass());
128 providerService = providerRegistry.register(this);
129 modified(context);
130 log.info("activated ok");
131 }
132
133 @Deactivate
134 public void deactivate(ComponentContext context) {
135
136 log.info("deactivating for snmp devices ...");
137
138 cfgService.unregisterProperties(getClass(), false);
139 try {
140 snmpDeviceMap
141 .entrySet().stream().forEach((deviceEntry) -> {
142 deviceBuilder.submit(new DeviceCreator(deviceEntry.getValue(), false));
143 });
144 deviceBuilder.awaitTermination(1000, TimeUnit.MILLISECONDS);
145 } catch (InterruptedException e) {
146 log.error("Device builder did not terminate");
147 }
148 deviceBuilder.shutdownNow();
149 snmpDeviceMap.clear();
150 providerRegistry.unregister(this);
151 providerService = null;
152 log.info("Stopped");
153 }
154
155 @Modified
156 public void modified(ComponentContext context) {
157 log.info("modified ...");
158
159 if (context == null) {
160 log.info("No configuration file");
161 return;
162 }
163 Dictionary<?, ?> properties = context.getProperties();
164
165 log.info("properties={}", context.getProperties());
166
167 String deviceCfgValue = get(properties, "devConfigs");
168 log.info("Settings: devConfigs={}", deviceCfgValue);
169 if (!isNullOrEmpty(deviceCfgValue)) {
170 addOrRemoveDevicesConfig(deviceCfgValue);
171 }
172 log.info("... modified");
173
174 }
175
176 private void addOrRemoveDevicesConfig(String deviceConfig) {
177 for (String deviceEntry : deviceConfig.split(",")) {
178 SnmpDevice device = processDeviceEntry(deviceEntry);
179 if (device != null) {
180 log.info("Device Detail:host={}, port={}, state={}",
181 new Object[]{device.getSnmpHost(),
182 device.getSnmpPort(),
183 device.getDeviceState().name()}
184 );
185 if (device.isActive()) {
186 deviceBuilder.submit(new DeviceCreator(device, true));
187 } else {
188 deviceBuilder.submit(new DeviceCreator(device, false));
189 }
190 }
191 }
192 }
193
194 private SnmpDevice processDeviceEntry(String deviceEntry) {
195 if (deviceEntry == null) {
196 log.info("No content for Device Entry, so cannot proceed further.");
197 return null;
198 }
199 log.info("Trying to convert {} to a SNMP Device Object", deviceEntry);
200 SnmpDevice device = null;
201 try {
202 String userInfo = deviceEntry.substring(0, deviceEntry
203 .lastIndexOf('@'));
204 String hostInfo = deviceEntry.substring(deviceEntry
205 .lastIndexOf('@') + 1);
206 String[] infoSplit = userInfo.split(":");
207 String username = infoSplit[0];
208 String password = infoSplit[1];
209 infoSplit = hostInfo.split(":");
210 String hostIp = infoSplit[0];
211 Integer hostPort;
212 try {
213 hostPort = Integer.parseInt(infoSplit[1]);
214 } catch (NumberFormatException nfe) {
215 log.error("Bad Configuration Data: Failed to parse host port number string: "
216 + infoSplit[1]);
217 throw nfe;
218 }
219 String deviceState = infoSplit[2];
220 if (isNullOrEmpty(username) || isNullOrEmpty(password)
221 || isNullOrEmpty(hostIp) || hostPort == 0) {
222 log.warn("Bad Configuration Data: both user and device information parts of Configuration "
223 + deviceEntry + " should be non-nullable");
224 } else {
225 device = new SnmpDevice(hostIp, hostPort, password);
226 if (!isNullOrEmpty(deviceState)) {
227 if (deviceState.toUpperCase().equals(DeviceState.ACTIVE.name())) {
228 device.setDeviceState(DeviceState.ACTIVE);
229 } else if (deviceState.toUpperCase()
230 .equals(DeviceState.INACTIVE.name())) {
231 device.setDeviceState(DeviceState.INACTIVE);
232 } else {
233 log.warn("Device State Information can not be empty, so marking the state as INVALID");
234 device.setDeviceState(DeviceState.INVALID);
235 }
236 } else {
237 log.warn("The device entry do not specify state information, so marking the state as INVALID");
238 device.setDeviceState(DeviceState.INVALID);
239 }
240 }
241 } catch (ArrayIndexOutOfBoundsException aie) {
242 log.error("Error while reading config infromation from the config file: "
243 + "The user, host and device state infomation should be "
244 + "in the order 'userInfo@hostInfo:deviceState'"
245 + deviceEntry, aie);
246 } catch (Exception e) {
247 log.error("Error while parsing config information for the device entry: "
248 + deviceEntry, e);
249 }
250 return device;
251 }
252
253 @Override
254 public void triggerProbe(DeviceId deviceId) {
255 // TODO SNMP devices should be polled at scheduled intervals to retrieve their
256 // reachability status and other details e.g.swVersion, serialNumber,chassis,
257 }
258
259 @Override
260 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
261
262 }
263
264 @Override
265 public boolean isReachable(DeviceId deviceId) {
266 SnmpDevice snmpDevice = snmpDeviceMap.get(deviceId);
267 if (snmpDevice == null) {
268 log.warn("BAD REQUEST: the requested device id: "
269 + deviceId.toString()
270 + " is not associated to any SNMP Device");
271 return false;
272 }
273 return snmpDevice.isReachable();
274 }
275
276 /**
277 * This class is intended to add or remove Configured SNMP Devices. Functionality relies on 'createFlag' and
278 * 'SnmpDevice' content. The functionality runs as a thread and depending on the 'createFlag' value it will create
279 * or remove Device entry from the core.
280 */
281 private class DeviceCreator implements Runnable {
282
283 private SnmpDevice device;
284 private boolean createFlag;
285
286 public DeviceCreator(SnmpDevice device, boolean createFlag) {
287 this.device = device;
288 this.createFlag = createFlag;
289 }
290
291 @Override
292 public void run() {
293 if (createFlag) {
294 log.info("Trying to create Device Info on ONOS core");
295 advertiseDevices();
296 } else {
297 log.info("Trying to remove Device Info on ONOS core");
298 removeDevices();
299 }
300 }
301
302 /**
303 * For each SNMP Device, remove the entry from the device store.
304 */
305 private void removeDevices() {
306 if (device == null) {
307 log.warn("The Request SNMP Device is null, cannot proceed further");
308 return;
309 }
310 try {
311 DeviceId did = getDeviceId();
312 if (!snmpDeviceMap.containsKey(did)) {
313 log.error("BAD Request: 'Currently device is not discovered, "
314 + "so cannot remove/disconnect the device: "
315 + device.deviceInfo() + "'");
316 return;
317 }
318 providerService.deviceDisconnected(did);
319 device.disconnect();
320 snmpDeviceMap.remove(did);
321 delay(EVENTINTERVAL);
322 } catch (URISyntaxException uriSyntaxExcpetion) {
323 log.error("Syntax Error while creating URI for the device: "
324 + device.deviceInfo()
325 + " couldn't remove the device from the store",
326 uriSyntaxExcpetion);
327 }
328 }
329
330 /**
331 * Initialize SNMP Device object, and notify core saying device connected.
332 */
333 private void advertiseDevices() {
334 try {
335 if (device == null) {
336 log.warn("The Request SNMP Device is null, cannot proceed further");
337 return;
338 }
339 device.init();
340 DeviceId did = getDeviceId();
341 ChassisId cid = new ChassisId();
342
343
344 DeviceDescription desc = new DefaultDeviceDescription(
345 did.uri(), Device.Type.OTHER, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, cid);
346
347 desc = populateDescriptionFromDevice(did, desc);
348
349 log.info("Persisting Device " + did.uri().toString());
350
351 snmpDeviceMap.put(did, device);
352 providerService.deviceConnected(did, desc);
353 log.info("Done with Device Info Creation on ONOS core. Device Info: "
354 + device.deviceInfo() + " " + did.uri().toString());
355 delay(EVENTINTERVAL);
356 } catch (URISyntaxException e) {
357 log.error("Syntax Error while creating URI for the device: "
358 + device.deviceInfo()
359 + " couldn't persist the device onto the store", e);
360 } catch (Exception e) {
361 log.error("Error while initializing session for the device: "
362 + (device != null ? device.deviceInfo() : null), e);
363 }
364 }
365
366 private DeviceDescription populateDescriptionFromDevice(DeviceId did, DeviceDescription desc) {
367 String[] deviceComponents = did.toString().split(":");
368 if (deviceComponents.length > 1) {
369 String ipAddress = deviceComponents[1];
370 String port = deviceComponents[2];
371
372 ISnmpConfiguration config = new V2cSnmpConfiguration();
373 config.setPort(Integer.parseInt(port));
374
375 try (ISnmpSession session = sessionFactory.createSession(config, ipAddress)) {
376 // Each session will be auto-closed.
377 String deviceOID = session.identifyDevice();
378
379 if (providers.containsKey(deviceOID)) {
380 desc = providers.get(deviceOID).populateDescription(session, desc);
381 }
382
383 } catch (IOException | RuntimeException ex) {
384 log.error("Failed to walk device.", ex.getMessage());
385 log.debug("Detailed problem was ", ex);
386 }
387 }
388 return desc;
389 }
390
391 /**
392 * This will build a device id for the device.
393 */
394 private DeviceId getDeviceId() throws URISyntaxException {
395 String additionalSSP = new StringBuilder(
396 device.getSnmpHost()).append(":")
397 .append(device.getSnmpPort()).toString();
398 return DeviceId.deviceId(new URI(SCHEME, additionalSSP,
399 null));
400 }
401 }
402
403 protected ISnmpSessionFactory getSessionFactory(ISnmpConfigurationFactory configurationFactory) {
404 return new SnmpSessionFactory(configurationFactory);
405 }
406}