blob: cb0a71237beb5de64cba007dc4f35d43a9149f16 [file] [log] [blame]
andreaeb70a942015-10-16 21:34:46 -07001/*
gyewan.an3c99ee72019-02-18 15:53:55 +09002 * Copyright 2019-present Open Networking Foundation
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
gyewan.an91d7e7e2019-01-17 15:12:48 +090019
David K. Bainbridge56e90232018-12-18 23:25:08 -080020import org.apache.commons.lang3.tuple.Triple;
21
gyewan.an91d7e7e2019-01-17 15:12:48 +090022import com.google.common.annotations.Beta;
23import com.google.common.collect.Lists;
24
gyewan.an3c99ee72019-02-18 15:53:55 +090025import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.ControllerNode;
27import org.onosproject.cluster.NodeId;
gyewan.an91d7e7e2019-01-17 15:12:48 +090028import org.osgi.service.component.annotations.Activate;
29import org.osgi.service.component.annotations.Component;
30import org.osgi.service.component.annotations.Deactivate;
31import org.osgi.service.component.annotations.Modified;
32import org.osgi.service.component.annotations.Reference;
33import org.osgi.service.component.annotations.ReferenceCardinality;
34
Holger Schulz092cbbf2017-08-31 17:52:30 +020035import org.bouncycastle.jce.provider.BouncyCastleProvider;
andreaeb70a942015-10-16 21:34:46 -070036import org.onlab.packet.IpAddress;
gyewan.an91d7e7e2019-01-17 15:12:48 +090037import org.onlab.util.KryoNamespace;
Andreas Papazois4752cfa2016-04-25 14:52:12 +030038import org.onosproject.cfg.ComponentConfigService;
Andrea Campanellaa2a6c3c2018-12-11 12:56:38 +010039import org.onosproject.mastership.MastershipService;
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -060040import org.onosproject.net.AnnotationKeys;
Andrea Campanella7e6200a2016-03-21 09:48:40 -070041import org.onosproject.net.Device;
andreaeb70a942015-10-16 21:34:46 -070042import org.onosproject.net.DeviceId;
gyewan.an91d7e7e2019-01-17 15:12:48 +090043import org.onosproject.net.MastershipRole;
Sean Condon54d82432017-07-26 22:27:25 +010044import org.onosproject.net.config.NetworkConfigRegistry;
Andrea Campanella7e6200a2016-03-21 09:48:40 -070045import org.onosproject.net.device.DeviceService;
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -060046import org.onosproject.net.key.DeviceKey;
Andrea Campanella7e6200a2016-03-21 09:48:40 -070047import org.onosproject.net.key.DeviceKeyId;
48import org.onosproject.net.key.DeviceKeyService;
49import org.onosproject.net.key.UsernamePassword;
andreaeb70a942015-10-16 21:34:46 -070050import org.onosproject.netconf.NetconfController;
51import org.onosproject.netconf.NetconfDevice;
Andrea Campanella950310c2016-02-12 18:14:38 -080052import org.onosproject.netconf.NetconfDeviceFactory;
andreaeb70a942015-10-16 21:34:46 -070053import org.onosproject.netconf.NetconfDeviceInfo;
54import org.onosproject.netconf.NetconfDeviceListener;
Andrea Campanella2464dc32016-02-17 17:54:53 -080055import org.onosproject.netconf.NetconfDeviceOutputEvent;
56import org.onosproject.netconf.NetconfDeviceOutputEventListener;
Andrea Campanella101417d2015-12-11 17:58:07 -080057import org.onosproject.netconf.NetconfException;
gyewan.an91d7e7e2019-01-17 15:12:48 +090058import org.onosproject.netconf.NetconfProxyMessage;
59import org.onosproject.netconf.NetconfProxyMessageHandler;
60import org.onosproject.netconf.NetconfSession;
Sean Condon54d82432017-07-26 22:27:25 +010061import org.onosproject.netconf.config.NetconfDeviceConfig;
62import org.onosproject.netconf.config.NetconfSshClientLib;
gyewan.an91d7e7e2019-01-17 15:12:48 +090063import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
64import org.onosproject.store.cluster.messaging.MessageSubject;
65import org.onosproject.store.serializers.KryoNamespaces;
66import org.onosproject.store.service.Serializer;
andreaeb70a942015-10-16 21:34:46 -070067import org.osgi.service.component.ComponentContext;
gyewan.an91d7e7e2019-01-17 15:12:48 +090068
andreaeb70a942015-10-16 21:34:46 -070069import org.slf4j.Logger;
70import org.slf4j.LoggerFactory;
71
Holger Schulz092cbbf2017-08-31 17:52:30 +020072import java.security.Security;
gyewan.an91d7e7e2019-01-17 15:12:48 +090073
74import java.util.ArrayList;
Andreas Papazois4752cfa2016-04-25 14:52:12 +030075import java.util.Dictionary;
gyewan.an3c99ee72019-02-18 15:53:55 +090076import java.util.LinkedHashSet;
andreaeb70a942015-10-16 21:34:46 -070077import java.util.Map;
David K. Bainbridge56e90232018-12-18 23:25:08 -080078import java.util.Optional;
andreaeb70a942015-10-16 21:34:46 -070079import java.util.Set;
gyewan.an91d7e7e2019-01-17 15:12:48 +090080import java.util.concurrent.CompletableFuture;
andreaeb70a942015-10-16 21:34:46 -070081import java.util.concurrent.ConcurrentHashMap;
82import java.util.concurrent.CopyOnWriteArraySet;
gyewan.an3c99ee72019-02-18 15:53:55 +090083import java.util.concurrent.CountDownLatch;
gyewan.an91d7e7e2019-01-17 15:12:48 +090084import java.util.concurrent.ExecutionException;
Andrea Campanellac3627842017-04-04 18:06:54 +020085import java.util.concurrent.ExecutorService;
86import java.util.concurrent.Executors;
David K. Bainbridge9b582b02019-02-01 16:04:05 -080087import java.util.concurrent.locks.Lock;
88import java.util.concurrent.locks.ReentrantLock;
gyewan.an91d7e7e2019-01-17 15:12:48 +090089import java.util.concurrent.TimeUnit;
90import java.util.concurrent.TimeoutException;
andreaeb70a942015-10-16 21:34:46 -070091
Andreas Papazois4752cfa2016-04-25 14:52:12 +030092import static org.onlab.util.Tools.get;
Sean Condon54d82432017-07-26 22:27:25 +010093import static org.onlab.util.Tools.getIntegerProperty;
Andrea Campanellac3627842017-04-04 18:06:54 +020094import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -070095import static org.onosproject.netconf.ctl.impl.OsgiPropertyConstants.*;
David K. Bainbridge56e90232018-12-18 23:25:08 -080096import static org.onosproject.netconf.NetconfDeviceInfo.extractIpPortPath;
Andreas Papazois4752cfa2016-04-25 14:52:12 +030097
andreaeb70a942015-10-16 21:34:46 -070098/**
99 * The implementation of NetconfController.
100 */
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700101@Component(immediate = true, service = NetconfController.class,
102 property = {
103 NETCONF_CONNECT_TIMEOUT + ":Integer=" + NETCONF_CONNECT_TIMEOUT_DEFAULT,
104 NETCONF_REPLY_TIMEOUT + ":Integer=" + NETCONF_REPLY_TIMEOUT_DEFAULT,
105 NETCONF_IDLE_TIMEOUT + ":Integer=" + NETCONF_IDLE_TIMEOUT_DEFAULT,
106 SSH_LIBRARY + "=" + SSH_LIBRARY_DEFAULT,
107 })
andreaeb70a942015-10-16 21:34:46 -0700108public class NetconfControllerImpl implements NetconfController {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700109
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700110 /** Time (in seconds) to wait for a NETCONF connect. */
111 protected static int netconfConnectTimeout = NETCONF_CONNECT_TIMEOUT_DEFAULT;
Sean Condon334ad692016-12-13 17:56:56 +0000112
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700113 /** Time (in seconds) waiting for a NetConf reply. */
114 protected static int netconfReplyTimeout = NETCONF_REPLY_TIMEOUT_DEFAULT;
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300115
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700116 /** Time (in seconds) SSH session will close if no traffic seen. */
117 protected static int netconfIdleTimeout = NETCONF_IDLE_TIMEOUT_DEFAULT;
Sean Condon7347de92017-07-21 12:17:25 +0100118
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700119 /** SSH client library to use. */
120 protected static String sshLibrary = SSH_LIBRARY_DEFAULT;
121
122 protected NetconfSshClientLib sshClientLib = NetconfSshClientLib.APACHE_MINA;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700123
gyewan.an91d7e7e2019-01-17 15:12:48 +0900124 private static final MessageSubject SEND_REQUEST_SUBJECT_STRING =
125 new MessageSubject("netconf-session-master-send-message-string");
126
gyewan.an3c99ee72019-02-18 15:53:55 +0900127 private static final MessageSubject SEND_REPLY_SUBJECT_STRING =
128 new MessageSubject("netconf-session-master-send-reply-message-string");
129
gyewan.an91d7e7e2019-01-17 15:12:48 +0900130 private static final MessageSubject SEND_REQUEST_SUBJECT_SET_STRING =
131 new MessageSubject("netconf-session-master-send-message-set-string");
132
gyewan.an3c99ee72019-02-18 15:53:55 +0900133 private static final MessageSubject SEND_REPLY_SUBJECT_SET_STRING =
134 new MessageSubject("netconf-session-master-send-reply-message-set-string");
135
136
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700137 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300138 protected ComponentConfigService cfgService;
139
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700140 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700141 protected DeviceService deviceService;
142
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700143 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700144 protected DeviceKeyService deviceKeyService;
andreaeb70a942015-10-16 21:34:46 -0700145
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700146 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Sean Condon54d82432017-07-26 22:27:25 +0100147 protected NetworkConfigRegistry netCfgService;
148
Andrea Campanellaa2a6c3c2018-12-11 12:56:38 +0100149 @Reference(cardinality = ReferenceCardinality.MANDATORY)
150 protected MastershipService mastershipService;
151
gyewan.an91d7e7e2019-01-17 15:12:48 +0900152 @Reference(cardinality = ReferenceCardinality.MANDATORY)
153 protected ClusterCommunicationService clusterCommunicator;
154
gyewan.an3c99ee72019-02-18 15:53:55 +0900155 @Reference(cardinality = ReferenceCardinality.MANDATORY)
156 protected ClusterService clusterService;
157
andreaeb70a942015-10-16 21:34:46 -0700158 public static final Logger log = LoggerFactory
159 .getLogger(NetconfControllerImpl.class);
160
Andrea Campanella8b1cb672016-01-25 13:58:58 -0800161 private Map<DeviceId, NetconfDevice> netconfDeviceMap = new ConcurrentHashMap<>();
andreaeb70a942015-10-16 21:34:46 -0700162
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800163 private Map<DeviceId, Lock> netconfCreateMutex = new ConcurrentHashMap<>();
164
Andrea Campanella2464dc32016-02-17 17:54:53 -0800165 private final NetconfDeviceOutputEventListener downListener = new DeviceDownEventListener();
166
andreaeb70a942015-10-16 21:34:46 -0700167 protected Set<NetconfDeviceListener> netconfDeviceListeners = new CopyOnWriteArraySet<>();
gyewan.an91d7e7e2019-01-17 15:12:48 +0900168 protected NetconfDeviceFactory deviceFactory = new DefaultNetconfDeviceFactory();
169
170 protected NetconfProxyMessageHandler netconfProxyMessageHandler = new NetconfProxyMessageHandlerImpl();
171
andreaeb70a942015-10-16 21:34:46 -0700172
Andrea Campanellac3627842017-04-04 18:06:54 +0200173 protected final ExecutorService executor =
174 Executors.newCachedThreadPool(groupedThreads("onos/netconfdevicecontroller",
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700175 "connection-reopen-%d", log));
Andrea Campanellac3627842017-04-04 18:06:54 +0200176
gyewan.an3c99ee72019-02-18 15:53:55 +0900177 private final ExecutorService remoteRequestExecutor =
178 Executors.newCachedThreadPool();
179
180 protected NodeId localNodeId;
181
182 private CountDownLatch countDownLatch;
183
184 private ArrayList<String> replyArguments = new ArrayList<>();
185
gyewan.an91d7e7e2019-01-17 15:12:48 +0900186 public static final Serializer SERIALIZER = Serializer.using(
187 KryoNamespace.newBuilder()
188 .register(KryoNamespaces.API)
189 .register(NetconfProxyMessage.class)
190 .register(NetconfProxyMessage.SubjectType.class)
191 .register(DefaultNetconfProxyMessage.class)
192 .register(String.class)
193 .build("NetconfProxySession"));
194
andreaeb70a942015-10-16 21:34:46 -0700195 @Activate
196 public void activate(ComponentContext context) {
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300197 cfgService.registerProperties(getClass());
198 modified(context);
Yuta HIGUCHIe9761742017-09-10 15:10:19 -0700199 Security.addProvider(new BouncyCastleProvider());
gyewan.an3c99ee72019-02-18 15:53:55 +0900200 clusterCommunicator.<NetconfProxyMessage>addSubscriber(
gyewan.an91d7e7e2019-01-17 15:12:48 +0900201 SEND_REQUEST_SUBJECT_STRING,
202 SERIALIZER::decode,
203 this::handleProxyMessage,
gyewan.an3c99ee72019-02-18 15:53:55 +0900204 remoteRequestExecutor);
205 clusterCommunicator.<NetconfProxyMessage>addSubscriber(
gyewan.an91d7e7e2019-01-17 15:12:48 +0900206 SEND_REQUEST_SUBJECT_SET_STRING,
207 SERIALIZER::decode,
208 this::handleProxyMessage,
gyewan.an3c99ee72019-02-18 15:53:55 +0900209 remoteRequestExecutor);
210 clusterCommunicator.<NetconfProxyMessage>addSubscriber(
211 SEND_REPLY_SUBJECT_STRING,
212 SERIALIZER::decode,
213 this::handleProxyReplyMessage,
214 remoteRequestExecutor);
215 clusterCommunicator.<NetconfProxyMessage>addSubscriber(
216 SEND_REPLY_SUBJECT_SET_STRING,
217 SERIALIZER::decode,
218 this::handleProxyReplyMessage,
219 remoteRequestExecutor);
220
221 localNodeId = Optional.ofNullable(clusterService.getLocalNode())
222 .map(ControllerNode::id)
223 .orElseGet(() -> new NodeId("nullNodeId"));
andreaeb70a942015-10-16 21:34:46 -0700224 log.info("Started");
225 }
226
227 @Deactivate
228 public void deactivate() {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700229 netconfDeviceMap.values().forEach(device -> {
gyewan.an91d7e7e2019-01-17 15:12:48 +0900230 if (device.isMasterSession()) {
231 try {
232 device.getSession().removeDeviceOutputListener(downListener);
233 } catch (NetconfException e) {
234 log.error("removeDeviceOutputListener Failed {}", e.getMessage());
235 }
236 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700237 device.disconnect();
238 });
gyewan.an91d7e7e2019-01-17 15:12:48 +0900239 clusterCommunicator.removeSubscriber(SEND_REQUEST_SUBJECT_STRING);
240 clusterCommunicator.removeSubscriber(SEND_REQUEST_SUBJECT_SET_STRING);
gyewan.an3c99ee72019-02-18 15:53:55 +0900241 clusterCommunicator.removeSubscriber(SEND_REPLY_SUBJECT_STRING);
242 clusterCommunicator.removeSubscriber(SEND_REPLY_SUBJECT_SET_STRING);
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300243 cfgService.unregisterProperties(getClass(), false);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700244 netconfDeviceListeners.clear();
andreaeb70a942015-10-16 21:34:46 -0700245 netconfDeviceMap.clear();
Holger Schulz092cbbf2017-08-31 17:52:30 +0200246 Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
andreaeb70a942015-10-16 21:34:46 -0700247 log.info("Stopped");
248 }
249
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300250 @Modified
251 public void modified(ComponentContext context) {
252 if (context == null) {
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700253 netconfReplyTimeout = NETCONF_REPLY_TIMEOUT_DEFAULT;
254 netconfConnectTimeout = NETCONF_CONNECT_TIMEOUT_DEFAULT;
255 netconfIdleTimeout = NETCONF_IDLE_TIMEOUT_DEFAULT;
256 sshLibrary = SSH_LIBRARY_DEFAULT;
257 sshClientLib = NetconfSshClientLib.APACHE_MINA;
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300258 log.info("No component configuration");
259 return;
260 }
261
262 Dictionary<?, ?> properties = context.getProperties();
263
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700264 String newSshLibrary;
Sean Condon334ad692016-12-13 17:56:56 +0000265
Sean Condon54d82432017-07-26 22:27:25 +0100266 int newNetconfReplyTimeout = getIntegerProperty(
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700267 properties, NETCONF_REPLY_TIMEOUT, netconfReplyTimeout);
Sean Condon54d82432017-07-26 22:27:25 +0100268 int newNetconfConnectTimeout = getIntegerProperty(
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700269 properties, NETCONF_CONNECT_TIMEOUT, netconfConnectTimeout);
Sean Condon54d82432017-07-26 22:27:25 +0100270 int newNetconfIdleTimeout = getIntegerProperty(
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700271 properties, NETCONF_IDLE_TIMEOUT, netconfIdleTimeout);
Sean Condon334ad692016-12-13 17:56:56 +0000272
Sean Condon54d82432017-07-26 22:27:25 +0100273 newSshLibrary = get(properties, SSH_LIBRARY);
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300274
Sean Condon334ad692016-12-13 17:56:56 +0000275 if (newNetconfConnectTimeout < 0) {
276 log.warn("netconfConnectTimeout is invalid - less than 0");
277 return;
278 } else if (newNetconfReplyTimeout <= 0) {
279 log.warn("netconfReplyTimeout is invalid - 0 or less.");
280 return;
Sean Condon7347de92017-07-21 12:17:25 +0100281 } else if (newNetconfIdleTimeout <= 0) {
282 log.warn("netconfIdleTimeout is invalid - 0 or less.");
283 return;
Sean Condon334ad692016-12-13 17:56:56 +0000284 }
285
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300286 netconfReplyTimeout = newNetconfReplyTimeout;
Sean Condon334ad692016-12-13 17:56:56 +0000287 netconfConnectTimeout = newNetconfConnectTimeout;
Sean Condon7347de92017-07-21 12:17:25 +0100288 netconfIdleTimeout = newNetconfIdleTimeout;
Sean Condon54d82432017-07-26 22:27:25 +0100289 if (newSshLibrary != null) {
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700290 sshLibrary = newSshLibrary;
291 sshClientLib = NetconfSshClientLib.getEnum(newSshLibrary);
Sean Condon54d82432017-07-26 22:27:25 +0100292 }
293 log.info("Settings: {} = {}, {} = {}, {} = {}, {} = {}",
Thomas Vachuska00b5d4f2018-10-30 15:13:20 -0700294 NETCONF_REPLY_TIMEOUT, netconfReplyTimeout,
295 NETCONF_CONNECT_TIMEOUT, netconfConnectTimeout,
296 NETCONF_IDLE_TIMEOUT, netconfIdleTimeout,
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700297 SSH_LIBRARY, sshLibrary);
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300298 }
299
andreaeb70a942015-10-16 21:34:46 -0700300 @Override
301 public void addDeviceListener(NetconfDeviceListener listener) {
302 if (!netconfDeviceListeners.contains(listener)) {
303 netconfDeviceListeners.add(listener);
304 }
305 }
306
307 @Override
308 public void removeDeviceListener(NetconfDeviceListener listener) {
309 netconfDeviceListeners.remove(listener);
310 }
311
312 @Override
313 public NetconfDevice getNetconfDevice(DeviceId deviceInfo) {
314 return netconfDeviceMap.get(deviceInfo);
315 }
316
317 @Override
David K. Bainbridge56e90232018-12-18 23:25:08 -0800318 public NetconfDevice getNetconfDevice(IpAddress ip, int port, String path) {
319 return getNetconfDevice(DeviceId.deviceId(
320 String.format("netconf:%s:%d%s",
321 ip.toString(), port, (path != null && !path.isEmpty() ? "/" + path : ""))));
322 }
323
324 @Override
andreaeb70a942015-10-16 21:34:46 -0700325 public NetconfDevice getNetconfDevice(IpAddress ip, int port) {
David K. Bainbridge56e90232018-12-18 23:25:08 -0800326 return getNetconfDevice(ip, port, null);
andreaeb70a942015-10-16 21:34:46 -0700327 }
328
329 @Override
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700330 public NetconfDevice connectDevice(DeviceId deviceId) throws NetconfException {
gyewan.an91d7e7e2019-01-17 15:12:48 +0900331 return connectDevice(deviceId, true);
332 }
333
334 @Override
335 public NetconfDevice connectDevice(DeviceId deviceId, boolean isMaster) throws NetconfException {
Sean Condon54d82432017-07-26 22:27:25 +0100336 NetconfDeviceConfig netCfg = netCfgService.getConfig(
337 deviceId, NetconfDeviceConfig.class);
338 NetconfDeviceInfo deviceInfo = null;
339
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800340 /*
341 * A bit of an ugly race condition can be found here. It is possible
342 * that this method is called to create a connection to device A and
343 * while that device is in the process of being created another call
344 * to this method for A will be invoked. Since the first call to
345 * create A has not been completed device A is not in the the
346 * netconfDeviceMap yet.
347 *
348 * To prevent this situation a mutex is introduced so that the first
349 * call will be allowed to complete before the second is processed.
350 * The mutex is based on the device ID, so that it should be still
351 * possible to connect to different devices concurrently.
352 */
353 Lock mutex;
354 synchronized (netconfCreateMutex) {
355 mutex = netconfCreateMutex.get(deviceId);
356 if (mutex == null) {
357 mutex = new ReentrantLock();
358 netconfCreateMutex.put(deviceId, mutex);
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700359 }
andreaeb70a942015-10-16 21:34:46 -0700360 }
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800361 mutex.lock();
362 try {
363 if (netconfDeviceMap.containsKey(deviceId)) {
gyewan.an91d7e7e2019-01-17 15:12:48 +0900364 //If not master or already has session: return, otherwise create device again.
365 if (!isMaster || netconfDeviceMap.get(deviceId).isMasterSession()) {
366 log.debug("Device {} is already present", deviceId);
367 return netconfDeviceMap.get(deviceId);
368 }
369 }
370
371 if (netCfg != null) {
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800372 log.debug("Device {} is present in NetworkConfig", deviceId);
Anurag Chadha929cbd02021-03-26 14:43:41 +0530373 deviceInfo = new NetconfDeviceInfo(netCfg, deviceId);
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800374 } else {
375 log.debug("Creating NETCONF device {}", deviceId);
gyewan.an91d7e7e2019-01-17 15:12:48 +0900376 deviceInfo = createDeviceInfo(deviceId);
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800377 }
gyewan.an91d7e7e2019-01-17 15:12:48 +0900378 NetconfDevice netconfDevice = createDevice(deviceInfo, isMaster);
379 if (isMaster) {
380 netconfDevice.getSession().addDeviceOutputListener(downListener);
381 }
382 return netconfDevice;
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800383 } finally {
Andrea Campanellac535b672019-02-25 16:25:35 +0100384
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800385 mutex.unlock();
386 }
andreaeb70a942015-10-16 21:34:46 -0700387 }
388
gyewan.an3c99ee72019-02-18 15:53:55 +0900389 @Override
390 public NodeId getLocalNodeId() {
391 return localNodeId;
392 }
393
gyewan.an91d7e7e2019-01-17 15:12:48 +0900394 private NetconfDeviceInfo createDeviceInfo(DeviceId deviceId) throws NetconfException {
395 Device device = deviceService.getDevice(deviceId);
396 String ip, path = null;
397 int port;
398 if (device != null) {
399 ip = device.annotations().value("ipaddress");
400 port = Integer.parseInt(device.annotations().value("port"));
401 } else {
402 Triple<String, Integer, Optional<String>> info = extractIpPortPath(deviceId);
403 ip = info.getLeft();
404 port = info.getMiddle();
405 path = (info.getRight().isPresent() ? info.getRight().get() : null);
406 }
407 try {
408 DeviceKey deviceKey = deviceKeyService.getDeviceKey(
409 DeviceKeyId.deviceKeyId(deviceId.toString()));
410 if (deviceKey.type() == DeviceKey.Type.USERNAME_PASSWORD) {
411 UsernamePassword usernamepasswd = deviceKey.asUsernamePassword();
412
413 return new NetconfDeviceInfo(usernamepasswd.username(),
414 usernamepasswd.password(),
415 IpAddress.valueOf(ip),
416 port,
417 path);
418
419 } else if (deviceKey.type() == DeviceKey.Type.SSL_KEY) {
420 String username = deviceKey.annotations().value(AnnotationKeys.USERNAME);
421 String password = deviceKey.annotations().value(AnnotationKeys.PASSWORD);
422 String sshkey = deviceKey.annotations().value(AnnotationKeys.SSHKEY);
423
424 return new NetconfDeviceInfo(username,
425 password,
426 IpAddress.valueOf(ip),
427 port,
428 path,
429 sshkey);
430 } else {
431 log.error("Unknown device key for device {}", deviceId);
432 throw new NetconfException("Unknown device key for device " + deviceId);
433 }
434 } catch (NullPointerException e) {
435 log.error("No Device Key for device {}, {}", deviceId, e);
436 throw new NetconfException("No Device Key for device " + deviceId, e);
437 }
438 }
439
andreaeb70a942015-10-16 21:34:46 -0700440 @Override
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700441 public void disconnectDevice(DeviceId deviceId, boolean remove) {
442 if (!netconfDeviceMap.containsKey(deviceId)) {
Andrea Campanellaa2a6c3c2018-12-11 12:56:38 +0100443 log.debug("Device {} is not present", deviceId);
andreaeb70a942015-10-16 21:34:46 -0700444 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700445 stopDevice(deviceId, remove);
andreaeb70a942015-10-16 21:34:46 -0700446 }
447 }
448
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700449 private void stopDevice(DeviceId deviceId, boolean remove) {
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800450 Lock mutex;
451 synchronized (netconfCreateMutex) {
452 mutex = netconfCreateMutex.remove(deviceId);
453 }
454 NetconfDevice nc;
455 if (mutex == null) {
456 log.warn("Unexpected stoping a device that has no lock");
457 nc = netconfDeviceMap.remove(deviceId);
458 } else {
459 mutex.lock();
460 try {
461 nc = netconfDeviceMap.remove(deviceId);
462 } finally {
463 mutex.unlock();
464 }
465 }
David K. Bainbridge1070de92019-01-29 22:23:11 -0800466 if (nc != null) {
467 nc.disconnect();
468 }
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700469 if (remove) {
Andrea Campanella86294db2016-03-07 11:42:49 -0800470 for (NetconfDeviceListener l : netconfDeviceListeners) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700471 l.deviceRemoved(deviceId);
472 }
473 }
474 }
475
476 @Override
477 public void removeDevice(DeviceId deviceId) {
478 if (!netconfDeviceMap.containsKey(deviceId)) {
479 log.warn("Device {} is not present", deviceId);
480 for (NetconfDeviceListener l : netconfDeviceListeners) {
481 l.deviceRemoved(deviceId);
482 }
483 } else {
zhongguo zhao78eab372018-08-27 16:22:39 +0800484 stopDevice(deviceId, true);
Andrea Campanella86294db2016-03-07 11:42:49 -0800485 }
486 }
487
Andrea Campanella101417d2015-12-11 17:58:07 -0800488 private NetconfDevice createDevice(NetconfDeviceInfo deviceInfo) throws NetconfException {
gyewan.an91d7e7e2019-01-17 15:12:48 +0900489 return createDevice(deviceInfo, true);
490 }
491
492 private NetconfDevice createDevice(NetconfDeviceInfo deviceInfo,
493 boolean isMaster) throws NetconfException {
494 NetconfDevice netconfDevice = deviceFactory.createNetconfDevice(deviceInfo, isMaster);
Andrea Campanella087ceb92015-12-07 09:58:34 -0800495 netconfDeviceMap.put(deviceInfo.getDeviceId(), netconfDevice);
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700496 for (NetconfDeviceListener l : netconfDeviceListeners) {
497 l.deviceAdded(deviceInfo.getDeviceId());
498 }
andreaeb70a942015-10-16 21:34:46 -0700499 return netconfDevice;
500 }
501
andreaeb70a942015-10-16 21:34:46 -0700502
503 @Override
504 public Map<DeviceId, NetconfDevice> getDevicesMap() {
505 return netconfDeviceMap;
506 }
Andrea Campanella950310c2016-02-12 18:14:38 -0800507
Andrea Campanella86294db2016-03-07 11:42:49 -0800508 @Override
509 public Set<DeviceId> getNetconfDevices() {
510 return netconfDeviceMap.keySet();
511 }
512
gyewan.an3c99ee72019-02-18 15:53:55 +0900513 private void unicastRpcToMaster(NetconfProxyMessage proxyMessage, NodeId receiverId) {
514 MessageSubject messageSubject;
515
516 switch (proxyMessage.subjectType()) {
517 case GET_DEVICE_CAPABILITIES_SET:
518 messageSubject = SEND_REQUEST_SUBJECT_SET_STRING;
519 break;
520 default:
521 messageSubject = SEND_REQUEST_SUBJECT_STRING;
522 break;
523 }
524
525 clusterCommunicator
526 .unicast(proxyMessage,
527 messageSubject,
528 SERIALIZER::encode,
529 receiverId);
530 }
531
532 private void unicastReplyToSender(NetconfProxyMessage proxyMessage, NodeId receiverId) {
533 MessageSubject messageSubject;
534
535 switch (proxyMessage.subjectType()) {
536 case GET_DEVICE_CAPABILITIES_SET:
537 messageSubject = SEND_REPLY_SUBJECT_SET_STRING;
538 break;
539 default:
540 messageSubject = SEND_REPLY_SUBJECT_STRING;
541 break;
542 }
543
544 clusterCommunicator
545 .unicast(proxyMessage,
546 messageSubject,
547 SERIALIZER::encode,
548 receiverId);
549 }
550
gyewan.an91d7e7e2019-01-17 15:12:48 +0900551 @Override
552 public <T> CompletableFuture<T> executeAtMaster(NetconfProxyMessage proxyMessage) throws NetconfException {
553 DeviceId deviceId = proxyMessage.deviceId();
554 if (deviceService.getRole(deviceId).equals(MastershipRole.MASTER)) {
gyewan.an3c99ee72019-02-18 15:53:55 +0900555 return handleProxyMessage(proxyMessage);
gyewan.an91d7e7e2019-01-17 15:12:48 +0900556 } else {
gyewan.an3c99ee72019-02-18 15:53:55 +0900557 return relayMessageToMaster(proxyMessage);
558 }
559 }
560
Anurag Chadha135cb7a2020-12-04 19:48:56 +0530561 @Override
562 public <T> boolean pingDevice(DeviceId deviceId) {
563 NetconfProxyMessage proxyMessage = new DefaultNetconfProxyMessage(
564 NetconfProxyMessage.SubjectType.GET_DEVICE_CAPABILITIES_SET, deviceId, null, localNodeId);
565 CompletableFuture<T> reply;
566 if (deviceService.getRole(deviceId).equals(MastershipRole.MASTER)) {
567 reply = handleProxyMessage(proxyMessage);
568 } else {
569 reply = relayMessageToMaster(proxyMessage);
570 }
571 try {
572 T deviceCapabilities = reply.get();
573 log.debug("Get device capabilities from device : {} -> {}", deviceId, deviceCapabilities);
574 } catch (InterruptedException | ExecutionException e) {
575 log.error("Error while getting device capabilities for device : {}", deviceId);
576 log.error("Error details : ", e);
577 return false;
578 }
579 return true;
580 }
581
gyewan.an3c99ee72019-02-18 15:53:55 +0900582 public <T> CompletableFuture<T> relayMessageToMaster(NetconfProxyMessage proxyMessage) {
583 DeviceId deviceId = proxyMessage.deviceId();
584
585 countDownLatch = new CountDownLatch(1);
586 unicastRpcToMaster(proxyMessage, mastershipService.getMasterFor(deviceId));
587
588 try {
589 countDownLatch.await(netconfReplyTimeout, TimeUnit.SECONDS);
590
gyewan.an91d7e7e2019-01-17 15:12:48 +0900591 switch (proxyMessage.subjectType()) {
592 case GET_DEVICE_CAPABILITIES_SET:
gyewan.an3c99ee72019-02-18 15:53:55 +0900593 Set<String> forReturnValue = new LinkedHashSet<>(replyArguments);
594 return CompletableFuture.completedFuture((T) forReturnValue);
gyewan.an91d7e7e2019-01-17 15:12:48 +0900595 default:
Anurag Chadha01ab40a2020-07-13 13:38:08 +0530596 String returnValue = null;
597 if (!replyArguments.isEmpty()) {
598 returnValue = Optional.ofNullable(replyArguments.get(0)).orElse(null);
599 }
gyewan.an3c99ee72019-02-18 15:53:55 +0900600 return CompletableFuture.completedFuture((T) returnValue);
gyewan.an91d7e7e2019-01-17 15:12:48 +0900601 }
gyewan.an3c99ee72019-02-18 15:53:55 +0900602 } catch (InterruptedException e) {
603 log.error("InterruptedOccured while awaiting because of {}", e);
604 CompletableFuture<T> errorFuture = new CompletableFuture<>();
605 errorFuture.completeExceptionally(e);
606 return errorFuture;
607 } catch (Exception e) {
608 log.error("Exception occured because of {}", e);
609 CompletableFuture<T> errorFuture = new CompletableFuture<>();
610 errorFuture.completeExceptionally(e);
611 return errorFuture;
gyewan.an91d7e7e2019-01-17 15:12:48 +0900612 }
613 }
614
615 private <T> CompletableFuture<T> handleProxyMessage(NetconfProxyMessage proxyMessage) {
Anurag Chadha135cb7a2020-12-04 19:48:56 +0530616 countDownLatch = new CountDownLatch(1);
gyewan.an91d7e7e2019-01-17 15:12:48 +0900617 try {
gyewan.an3c99ee72019-02-18 15:53:55 +0900618 switch (proxyMessage.subjectType()) {
619 case GET_DEVICE_CAPABILITIES_SET:
620 return CompletableFuture.completedFuture(
621 (T) netconfProxyMessageHandler.handleIncomingSetMessage(proxyMessage));
622 default:
623 return CompletableFuture.completedFuture(
624 netconfProxyMessageHandler.handleIncomingMessage(proxyMessage));
625 }
gyewan.an91d7e7e2019-01-17 15:12:48 +0900626 } catch (NetconfException e) {
627 CompletableFuture<T> errorFuture = new CompletableFuture<>();
628 errorFuture.completeExceptionally(e);
629 return errorFuture;
630 }
631 }
632
gyewan.an3c99ee72019-02-18 15:53:55 +0900633 private <T> CompletableFuture<T> handleProxyReplyMessage(NetconfProxyMessage replyMessage) {
634 try {
635 switch (replyMessage.subjectType()) {
636 case GET_DEVICE_CAPABILITIES_SET:
637 return CompletableFuture.completedFuture(
638 (T) netconfProxyMessageHandler.handleReplySetMessage(replyMessage));
639 default:
640 return CompletableFuture.completedFuture(
641 netconfProxyMessageHandler.handleReplyMessage(replyMessage));
642
643 }
644 } catch (NetconfException e) {
645 CompletableFuture<T> errorFuture = new CompletableFuture<>();
646 errorFuture.completeExceptionally(e);
647 return errorFuture;
648 }
649 }
650
651
gyewan.an91d7e7e2019-01-17 15:12:48 +0900652 /**
653 * Netconf Proxy Message Handler Implementation class.
654 */
655 private class NetconfProxyMessageHandlerImpl implements NetconfProxyMessageHandler {
656
657 @Override
658 public <T> T handleIncomingMessage(NetconfProxyMessage proxyMessage) throws NetconfException {
659 //TODO: Should throw Netconf Exception in error cases?
660 DeviceId deviceId = proxyMessage.deviceId();
661 NetconfProxyMessage.SubjectType subjectType = proxyMessage.subjectType();
662 NetconfSession secureTransportSession;
gyewan.an3c99ee72019-02-18 15:53:55 +0900663
Anurag Chadha135cb7a2020-12-04 19:48:56 +0530664 if (netconfDeviceMap.get(deviceId) != null && netconfDeviceMap.get(deviceId).isMasterSession()) {
gyewan.an91d7e7e2019-01-17 15:12:48 +0900665 secureTransportSession = netconfDeviceMap.get(deviceId).getSession();
666 } else {
667 throw new NetconfException("Ssh session not present");
668 }
669 T reply = null;
670 ArrayList<String> arguments = Lists.newArrayList(proxyMessage.arguments());
671 try {
672 switch (subjectType) {
673 case RPC:
674 reply = (T) secureTransportSession.rpc(arguments.get(0))
675 .get(netconfReplyTimeout, TimeUnit.SECONDS);
676 break;
677 case REQUEST_SYNC:
678 reply = (T) secureTransportSession.requestSync(arguments.get(0));
679 break;
680 case START_SUBSCRIPTION:
681 secureTransportSession.startSubscription(arguments.get(0));
682 break;
683 case END_SUBSCRIPTION:
684 secureTransportSession.endSubscription();
685 break;
686 case REQUEST:
687 reply = (T) secureTransportSession.request(arguments.get(0))
688 .get(netconfReplyTimeout, TimeUnit.SECONDS);
689 break;
690 case GET_SESSION_ID:
691 reply = (T) secureTransportSession.getSessionId();
692 break;
gyewan.an91d7e7e2019-01-17 15:12:48 +0900693 case GET_DEVICE_CAPABILITIES_SET:
694 reply = (T) secureTransportSession.getDeviceCapabilitiesSet();
695 break;
696 default:
697 log.error("Not yet supported for session method {}", subjectType);
698 }
699 } catch (InterruptedException e) {
700 Thread.currentThread().interrupt();
701 throw new NetconfException(e.getMessage(), e.getCause());
702 } catch (ExecutionException | TimeoutException e) {
703 throw new NetconfException(e.getMessage(), e.getCause());
704 }
705
gyewan.an3c99ee72019-02-18 15:53:55 +0900706 ArrayList<String> returnArgument = new ArrayList<String>();
707 Optional.ofNullable(reply).ifPresent(r -> returnArgument.add((String) r));
708
709 DefaultNetconfProxyMessage replyMessage = new DefaultNetconfProxyMessage(
710 subjectType,
711 deviceId,
712 returnArgument,
713 localNodeId);
714
715 unicastReplyToSender(replyMessage, proxyMessage.senderId());
716
717
gyewan.an91d7e7e2019-01-17 15:12:48 +0900718 return reply;
719 }
gyewan.an3c99ee72019-02-18 15:53:55 +0900720
721 @Override
722 public <T> T handleReplyMessage(NetconfProxyMessage replyMessage) {
723 replyArguments = new ArrayList<>(replyMessage.arguments());
724 countDownLatch.countDown();
725 return (T) Optional.ofNullable(replyArguments.get(0)).orElse(null);
726 }
727
728 @Override
729 public Set<String> handleIncomingSetMessage(NetconfProxyMessage proxyMessage) throws NetconfException {
730 DeviceId deviceId = proxyMessage.deviceId();
731 NetconfProxyMessage.SubjectType subjectType = proxyMessage.subjectType();
732 NetconfSession secureTransportSession;
733
Anurag Chadha135cb7a2020-12-04 19:48:56 +0530734 if (netconfDeviceMap.get(deviceId) != null && netconfDeviceMap.get(deviceId).isMasterSession()) {
gyewan.an3c99ee72019-02-18 15:53:55 +0900735 secureTransportSession = netconfDeviceMap.get(deviceId).getSession();
736 } else {
737 throw new NetconfException("SSH session not present");
738 }
739
740 Set<String> reply = secureTransportSession.getDeviceCapabilitiesSet();
741 ArrayList<String> returnArgument = new ArrayList<String>(reply);
742
743 DefaultNetconfProxyMessage replyMessage = new DefaultNetconfProxyMessage(
744 subjectType,
745 deviceId,
746 returnArgument,
747 localNodeId);
748
749 unicastReplyToSender(replyMessage, proxyMessage.senderId());
750 return reply;
751 }
752
753 @Override
754 public Set<String> handleReplySetMessage(NetconfProxyMessage replyMessage) {
755 replyArguments = new ArrayList<>(replyMessage.arguments());
756 countDownLatch.countDown();
757
758 return new LinkedHashSet<>(replyArguments);
759
760 }
gyewan.an91d7e7e2019-01-17 15:12:48 +0900761 }
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700762
Yuta HIGUCHI2ee4fba2018-06-12 16:21:06 -0700763 /**
764 * Device factory for the specific NetconfDeviceImpl.
765 *
766 * @deprecated in 1.14.0
767 */
768 @Deprecated
Andrea Campanella950310c2016-02-12 18:14:38 -0800769 private class DefaultNetconfDeviceFactory implements NetconfDeviceFactory {
770
771 @Override
gyewan.an91d7e7e2019-01-17 15:12:48 +0900772 public NetconfDevice createNetconfDevice(NetconfDeviceInfo netconfDeviceInfo) throws NetconfException {
773 return createNetconfDevice(netconfDeviceInfo, true);
774 }
775
776 @Beta
777 @Override
778 public NetconfDevice createNetconfDevice(NetconfDeviceInfo netconfDeviceInfo,
779 boolean isMaster)
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700780 throws NetconfException {
gyewan.an91d7e7e2019-01-17 15:12:48 +0900781 if (isMaster) {
782 log.info("Creating NETCONF session to {} with {}",
783 netconfDeviceInfo.getDeviceId(), NetconfSshClientLib.APACHE_MINA);
784 }
785 return new DefaultNetconfDevice(netconfDeviceInfo, isMaster, NetconfControllerImpl.this);
Andrea Campanella950310c2016-02-12 18:14:38 -0800786 }
787 }
Andrea Campanella2464dc32016-02-17 17:54:53 -0800788
Yuta HIGUCHIf7089102017-05-03 10:31:46 -0700789 //Listener for closed session with devices, gets triggered when devices goes down
790 // or sends the end pattern ]]>]]>
Andrea Campanella2464dc32016-02-17 17:54:53 -0800791 private class DeviceDownEventListener implements NetconfDeviceOutputEventListener {
792
793 @Override
794 public void event(NetconfDeviceOutputEvent event) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200795 DeviceId did = event.getDeviceInfo().getDeviceId();
Andrea Campanellaa2a6c3c2018-12-11 12:56:38 +0100796 if (event.type().equals(NetconfDeviceOutputEvent.Type.DEVICE_UNREGISTERED) ||
797 !mastershipService.isLocalMaster(did)) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200798 removeDevice(did);
799 } else if (event.type().equals(NetconfDeviceOutputEvent.Type.SESSION_CLOSED)) {
800 log.info("Trying to reestablish connection with device {}", did);
801 executor.execute(() -> {
802 try {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700803 NetconfDevice device = netconfDeviceMap.get(did);
804 if (device != null) {
805 device.getSession().checkAndReestablish();
806 log.info("Connection with device {} was reestablished", did);
807 } else {
808 log.warn("The device {} is not in the system", did);
809 }
810
Andrea Campanellac3627842017-04-04 18:06:54 +0200811 } catch (NetconfException e) {
812 log.error("The SSH connection with device {} couldn't be " +
Sean Condon54d82432017-07-26 22:27:25 +0100813 "reestablished due to {}. " +
Andrea Campanellac535b672019-02-25 16:25:35 +0100814 "Marking the device as unreachable", did, e.getMessage());
Andrea Campanellac3627842017-04-04 18:06:54 +0200815 log.debug("Complete exception: ", e);
816 removeDevice(did);
817 }
818 });
Andrea Campanella2464dc32016-02-17 17:54:53 -0800819 }
820 }
821
822 @Override
823 public boolean isRelevant(NetconfDeviceOutputEvent event) {
824 return getDevicesMap().containsKey(event.getDeviceInfo().getDeviceId());
825 }
826 }
andreaeb70a942015-10-16 21:34:46 -0700827}