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