blob: 90f6d146121d8bc56f3294aee32d27d842951402 [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);
373 deviceInfo = new NetconfDeviceInfo(netCfg);
374 } 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
561 public <T> CompletableFuture<T> relayMessageToMaster(NetconfProxyMessage proxyMessage) {
562 DeviceId deviceId = proxyMessage.deviceId();
563
564 countDownLatch = new CountDownLatch(1);
565 unicastRpcToMaster(proxyMessage, mastershipService.getMasterFor(deviceId));
566
567 try {
568 countDownLatch.await(netconfReplyTimeout, TimeUnit.SECONDS);
569
gyewan.an91d7e7e2019-01-17 15:12:48 +0900570 switch (proxyMessage.subjectType()) {
571 case GET_DEVICE_CAPABILITIES_SET:
gyewan.an3c99ee72019-02-18 15:53:55 +0900572 Set<String> forReturnValue = new LinkedHashSet<>(replyArguments);
573 return CompletableFuture.completedFuture((T) forReturnValue);
gyewan.an91d7e7e2019-01-17 15:12:48 +0900574 default:
Anurag Chadha01ab40a2020-07-13 13:38:08 +0530575 String returnValue = null;
576 if (!replyArguments.isEmpty()) {
577 returnValue = Optional.ofNullable(replyArguments.get(0)).orElse(null);
578 }
gyewan.an3c99ee72019-02-18 15:53:55 +0900579 return CompletableFuture.completedFuture((T) returnValue);
gyewan.an91d7e7e2019-01-17 15:12:48 +0900580 }
gyewan.an3c99ee72019-02-18 15:53:55 +0900581 } catch (InterruptedException e) {
582 log.error("InterruptedOccured while awaiting because of {}", e);
583 CompletableFuture<T> errorFuture = new CompletableFuture<>();
584 errorFuture.completeExceptionally(e);
585 return errorFuture;
586 } catch (Exception e) {
587 log.error("Exception occured because of {}", e);
588 CompletableFuture<T> errorFuture = new CompletableFuture<>();
589 errorFuture.completeExceptionally(e);
590 return errorFuture;
gyewan.an91d7e7e2019-01-17 15:12:48 +0900591 }
592 }
593
594 private <T> CompletableFuture<T> handleProxyMessage(NetconfProxyMessage proxyMessage) {
595 try {
gyewan.an3c99ee72019-02-18 15:53:55 +0900596 switch (proxyMessage.subjectType()) {
597 case GET_DEVICE_CAPABILITIES_SET:
598 return CompletableFuture.completedFuture(
599 (T) netconfProxyMessageHandler.handleIncomingSetMessage(proxyMessage));
600 default:
601 return CompletableFuture.completedFuture(
602 netconfProxyMessageHandler.handleIncomingMessage(proxyMessage));
603 }
gyewan.an91d7e7e2019-01-17 15:12:48 +0900604 } catch (NetconfException e) {
605 CompletableFuture<T> errorFuture = new CompletableFuture<>();
606 errorFuture.completeExceptionally(e);
607 return errorFuture;
608 }
609 }
610
gyewan.an3c99ee72019-02-18 15:53:55 +0900611 private <T> CompletableFuture<T> handleProxyReplyMessage(NetconfProxyMessage replyMessage) {
612 try {
613 switch (replyMessage.subjectType()) {
614 case GET_DEVICE_CAPABILITIES_SET:
615 return CompletableFuture.completedFuture(
616 (T) netconfProxyMessageHandler.handleReplySetMessage(replyMessage));
617 default:
618 return CompletableFuture.completedFuture(
619 netconfProxyMessageHandler.handleReplyMessage(replyMessage));
620
621 }
622 } catch (NetconfException e) {
623 CompletableFuture<T> errorFuture = new CompletableFuture<>();
624 errorFuture.completeExceptionally(e);
625 return errorFuture;
626 }
627 }
628
629
gyewan.an91d7e7e2019-01-17 15:12:48 +0900630 /**
631 * Netconf Proxy Message Handler Implementation class.
632 */
633 private class NetconfProxyMessageHandlerImpl implements NetconfProxyMessageHandler {
634
635 @Override
636 public <T> T handleIncomingMessage(NetconfProxyMessage proxyMessage) throws NetconfException {
637 //TODO: Should throw Netconf Exception in error cases?
638 DeviceId deviceId = proxyMessage.deviceId();
639 NetconfProxyMessage.SubjectType subjectType = proxyMessage.subjectType();
640 NetconfSession secureTransportSession;
gyewan.an3c99ee72019-02-18 15:53:55 +0900641
gyewan.an91d7e7e2019-01-17 15:12:48 +0900642 if (netconfDeviceMap.get(deviceId).isMasterSession()) {
643 secureTransportSession = netconfDeviceMap.get(deviceId).getSession();
644 } else {
645 throw new NetconfException("Ssh session not present");
646 }
647 T reply = null;
648 ArrayList<String> arguments = Lists.newArrayList(proxyMessage.arguments());
649 try {
650 switch (subjectType) {
651 case RPC:
652 reply = (T) secureTransportSession.rpc(arguments.get(0))
653 .get(netconfReplyTimeout, TimeUnit.SECONDS);
654 break;
655 case REQUEST_SYNC:
656 reply = (T) secureTransportSession.requestSync(arguments.get(0));
657 break;
658 case START_SUBSCRIPTION:
659 secureTransportSession.startSubscription(arguments.get(0));
660 break;
661 case END_SUBSCRIPTION:
662 secureTransportSession.endSubscription();
663 break;
664 case REQUEST:
665 reply = (T) secureTransportSession.request(arguments.get(0))
666 .get(netconfReplyTimeout, TimeUnit.SECONDS);
667 break;
668 case GET_SESSION_ID:
669 reply = (T) secureTransportSession.getSessionId();
670 break;
gyewan.an91d7e7e2019-01-17 15:12:48 +0900671 case GET_DEVICE_CAPABILITIES_SET:
672 reply = (T) secureTransportSession.getDeviceCapabilitiesSet();
673 break;
674 default:
675 log.error("Not yet supported for session method {}", subjectType);
676 }
677 } catch (InterruptedException e) {
678 Thread.currentThread().interrupt();
679 throw new NetconfException(e.getMessage(), e.getCause());
680 } catch (ExecutionException | TimeoutException e) {
681 throw new NetconfException(e.getMessage(), e.getCause());
682 }
683
gyewan.an3c99ee72019-02-18 15:53:55 +0900684 ArrayList<String> returnArgument = new ArrayList<String>();
685 Optional.ofNullable(reply).ifPresent(r -> returnArgument.add((String) r));
686
687 DefaultNetconfProxyMessage replyMessage = new DefaultNetconfProxyMessage(
688 subjectType,
689 deviceId,
690 returnArgument,
691 localNodeId);
692
693 unicastReplyToSender(replyMessage, proxyMessage.senderId());
694
695
gyewan.an91d7e7e2019-01-17 15:12:48 +0900696 return reply;
697 }
gyewan.an3c99ee72019-02-18 15:53:55 +0900698
699 @Override
700 public <T> T handleReplyMessage(NetconfProxyMessage replyMessage) {
701 replyArguments = new ArrayList<>(replyMessage.arguments());
702 countDownLatch.countDown();
703 return (T) Optional.ofNullable(replyArguments.get(0)).orElse(null);
704 }
705
706 @Override
707 public Set<String> handleIncomingSetMessage(NetconfProxyMessage proxyMessage) throws NetconfException {
708 DeviceId deviceId = proxyMessage.deviceId();
709 NetconfProxyMessage.SubjectType subjectType = proxyMessage.subjectType();
710 NetconfSession secureTransportSession;
711
712 if (netconfDeviceMap.get(deviceId).isMasterSession()) {
713 secureTransportSession = netconfDeviceMap.get(deviceId).getSession();
714 } else {
715 throw new NetconfException("SSH session not present");
716 }
717
718 Set<String> reply = secureTransportSession.getDeviceCapabilitiesSet();
719 ArrayList<String> returnArgument = new ArrayList<String>(reply);
720
721 DefaultNetconfProxyMessage replyMessage = new DefaultNetconfProxyMessage(
722 subjectType,
723 deviceId,
724 returnArgument,
725 localNodeId);
726
727 unicastReplyToSender(replyMessage, proxyMessage.senderId());
728 return reply;
729 }
730
731 @Override
732 public Set<String> handleReplySetMessage(NetconfProxyMessage replyMessage) {
733 replyArguments = new ArrayList<>(replyMessage.arguments());
734 countDownLatch.countDown();
735
736 return new LinkedHashSet<>(replyArguments);
737
738 }
gyewan.an91d7e7e2019-01-17 15:12:48 +0900739 }
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700740
Yuta HIGUCHI2ee4fba2018-06-12 16:21:06 -0700741 /**
742 * Device factory for the specific NetconfDeviceImpl.
743 *
744 * @deprecated in 1.14.0
745 */
746 @Deprecated
Andrea Campanella950310c2016-02-12 18:14:38 -0800747 private class DefaultNetconfDeviceFactory implements NetconfDeviceFactory {
748
749 @Override
gyewan.an91d7e7e2019-01-17 15:12:48 +0900750 public NetconfDevice createNetconfDevice(NetconfDeviceInfo netconfDeviceInfo) throws NetconfException {
751 return createNetconfDevice(netconfDeviceInfo, true);
752 }
753
754 @Beta
755 @Override
756 public NetconfDevice createNetconfDevice(NetconfDeviceInfo netconfDeviceInfo,
757 boolean isMaster)
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700758 throws NetconfException {
gyewan.an91d7e7e2019-01-17 15:12:48 +0900759 if (isMaster) {
760 log.info("Creating NETCONF session to {} with {}",
761 netconfDeviceInfo.getDeviceId(), NetconfSshClientLib.APACHE_MINA);
762 }
763 return new DefaultNetconfDevice(netconfDeviceInfo, isMaster, NetconfControllerImpl.this);
Andrea Campanella950310c2016-02-12 18:14:38 -0800764 }
765 }
Andrea Campanella2464dc32016-02-17 17:54:53 -0800766
Yuta HIGUCHIf7089102017-05-03 10:31:46 -0700767 //Listener for closed session with devices, gets triggered when devices goes down
768 // or sends the end pattern ]]>]]>
Andrea Campanella2464dc32016-02-17 17:54:53 -0800769 private class DeviceDownEventListener implements NetconfDeviceOutputEventListener {
770
771 @Override
772 public void event(NetconfDeviceOutputEvent event) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200773 DeviceId did = event.getDeviceInfo().getDeviceId();
Andrea Campanellaa2a6c3c2018-12-11 12:56:38 +0100774 if (event.type().equals(NetconfDeviceOutputEvent.Type.DEVICE_UNREGISTERED) ||
775 !mastershipService.isLocalMaster(did)) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200776 removeDevice(did);
777 } else if (event.type().equals(NetconfDeviceOutputEvent.Type.SESSION_CLOSED)) {
778 log.info("Trying to reestablish connection with device {}", did);
779 executor.execute(() -> {
780 try {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700781 NetconfDevice device = netconfDeviceMap.get(did);
782 if (device != null) {
783 device.getSession().checkAndReestablish();
784 log.info("Connection with device {} was reestablished", did);
785 } else {
786 log.warn("The device {} is not in the system", did);
787 }
788
Andrea Campanellac3627842017-04-04 18:06:54 +0200789 } catch (NetconfException e) {
790 log.error("The SSH connection with device {} couldn't be " +
Sean Condon54d82432017-07-26 22:27:25 +0100791 "reestablished due to {}. " +
Andrea Campanellac535b672019-02-25 16:25:35 +0100792 "Marking the device as unreachable", did, e.getMessage());
Andrea Campanellac3627842017-04-04 18:06:54 +0200793 log.debug("Complete exception: ", e);
794 removeDevice(did);
795 }
796 });
Andrea Campanella2464dc32016-02-17 17:54:53 -0800797 }
798 }
799
800 @Override
801 public boolean isRelevant(NetconfDeviceOutputEvent event) {
802 return getDevicesMap().containsKey(event.getDeviceInfo().getDeviceId());
803 }
804 }
andreaeb70a942015-10-16 21:34:46 -0700805}