Use base64 encoding for netconf device passwords
[netconf.git] / apps / netconf-topology / src / main / java / org / opendaylight / netconf / topology / spi / NetconfDeviceTopologyAdapter.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.collect.ImmutableList;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import com.google.common.util.concurrent.SettableFuture;
17 import org.checkerframework.checker.lock.qual.GuardedBy;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.mdsal.binding.api.DataBroker;
20 import org.opendaylight.mdsal.binding.api.Transaction;
21 import org.opendaylight.mdsal.binding.api.TransactionChain;
22 import org.opendaylight.mdsal.binding.api.TransactionChainListener;
23 import org.opendaylight.mdsal.binding.api.WriteTransaction;
24 import org.opendaylight.mdsal.common.api.CommitInfo;
25 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
26 import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities;
27 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.SessionIdType;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231121.ConnectionOper.ConnectionStatus;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231121.connection.oper.AvailableCapabilitiesBuilder;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231121.connection.oper.ClusteredConnectionStatusBuilder;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231121.connection.oper.UnavailableCapabilitiesBuilder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231121.connection.oper.available.capabilities.AvailableCapability;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231121.connection.oper.unavailable.capabilities.UnavailableCapabilityBuilder;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev231121.NetconfNode;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev231121.NetconfNodeBuilder;
38 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
39 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
40 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
41 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
42 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
43 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
44 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
45 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
46 import org.opendaylight.yangtools.yang.common.Empty;
47 import org.opendaylight.yangtools.yang.common.Uint16;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 public final class NetconfDeviceTopologyAdapter implements TransactionChainListener {
52     private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceTopologyAdapter.class);
53
54     private final @NonNull KeyedInstanceIdentifier<Topology, TopologyKey> topologyPath;
55     private final DataBroker dataBroker;
56     private final RemoteDeviceId id;
57
58     @GuardedBy("this")
59     private SettableFuture<Empty> closeFuture;
60     @GuardedBy("this")
61     private TransactionChain txChain;
62
63     public NetconfDeviceTopologyAdapter(final DataBroker dataBroker,
64             final KeyedInstanceIdentifier<Topology, TopologyKey> topologyPath, final RemoteDeviceId id) {
65         this.dataBroker = requireNonNull(dataBroker);
66         this.topologyPath = requireNonNull(topologyPath);
67         this.id = requireNonNull(id);
68         txChain = dataBroker.createMergingTransactionChain(this);
69
70         final var tx = txChain.newWriteOnlyTransaction();
71         LOG.trace("{}: Init device state transaction {} putting if absent operational data started.", id,
72             tx.getIdentifier());
73         final var nodePath = nodePath();
74         tx.put(LogicalDatastoreType.OPERATIONAL, nodePath, new NodeBuilder()
75             .withKey(nodePath.getKey())
76             .addAugmentation(new NetconfNodeBuilder()
77                 .setConnectionStatus(ConnectionStatus.Connecting)
78                 .setHost(id.host())
79                 .setPort(new PortNumber(Uint16.valueOf(id.address().getPort()))).build())
80             .build());
81         LOG.trace("{}: Init device state transaction {} putting operational data ended.", id, tx.getIdentifier());
82         commitTransaction(tx, "init");
83     }
84
85     public synchronized void updateDeviceData(final boolean up, final NetconfDeviceCapabilities capabilities,
86             final SessionIdType sessionId) {
87         final var tx = txChain.newWriteOnlyTransaction();
88         LOG.trace("{}: Update device state transaction {} merging operational data started.", id, tx.getIdentifier());
89         tx.put(LogicalDatastoreType.OPERATIONAL, netconfNodePath(),
90             newNetconfNodeBuilder(up, capabilities, sessionId).build());
91         LOG.trace("{}: Update device state transaction {} merging operational data ended.", id, tx.getIdentifier());
92         commitTransaction(tx, "update");
93     }
94
95     public synchronized void updateClusteredDeviceData(final boolean up, final String masterAddress,
96             final NetconfDeviceCapabilities capabilities, final SessionIdType sessionId) {
97         final var tx = txChain.newWriteOnlyTransaction();
98         LOG.trace("{}: Update device state transaction {} merging operational data started.", id, tx.getIdentifier());
99         tx.put(LogicalDatastoreType.OPERATIONAL, netconfNodePath(), newNetconfNodeBuilder(up, capabilities, sessionId)
100             .setClusteredConnectionStatus(new ClusteredConnectionStatusBuilder()
101                 .setNetconfMasterNode(masterAddress)
102                 .build())
103             .build());
104         LOG.trace("{}: Update device state transaction {} merging operational data ended.", id, tx.getIdentifier());
105         commitTransaction(tx, "update");
106     }
107
108     public synchronized void setDeviceAsFailed(final Throwable throwable) {
109         final var data = new NetconfNodeBuilder()
110                 .setHost(id.host())
111                 .setPort(new PortNumber(Uint16.valueOf(id.address().getPort())))
112                 .setConnectionStatus(ConnectionStatus.UnableToConnect)
113                 .setConnectedMessage(extractReason(throwable))
114                 .build();
115
116         final var tx = txChain.newWriteOnlyTransaction();
117         LOG.trace("{}: Setting device state as failed {} putting operational data started.", id, tx.getIdentifier());
118         tx.put(LogicalDatastoreType.OPERATIONAL, netconfNodePath(), data);
119         LOG.trace("{}: Setting device state as failed {} putting operational data ended.", id, tx.getIdentifier());
120         commitTransaction(tx, "update-failed-device");
121     }
122
123     public synchronized ListenableFuture<Empty> shutdown() {
124         if (closeFuture != null) {
125             return closeFuture;
126         }
127
128         closeFuture = SettableFuture.create();
129
130         final var tx = txChain.newWriteOnlyTransaction();
131         LOG.trace("{}: Close device state transaction {} removing all data started.", id, tx.getIdentifier());
132         tx.delete(LogicalDatastoreType.OPERATIONAL, nodePath());
133         LOG.trace("{}: Close device state transaction {} removing all data ended.", id, tx.getIdentifier());
134         commitTransaction(tx, "close");
135
136         txChain.close();
137         return closeFuture;
138     }
139
140     private @NonNull KeyedInstanceIdentifier<Node, NodeKey> nodePath() {
141         return topologyPath.child(Node.class, new NodeKey(new NodeId(id.name())));
142     }
143
144     private @NonNull InstanceIdentifier<NetconfNode> netconfNodePath() {
145         return nodePath().augmentation(NetconfNode.class);
146     }
147
148     private NetconfNodeBuilder newNetconfNodeBuilder(final boolean up, final NetconfDeviceCapabilities capabilities,
149             final SessionIdType sessionId) {
150         return new NetconfNodeBuilder()
151             .setHost(id.host())
152             .setPort(new PortNumber(Uint16.valueOf(id.address().getPort())))
153             .setConnectionStatus(up ? ConnectionStatus.Connected : ConnectionStatus.Connecting)
154             .setAvailableCapabilities(new AvailableCapabilitiesBuilder()
155                 .setAvailableCapability(ImmutableList.<AvailableCapability>builder()
156                     .addAll(capabilities.nonModuleBasedCapabilities())
157                     .addAll(capabilities.resolvedCapabilities())
158                     .build())
159                 .build())
160             .setUnavailableCapabilities(new UnavailableCapabilitiesBuilder()
161                 .setUnavailableCapability(capabilities.unresolvedCapabilites().entrySet().stream()
162                     .map(unresolved -> new UnavailableCapabilityBuilder()
163                         // FIXME: better conversion than 'toString' ?
164                         .setCapability(unresolved.getKey().toString())
165                         .setFailureReason(unresolved.getValue())
166                         .build())
167                     .toList())
168                 .build())
169             .setSessionId(sessionId);
170     }
171
172     private void commitTransaction(final WriteTransaction transaction, final String txType) {
173         LOG.trace("{}: Committing Transaction {}:{}", id, txType, transaction.getIdentifier());
174
175         transaction.commit().addCallback(new FutureCallback<CommitInfo>() {
176             @Override
177             public void onSuccess(final CommitInfo result) {
178                 LOG.trace("{}: Transaction({}) {} SUCCESSFUL", id, txType, transaction.getIdentifier());
179             }
180
181             @Override
182             public void onFailure(final Throwable throwable) {
183                 LOG.error("{}: Transaction({}) {} FAILED!", id, txType, transaction.getIdentifier(), throwable);
184             }
185         }, MoreExecutors.directExecutor());
186     }
187
188     @Override
189     public synchronized void onTransactionChainFailed(final TransactionChain chain, final Transaction transaction,
190             final Throwable cause) {
191         LOG.warn("{}: TransactionChain({}) {} FAILED!", id, chain, transaction.getIdentifier(), cause);
192         if (closeFuture != null) {
193             closeFuture.setException(cause);
194             return;
195         }
196
197         // FIXME: move this up once we have MDSAL-838 fixed
198         chain.close();
199
200         txChain = dataBroker.createMergingTransactionChain(this);
201         LOG.info("{}: TransactionChain reset to {}", id, txChain);
202         // FIXME: restart last update
203     }
204
205     @Override
206     public synchronized void onTransactionChainSuccessful(final TransactionChain chain) {
207         LOG.trace("{}: TransactionChain({}) SUCCESSFUL", id, chain);
208         closeFuture.set(Empty.value());
209     }
210
211     private static @NonNull String extractReason(final Throwable throwable) {
212         if (throwable != null) {
213             final var message = throwable.getMessage();
214             if (message != null) {
215                 return message;
216             }
217         }
218         return "Unknown reason";
219     }
220 }