b7bcc26313e8cac7c7810954210c4ec4f5024b6c
[netconf.git] / apps / netconf-topology / src / main / java / org / opendaylight / netconf / topology / spi / AbstractNetconfTopology.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.topology.spi;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import io.netty.util.concurrent.EventExecutor;
14 import java.util.HashMap;
15 import java.util.NoSuchElementException;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.Executor;
18 import java.util.concurrent.ScheduledExecutorService;
19 import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
20 import org.opendaylight.controller.config.threadpool.ThreadPool;
21 import org.opendaylight.mdsal.binding.api.DataBroker;
22 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
23 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
24 import org.opendaylight.netconf.client.NetconfClientDispatcher;
25 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
26 import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
27 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
28 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
29 import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev221225.NetconfNodeAugmentedOptional;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode;
32 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
33 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
34 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
35 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
36 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
37 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
38 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
39 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 public abstract class AbstractNetconfTopology {
44     private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
45
46     private final HashMap<NodeId, NetconfNodeHandler> activeConnectors = new HashMap<>();
47     private final NetconfClientDispatcher clientDispatcher;
48     private final EventExecutor eventExecutor;
49     private final DeviceActionFactory deviceActionFactory;
50     private final SchemaResourceManager schemaManager;
51     private final BaseNetconfSchemas baseSchemas;
52     private final NetconfClientConfigurationBuilderFactory builderFactory;
53
54     protected final ScheduledExecutorService keepaliveExecutor;
55     protected final Executor processingExecutor;
56     protected final DataBroker dataBroker;
57     protected final DOMMountPointService mountPointService;
58     protected final String topologyId;
59
60     protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
61             final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
62             final ThreadPool processingExecutor, final SchemaResourceManager schemaManager, final DataBroker dataBroker,
63             final DOMMountPointService mountPointService, final NetconfClientConfigurationBuilderFactory builderFactory,
64             final DeviceActionFactory deviceActionFactory, final BaseNetconfSchemas baseSchemas) {
65         this.topologyId = requireNonNull(topologyId);
66         this.clientDispatcher = clientDispatcher;
67         this.eventExecutor = eventExecutor;
68         this.keepaliveExecutor = keepaliveExecutor.getExecutor();
69         this.processingExecutor = processingExecutor.getExecutor();
70         this.schemaManager = requireNonNull(schemaManager);
71         this.deviceActionFactory = deviceActionFactory;
72         this.dataBroker = requireNonNull(dataBroker);
73         this.mountPointService = mountPointService;
74         this.builderFactory = requireNonNull(builderFactory);
75         this.baseSchemas = requireNonNull(baseSchemas);
76
77         // FIXME: this should be a put(), as we are initializing and will be re-populating the datastore with all the
78         //        devices. Whatever has been there before should be nuked to properly re-align lifecycle.
79         final var wtx = dataBroker.newWriteOnlyTransaction();
80         wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
81             .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
82             .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
83         final var future = wtx.commit();
84         try {
85             future.get();
86         } catch (InterruptedException | ExecutionException e) {
87             LOG.error("Unable to initialize topology {}", topologyId, e);
88             throw new IllegalStateException(e);
89         }
90
91         LOG.debug("Topology {} initialized", topologyId);
92     }
93
94     // Non-final for testing
95     protected void ensureNode(final Node node) {
96         lockedEnsureNode(node);
97     }
98
99     private synchronized void lockedEnsureNode(final Node node) {
100         final var nodeId = node.requireNodeId();
101         final var prev = activeConnectors.remove(nodeId);
102         if (prev != null) {
103             LOG.info("RemoteDevice{{}} was already configured, disconnecting", nodeId);
104             prev.close();
105         }
106         final var netconfNode = node.augmentation(NetconfNode.class);
107         if (netconfNode == null) {
108             LOG.warn("RemoteDevice{{}} is missing NETCONF node configuration, not connecting it", nodeId);
109             return;
110         }
111         final RemoteDeviceId deviceId;
112         try {
113             deviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, netconfNode);
114         } catch (NoSuchElementException e) {
115             LOG.warn("RemoteDevice{{}} has invalid configuration, not connecting it", nodeId, e);
116             return;
117         }
118
119         LOG.info("Connecting RemoteDevice{{}}, with config {}", nodeId, hideCredentials(node));
120
121         // Instantiate the handler ...
122         final var nodeOptional = node.augmentation(NetconfNodeAugmentedOptional.class);
123         final var deviceSalFacade = createSalFacade(deviceId, netconfNode.requireLockDatastore());
124
125         final NetconfNodeHandler nodeHandler;
126         try {
127             nodeHandler = new NetconfNodeHandler(clientDispatcher, eventExecutor, keepaliveExecutor, baseSchemas,
128                 schemaManager, processingExecutor, builderFactory, deviceActionFactory, deviceSalFacade, deviceId,
129                 nodeId, netconfNode, nodeOptional);
130         } catch (IllegalArgumentException e) {
131             // This is a workaround for NETCONF-1114 where the encrypted password's lexical structure is not enforced
132             // in the datastore and it ends up surfacing when we decrypt the password.
133             LOG.warn("RemoteDevice{{}} failed to connect, removing from operational datastore", nodeId, e);
134             deviceSalFacade.close();
135             return;
136         }
137
138         // ... record it ...
139         activeConnectors.put(nodeId, nodeHandler);
140
141         // ... and start it
142         nodeHandler.connect();
143     }
144
145     // Non-final for testing
146     protected void deleteNode(final NodeId nodeId) {
147         lockedDeleteNode(nodeId);
148     }
149
150     private synchronized void lockedDeleteNode(final NodeId nodeId) {
151         final var nodeName = nodeId.getValue();
152         LOG.debug("Disconnecting RemoteDevice{{}}", nodeName);
153
154         final var connectorDTO = activeConnectors.remove(nodeId);
155         if (connectorDTO != null) {
156             connectorDTO.close();
157         }
158     }
159
160     protected final synchronized void deleteAllNodes() {
161         activeConnectors.values().forEach(NetconfNodeHandler::close);
162         activeConnectors.clear();
163     }
164
165     protected RemoteDeviceHandler createSalFacade(final RemoteDeviceId deviceId, final boolean lockDatastore) {
166         return new NetconfTopologyDeviceSalFacade(deviceId, mountPointService, lockDatastore, dataBroker);
167     }
168
169     /**
170      * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
171      *
172      * @param nodeConfiguration Node configuration container.
173      * @return String representation of node configuration with credentials replaced by asterisks.
174      */
175     @VisibleForTesting
176     static final String hideCredentials(final Node nodeConfiguration) {
177         final var netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
178         final var nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
179         final var nodeConfigurationString = nodeConfiguration.toString();
180         return nodeConfigurationString.replace(nodeCredentials, "***");
181     }
182 }