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