Migrate netconf-topology to new transport
[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.NetconfClientFactory;
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 NetconfClientFactory clientFactory;
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 scheduledExecutor;
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 NetconfClientFactory clientDispatcher,
61             final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool,
62             final ThreadPool processingThreadPool, final SchemaResourceManager schemaManager,
63             final DataBroker dataBroker, final DOMMountPointService mountPointService,
64             final NetconfClientConfigurationBuilderFactory builderFactory,
65             final DeviceActionFactory deviceActionFactory, final BaseNetconfSchemas baseSchemas) {
66         this.topologyId = requireNonNull(topologyId);
67         this.clientFactory = clientDispatcher;
68         this.eventExecutor = eventExecutor;
69         this.scheduledExecutor = scheduledThreadPool.getExecutor();
70         this.processingExecutor = processingThreadPool.getExecutor();
71         this.schemaManager = requireNonNull(schemaManager);
72         this.deviceActionFactory = deviceActionFactory;
73         this.dataBroker = requireNonNull(dataBroker);
74         this.mountPointService = mountPointService;
75         this.builderFactory = requireNonNull(builderFactory);
76         this.baseSchemas = requireNonNull(baseSchemas);
77
78         // FIXME: this should be a put(), as we are initializing and will be re-populating the datastore with all the
79         //        devices. Whatever has been there before should be nuked to properly re-align lifecycle.
80         final var wtx = dataBroker.newWriteOnlyTransaction();
81         wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
82             .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
83             .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
84         final var future = wtx.commit();
85         try {
86             future.get();
87         } catch (InterruptedException | ExecutionException e) {
88             LOG.error("Unable to initialize topology {}", topologyId, e);
89             throw new IllegalStateException(e);
90         }
91
92         LOG.debug("Topology {} initialized", topologyId);
93     }
94
95     // Non-final for testing
96     protected void ensureNode(final Node node) {
97         lockedEnsureNode(node);
98     }
99
100     private synchronized void lockedEnsureNode(final Node node) {
101         final var nodeId = node.requireNodeId();
102         final var prev = activeConnectors.remove(nodeId);
103         if (prev != null) {
104             LOG.info("RemoteDevice{{}} was already configured, disconnecting", nodeId);
105             prev.close();
106         }
107         final var netconfNode = node.augmentation(NetconfNode.class);
108         if (netconfNode == null) {
109             LOG.warn("RemoteDevice{{}} is missing NETCONF node configuration, not connecting it", nodeId);
110             return;
111         }
112         final RemoteDeviceId deviceId;
113         try {
114             deviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, netconfNode);
115         } catch (NoSuchElementException e) {
116             LOG.warn("RemoteDevice{{}} has invalid configuration, not connecting it", nodeId, e);
117             return;
118         }
119
120         LOG.info("Connecting RemoteDevice{{}}, with config {}", nodeId, hideCredentials(node));
121
122         // Instantiate the handler ...
123         final var nodeOptional = node.augmentation(NetconfNodeAugmentedOptional.class);
124         final var deviceSalFacade = createSalFacade(deviceId, netconfNode.requireLockDatastore());
125
126         final NetconfNodeHandler nodeHandler;
127         try {
128             nodeHandler = new NetconfNodeHandler(clientFactory, scheduledExecutor, baseSchemas,
129                 schemaManager, processingExecutor, builderFactory, deviceActionFactory, deviceSalFacade,
130                 deviceId, nodeId, netconfNode, nodeOptional);
131         } catch (IllegalArgumentException e) {
132             // This is a workaround for NETCONF-1114 where the encrypted password's lexical structure is not enforced
133             // in the datastore and it ends up surfacing when we decrypt the password.
134             LOG.warn("RemoteDevice{{}} failed to connect, removing from operational datastore", nodeId, e);
135             deviceSalFacade.close();
136             return;
137         }
138
139         // ... record it ...
140         activeConnectors.put(nodeId, nodeHandler);
141
142         // ... and start it
143         nodeHandler.connect();
144     }
145
146     // Non-final for testing
147     protected void deleteNode(final NodeId nodeId) {
148         lockedDeleteNode(nodeId);
149     }
150
151     private synchronized void lockedDeleteNode(final NodeId nodeId) {
152         final var nodeName = nodeId.getValue();
153         LOG.debug("Disconnecting RemoteDevice{{}}", nodeName);
154
155         final var connectorDTO = activeConnectors.remove(nodeId);
156         if (connectorDTO != null) {
157             connectorDTO.close();
158         }
159     }
160
161     protected final synchronized void deleteAllNodes() {
162         activeConnectors.values().forEach(NetconfNodeHandler::close);
163         activeConnectors.clear();
164     }
165
166     protected RemoteDeviceHandler createSalFacade(final RemoteDeviceId deviceId, final boolean lockDatastore) {
167         return new NetconfTopologyDeviceSalFacade(deviceId, mountPointService, lockDatastore, dataBroker);
168     }
169
170     /**
171      * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
172      *
173      * @param nodeConfiguration Node configuration container.
174      * @return String representation of node configuration with credentials replaced by asterisks.
175      */
176     @VisibleForTesting
177     static final String hideCredentials(final Node nodeConfiguration) {
178         final var netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
179         final var nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
180         final var nodeConfigurationString = nodeConfiguration.toString();
181         return nodeConfigurationString.replace(nodeCredentials, "***");
182     }
183 }