blob: 428c9d2c45b52fc017de8b3cae60df472acebafc [file] [log] [blame]
andreaeb70a942015-10-16 21:34:46 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
andreaeb70a942015-10-16 21:34:46 -07003 *
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
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070017package org.onosproject.netconf.ctl.impl;
andreaeb70a942015-10-16 21:34:46 -070018
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
Andreas Papazois4752cfa2016-04-25 14:52:12 +030022import org.apache.felix.scr.annotations.Modified;
23import org.apache.felix.scr.annotations.Property;
Andrea Campanella7e6200a2016-03-21 09:48:40 -070024import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
andreaeb70a942015-10-16 21:34:46 -070026import org.apache.felix.scr.annotations.Service;
27import org.onlab.packet.IpAddress;
Andreas Papazois4752cfa2016-04-25 14:52:12 +030028import org.onosproject.cfg.ComponentConfigService;
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -060029import org.onosproject.net.AnnotationKeys;
Andrea Campanella7e6200a2016-03-21 09:48:40 -070030import org.onosproject.net.Device;
andreaeb70a942015-10-16 21:34:46 -070031import org.onosproject.net.DeviceId;
Andrea Campanella7e6200a2016-03-21 09:48:40 -070032import org.onosproject.net.device.DeviceService;
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -060033import org.onosproject.net.key.DeviceKey;
Andrea Campanella7e6200a2016-03-21 09:48:40 -070034import org.onosproject.net.key.DeviceKeyId;
35import org.onosproject.net.key.DeviceKeyService;
36import org.onosproject.net.key.UsernamePassword;
andreaeb70a942015-10-16 21:34:46 -070037import org.onosproject.netconf.NetconfController;
38import org.onosproject.netconf.NetconfDevice;
Andrea Campanella950310c2016-02-12 18:14:38 -080039import org.onosproject.netconf.NetconfDeviceFactory;
andreaeb70a942015-10-16 21:34:46 -070040import org.onosproject.netconf.NetconfDeviceInfo;
41import org.onosproject.netconf.NetconfDeviceListener;
Andrea Campanella2464dc32016-02-17 17:54:53 -080042import org.onosproject.netconf.NetconfDeviceOutputEvent;
43import org.onosproject.netconf.NetconfDeviceOutputEventListener;
Andrea Campanella101417d2015-12-11 17:58:07 -080044import org.onosproject.netconf.NetconfException;
andreaeb70a942015-10-16 21:34:46 -070045import org.osgi.service.component.ComponentContext;
46import org.slf4j.Logger;
47import org.slf4j.LoggerFactory;
48
Andrea Campanella7e6200a2016-03-21 09:48:40 -070049import java.util.Arrays;
Andreas Papazois4752cfa2016-04-25 14:52:12 +030050import java.util.Dictionary;
andreaeb70a942015-10-16 21:34:46 -070051import java.util.Map;
52import java.util.Set;
53import java.util.concurrent.ConcurrentHashMap;
54import java.util.concurrent.CopyOnWriteArraySet;
Andrea Campanellac3627842017-04-04 18:06:54 +020055import java.util.concurrent.ExecutorService;
56import java.util.concurrent.Executors;
andreaeb70a942015-10-16 21:34:46 -070057
Andreas Papazois4752cfa2016-04-25 14:52:12 +030058import static com.google.common.base.Strings.isNullOrEmpty;
59import static org.onlab.util.Tools.get;
Andrea Campanellac3627842017-04-04 18:06:54 +020060import static org.onlab.util.Tools.groupedThreads;
Andreas Papazois4752cfa2016-04-25 14:52:12 +030061
andreaeb70a942015-10-16 21:34:46 -070062/**
63 * The implementation of NetconfController.
64 */
65@Component(immediate = true)
66@Service
67public class NetconfControllerImpl implements NetconfController {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070068
69 private static final String ETHZ_SSH2 = "ethz-ssh2";
70
Sean Condon334ad692016-12-13 17:56:56 +000071 private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 5;
72 private static final String PROP_NETCONF_CONNECT_TIMEOUT = "netconfConnectTimeout";
73 @Property(name = PROP_NETCONF_CONNECT_TIMEOUT, intValue = DEFAULT_CONNECT_TIMEOUT_SECONDS,
74 label = "Time (in seconds) to wait for a NETCONF connect.")
75 protected static int netconfConnectTimeout = DEFAULT_CONNECT_TIMEOUT_SECONDS;
76
Andreas Papazois4752cfa2016-04-25 14:52:12 +030077 private static final String PROP_NETCONF_REPLY_TIMEOUT = "netconfReplyTimeout";
78 private static final int DEFAULT_REPLY_TIMEOUT_SECONDS = 5;
79 @Property(name = PROP_NETCONF_REPLY_TIMEOUT, intValue = DEFAULT_REPLY_TIMEOUT_SECONDS,
80 label = "Time (in seconds) waiting for a NetConf reply")
81 protected static int netconfReplyTimeout = DEFAULT_REPLY_TIMEOUT_SECONDS;
82
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070083 private static final String SSH_LIBRARY = "sshLibrary";
84 private static final String APACHE_MINA = "apache_mina";
85 @Property(name = SSH_LIBRARY, value = APACHE_MINA,
86 label = "Ssh Llbrary instead of Apache Mina (i.e. ethz-ssh2")
87 protected static String sshLibrary = APACHE_MINA;
88
Andreas Papazois4752cfa2016-04-25 14:52:12 +030089 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected ComponentConfigService cfgService;
91
Andrea Campanella7e6200a2016-03-21 09:48:40 -070092 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected DeviceService deviceService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected DeviceKeyService deviceKeyService;
andreaeb70a942015-10-16 21:34:46 -070097
98 public static final Logger log = LoggerFactory
99 .getLogger(NetconfControllerImpl.class);
100
Andrea Campanella8b1cb672016-01-25 13:58:58 -0800101 private Map<DeviceId, NetconfDevice> netconfDeviceMap = new ConcurrentHashMap<>();
andreaeb70a942015-10-16 21:34:46 -0700102
Andrea Campanella2464dc32016-02-17 17:54:53 -0800103 private final NetconfDeviceOutputEventListener downListener = new DeviceDownEventListener();
104
andreaeb70a942015-10-16 21:34:46 -0700105 protected Set<NetconfDeviceListener> netconfDeviceListeners = new CopyOnWriteArraySet<>();
Andrea Campanella950310c2016-02-12 18:14:38 -0800106 protected NetconfDeviceFactory deviceFactory = new DefaultNetconfDeviceFactory();
andreaeb70a942015-10-16 21:34:46 -0700107
Andrea Campanellac3627842017-04-04 18:06:54 +0200108 protected final ExecutorService executor =
109 Executors.newCachedThreadPool(groupedThreads("onos/netconfdevicecontroller",
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700110 "connection-reopen-%d", log));
Andrea Campanellac3627842017-04-04 18:06:54 +0200111
andreaeb70a942015-10-16 21:34:46 -0700112 @Activate
113 public void activate(ComponentContext context) {
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300114 cfgService.registerProperties(getClass());
115 modified(context);
andreaeb70a942015-10-16 21:34:46 -0700116 log.info("Started");
117 }
118
119 @Deactivate
120 public void deactivate() {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700121 netconfDeviceMap.values().forEach(device -> {
122 device.getSession().removeDeviceOutputListener(downListener);
123 device.disconnect();
124 });
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300125 cfgService.unregisterProperties(getClass(), false);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700126 netconfDeviceListeners.clear();
andreaeb70a942015-10-16 21:34:46 -0700127 netconfDeviceMap.clear();
128 log.info("Stopped");
129 }
130
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300131 @Modified
132 public void modified(ComponentContext context) {
133 if (context == null) {
134 netconfReplyTimeout = DEFAULT_REPLY_TIMEOUT_SECONDS;
Sean Condon334ad692016-12-13 17:56:56 +0000135 netconfConnectTimeout = DEFAULT_CONNECT_TIMEOUT_SECONDS;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700136 sshLibrary = APACHE_MINA;
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300137 log.info("No component configuration");
138 return;
139 }
140
141 Dictionary<?, ?> properties = context.getProperties();
142
143 int newNetconfReplyTimeout;
Sean Condon334ad692016-12-13 17:56:56 +0000144 int newNetconfConnectTimeout;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700145 String newSshLibrary;
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300146 try {
147 String s = get(properties, PROP_NETCONF_REPLY_TIMEOUT);
148 newNetconfReplyTimeout = isNullOrEmpty(s) ?
149 netconfReplyTimeout : Integer.parseInt(s.trim());
Sean Condon334ad692016-12-13 17:56:56 +0000150
151 s = get(properties, PROP_NETCONF_CONNECT_TIMEOUT);
152 newNetconfConnectTimeout = isNullOrEmpty(s) ?
153 netconfConnectTimeout : Integer.parseInt(s.trim());
154
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700155 newSshLibrary = get(properties, SSH_LIBRARY);
156
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300157 } catch (NumberFormatException e) {
158 log.warn("Component configuration had invalid value", e);
159 return;
160 }
161
Sean Condon334ad692016-12-13 17:56:56 +0000162 if (newNetconfConnectTimeout < 0) {
163 log.warn("netconfConnectTimeout is invalid - less than 0");
164 return;
165 } else if (newNetconfReplyTimeout <= 0) {
166 log.warn("netconfReplyTimeout is invalid - 0 or less.");
167 return;
168 }
169
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300170 netconfReplyTimeout = newNetconfReplyTimeout;
Sean Condon334ad692016-12-13 17:56:56 +0000171 netconfConnectTimeout = newNetconfConnectTimeout;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700172 sshLibrary = newSshLibrary;
173 log.info("Settings: {} = {}, {} = {}, {} = {}",
174 PROP_NETCONF_REPLY_TIMEOUT, netconfReplyTimeout,
175 PROP_NETCONF_CONNECT_TIMEOUT, netconfConnectTimeout,
176 SSH_LIBRARY, sshLibrary);
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300177 }
178
andreaeb70a942015-10-16 21:34:46 -0700179 @Override
180 public void addDeviceListener(NetconfDeviceListener listener) {
181 if (!netconfDeviceListeners.contains(listener)) {
182 netconfDeviceListeners.add(listener);
183 }
184 }
185
186 @Override
187 public void removeDeviceListener(NetconfDeviceListener listener) {
188 netconfDeviceListeners.remove(listener);
189 }
190
191 @Override
192 public NetconfDevice getNetconfDevice(DeviceId deviceInfo) {
193 return netconfDeviceMap.get(deviceInfo);
194 }
195
196 @Override
197 public NetconfDevice getNetconfDevice(IpAddress ip, int port) {
andreaeb70a942015-10-16 21:34:46 -0700198 for (DeviceId info : netconfDeviceMap.keySet()) {
Andrea Campanella57efbb22016-02-11 14:21:41 -0800199 if (info.uri().getSchemeSpecificPart().equals(ip.toString() + ":" + port)) {
andreaeb70a942015-10-16 21:34:46 -0700200 return netconfDeviceMap.get(info);
201 }
202 }
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800203 return null;
andreaeb70a942015-10-16 21:34:46 -0700204 }
205
206 @Override
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700207 public NetconfDevice connectDevice(DeviceId deviceId) throws NetconfException {
208 if (netconfDeviceMap.containsKey(deviceId)) {
209 log.debug("Device {} is already present", deviceId);
210 return netconfDeviceMap.get(deviceId);
andreaeb70a942015-10-16 21:34:46 -0700211 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700212 log.debug("Creating NETCONF device {}", deviceId);
213 Device device = deviceService.getDevice(deviceId);
214 String ip;
215 int port;
216 if (device != null) {
217 ip = device.annotations().value("ipaddress");
218 port = Integer.parseInt(device.annotations().value("port"));
219 } else {
220 String[] info = deviceId.toString().split(":");
221 if (info.length == 3) {
222 ip = info[1];
223 port = Integer.parseInt(info[2]);
224 } else {
225 ip = Arrays.asList(info).stream().filter(el -> !el.equals(info[0])
Andrea Campanellac3627842017-04-04 18:06:54 +0200226 && !el.equals(info[info.length - 1]))
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700227 .reduce((t, u) -> t + ":" + u)
228 .get();
229 log.debug("ip v6 {}", ip);
230 port = Integer.parseInt(info[info.length - 1]);
231 }
232 }
233 try {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600234 DeviceKey deviceKey = deviceKeyService.getDeviceKey(
Andrea Campanellac3627842017-04-04 18:06:54 +0200235 DeviceKeyId.deviceKeyId(deviceId.toString()));
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600236 NetconfDeviceInfo deviceInfo = null;
237 if (deviceKey.type() == DeviceKey.Type.USERNAME_PASSWORD) {
238 UsernamePassword usernamepasswd = deviceKey.asUsernamePassword();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700239
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600240 deviceInfo = new NetconfDeviceInfo(usernamepasswd.username(),
Andrea Campanellac3627842017-04-04 18:06:54 +0200241 usernamepasswd.password(),
242 IpAddress.valueOf(ip),
243 port);
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600244
245 } else if (deviceKey.type() == DeviceKey.Type.SSL_KEY) {
246 String username = deviceKey.annotations().value(AnnotationKeys.USERNAME);
247 String password = deviceKey.annotations().value(AnnotationKeys.PASSWORD);
Andrea Campanellac3627842017-04-04 18:06:54 +0200248 String sshkey = deviceKey.annotations().value(AnnotationKeys.SSHKEY);
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600249
250 deviceInfo = new NetconfDeviceInfo(username,
Andrea Campanellac3627842017-04-04 18:06:54 +0200251 password,
252 IpAddress.valueOf(ip),
253 port,
254 sshkey);
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600255 } else {
256 log.error("Unknown device key for device {}", deviceId);
257 }
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700258 NetconfDevice netconfDevicedevice = createDevice(deviceInfo);
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700259 netconfDevicedevice.getSession().addDeviceOutputListener(downListener);
260 return netconfDevicedevice;
261 } catch (NullPointerException e) {
262 throw new NetconfException("No Device Key for device " + deviceId, e);
263 }
andreaeb70a942015-10-16 21:34:46 -0700264 }
265 }
266
267 @Override
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700268 public void disconnectDevice(DeviceId deviceId, boolean remove) {
269 if (!netconfDeviceMap.containsKey(deviceId)) {
270 log.warn("Device {} is not present", deviceId);
andreaeb70a942015-10-16 21:34:46 -0700271 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700272 stopDevice(deviceId, remove);
andreaeb70a942015-10-16 21:34:46 -0700273 }
274 }
275
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700276 private void stopDevice(DeviceId deviceId, boolean remove) {
277 netconfDeviceMap.get(deviceId).disconnect();
278 netconfDeviceMap.remove(deviceId);
279 if (remove) {
Andrea Campanella86294db2016-03-07 11:42:49 -0800280 for (NetconfDeviceListener l : netconfDeviceListeners) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700281 l.deviceRemoved(deviceId);
282 }
283 }
284 }
285
286 @Override
287 public void removeDevice(DeviceId deviceId) {
288 if (!netconfDeviceMap.containsKey(deviceId)) {
289 log.warn("Device {} is not present", deviceId);
290 for (NetconfDeviceListener l : netconfDeviceListeners) {
291 l.deviceRemoved(deviceId);
292 }
293 } else {
294 netconfDeviceMap.remove(deviceId);
295 for (NetconfDeviceListener l : netconfDeviceListeners) {
296 l.deviceRemoved(deviceId);
Andrea Campanella86294db2016-03-07 11:42:49 -0800297 }
298 }
299 }
300
Andrea Campanella101417d2015-12-11 17:58:07 -0800301 private NetconfDevice createDevice(NetconfDeviceInfo deviceInfo) throws NetconfException {
Andrea Campanella950310c2016-02-12 18:14:38 -0800302 NetconfDevice netconfDevice = deviceFactory.createNetconfDevice(deviceInfo);
Andrea Campanella087ceb92015-12-07 09:58:34 -0800303 netconfDeviceMap.put(deviceInfo.getDeviceId(), netconfDevice);
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700304 for (NetconfDeviceListener l : netconfDeviceListeners) {
305 l.deviceAdded(deviceInfo.getDeviceId());
306 }
andreaeb70a942015-10-16 21:34:46 -0700307 return netconfDevice;
308 }
309
andreaeb70a942015-10-16 21:34:46 -0700310
311 @Override
312 public Map<DeviceId, NetconfDevice> getDevicesMap() {
313 return netconfDeviceMap;
314 }
Andrea Campanella950310c2016-02-12 18:14:38 -0800315
Andrea Campanella86294db2016-03-07 11:42:49 -0800316 @Override
317 public Set<DeviceId> getNetconfDevices() {
318 return netconfDeviceMap.keySet();
319 }
320
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700321
Andrea Campanella950310c2016-02-12 18:14:38 -0800322 //Device factory for the specific NetconfDeviceImpl
323 private class DefaultNetconfDeviceFactory implements NetconfDeviceFactory {
324
325 @Override
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700326 public NetconfDevice createNetconfDevice(NetconfDeviceInfo netconfDeviceInfo)
327 throws NetconfException {
328 if (sshLibrary.equals(ETHZ_SSH2)) {
329 return new DefaultNetconfDevice(netconfDeviceInfo,
330 new NetconfSessionImpl.SshNetconfSessionFactory());
331 }
Andrea Campanella950310c2016-02-12 18:14:38 -0800332 return new DefaultNetconfDevice(netconfDeviceInfo);
333 }
334 }
Andrea Campanella2464dc32016-02-17 17:54:53 -0800335
Yuta HIGUCHIf7089102017-05-03 10:31:46 -0700336 //Listener for closed session with devices, gets triggered when devices goes down
337 // or sends the end pattern ]]>]]>
Andrea Campanella2464dc32016-02-17 17:54:53 -0800338 private class DeviceDownEventListener implements NetconfDeviceOutputEventListener {
339
340 @Override
341 public void event(NetconfDeviceOutputEvent event) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200342 DeviceId did = event.getDeviceInfo().getDeviceId();
Andrea Campanella2464dc32016-02-17 17:54:53 -0800343 if (event.type().equals(NetconfDeviceOutputEvent.Type.DEVICE_UNREGISTERED)) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200344 removeDevice(did);
345 } else if (event.type().equals(NetconfDeviceOutputEvent.Type.SESSION_CLOSED)) {
346 log.info("Trying to reestablish connection with device {}", did);
347 executor.execute(() -> {
348 try {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700349 NetconfDevice device = netconfDeviceMap.get(did);
350 if (device != null) {
351 device.getSession().checkAndReestablish();
352 log.info("Connection with device {} was reestablished", did);
353 } else {
354 log.warn("The device {} is not in the system", did);
355 }
356
Andrea Campanellac3627842017-04-04 18:06:54 +0200357 } catch (NetconfException e) {
358 log.error("The SSH connection with device {} couldn't be " +
359 "reestablished due to {}. " +
360 "Marking the device as unreachable", e.getMessage());
361 log.debug("Complete exception: ", e);
362 removeDevice(did);
363 }
364 });
Andrea Campanella2464dc32016-02-17 17:54:53 -0800365 }
366 }
367
368 @Override
369 public boolean isRelevant(NetconfDeviceOutputEvent event) {
370 return getDevicesMap().containsKey(event.getDeviceInfo().getDeviceId());
371 }
372 }
andreaeb70a942015-10-16 21:34:46 -0700373}