Update MRI projects for Aluminium
[netconf.git] / netconf / callhome-provider / src / main / java / org / opendaylight / netconf / callhome / mount / CallhomeStatusReporter.java
1 /*
2  * Copyright (c) 2016 Brocade Communication Systems 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.callhome.mount;
9
10 import com.google.common.util.concurrent.FutureCallback;
11 import com.google.common.util.concurrent.MoreExecutors;
12 import java.io.IOException;
13 import java.security.GeneralSecurityException;
14 import java.security.PublicKey;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.Optional;
18 import java.util.concurrent.ExecutionException;
19 import org.opendaylight.mdsal.binding.api.DataBroker;
20 import org.opendaylight.mdsal.binding.api.DataObjectModification;
21 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
22 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
23 import org.opendaylight.mdsal.binding.api.DataTreeModification;
24 import org.opendaylight.mdsal.binding.api.ReadTransaction;
25 import org.opendaylight.mdsal.binding.api.WriteTransaction;
26 import org.opendaylight.mdsal.common.api.CommitInfo;
27 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
28 import org.opendaylight.netconf.callhome.protocol.AuthorizedKeysDecoder;
29 import org.opendaylight.netconf.callhome.protocol.StatusRecorder;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1.DeviceStatus;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1Builder;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.network.topology.topology.topology.types.TopologyNetconf;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceBuilder;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceKey;
41 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
42 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
43 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
44 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
45 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
46 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
47 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
48 import org.opendaylight.yangtools.concepts.ListenerRegistration;
49 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusRecorder, AutoCloseable {
54     private static final InstanceIdentifier<Topology> NETCONF_TOPO_IID =
55             InstanceIdentifier.create(NetworkTopology.class).child(Topology.class,
56                     new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName())));
57
58     private static final Logger LOG = LoggerFactory.getLogger(CallhomeStatusReporter.class);
59
60     private final DataBroker dataBroker;
61     private final ListenerRegistration<CallhomeStatusReporter> reg;
62
63     CallhomeStatusReporter(final DataBroker broker) {
64         this.dataBroker = broker;
65         this.reg = dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL,
66             NETCONF_TOPO_IID.child(Node.class)), this);
67     }
68
69     @Override
70     public void onDataTreeChanged(final Collection<DataTreeModification<Node>> changes) {
71         for (DataTreeModification<Node> change: changes) {
72             final DataObjectModification<Node> rootNode = change.getRootNode();
73             final InstanceIdentifier<Node> identifier = change.getRootPath().getRootIdentifier();
74             switch (rootNode.getModificationType()) {
75                 case WRITE:
76                 case SUBTREE_MODIFIED:
77                     if (isNetconfNode(rootNode.getDataAfter())) {
78                         NodeId nodeId = getNodeId(identifier);
79                         if (nodeId != null) {
80                             NetconfNode nnode = rootNode.getDataAfter().augmentation(NetconfNode.class);
81                             handledNetconfNode(nodeId, nnode);
82                         }
83                     }
84                     break;
85                 case DELETE:
86                     if (isNetconfNode(rootNode.getDataBefore())) {
87                         final NodeId nodeId = getNodeId(identifier);
88                         if (nodeId != null) {
89                             handleDisconnectedNetconfNode(nodeId);
90                         }
91                     }
92                     break;
93                 default:
94                     break;
95             }
96         }
97     }
98
99     private static boolean isNetconfNode(final Node node) {
100         return node.augmentation(NetconfNode.class) != null;
101     }
102
103     private static NodeId getNodeId(final InstanceIdentifier<?> path) {
104         NodeKey key = path.firstKeyOf(Node.class);
105         return key != null ? key.getNodeId() : null;
106     }
107
108     private void handledNetconfNode(final NodeId nodeId, final NetconfNode nnode) {
109         NetconfNodeConnectionStatus.ConnectionStatus csts = nnode.getConnectionStatus();
110
111         switch (csts) {
112             case Connected: {
113                 handleConnectedNetconfNode(nodeId);
114                 break;
115             }
116             default:
117             case UnableToConnect: {
118                 handleUnableToConnectNetconfNode(nodeId);
119                 break;
120             }
121         }
122     }
123
124     private void handleConnectedNetconfNode(final NodeId nodeId) {
125         // Fully connected, all services for remote device are
126         // available from the MountPointService.
127         LOG.debug("NETCONF Node: {} is fully connected", nodeId.getValue());
128
129         Device opDev = readAndGetDevice(nodeId);
130         if (opDev == null) {
131             LOG.warn("No corresponding callhome device found - exiting.");
132         } else {
133             Device modifiedDevice = withConnectedStatus(opDev);
134             if (modifiedDevice == null) {
135                 return;
136             }
137             LOG.info("Setting successful status for callhome device id:{}.", nodeId);
138             writeDevice(nodeId, modifiedDevice);
139         }
140     }
141
142     private void handleDisconnectedNetconfNode(final NodeId nodeId) {
143         LOG.debug("NETCONF Node: {} disconnected", nodeId.getValue());
144
145         Device opDev = readAndGetDevice(nodeId);
146         if (opDev == null) {
147             LOG.warn("No corresponding callhome device found - exiting.");
148         } else {
149             Device modifiedDevice = withDisconnectedStatus(opDev);
150             if (modifiedDevice == null) {
151                 return;
152             }
153             LOG.info("Setting disconnected status for callhome device id:{}.", nodeId);
154             writeDevice(nodeId, modifiedDevice);
155         }
156     }
157
158     private void handleUnableToConnectNetconfNode(final NodeId nodeId) {
159         // The maximum configured number of reconnect attempts
160         // have been reached. No more reconnects will be
161         // attempted by the Netconf Connector.
162         LOG.debug("NETCONF Node: {} connection failed", nodeId.getValue());
163
164         Device opDev = readAndGetDevice(nodeId);
165         if (opDev == null) {
166             LOG.warn("No corresponding callhome device found - exiting.");
167         } else {
168             Device modifiedDevice = withFailedStatus(opDev);
169             if (modifiedDevice == null) {
170                 return;
171             }
172             LOG.info("Setting failed status for callhome device id:{}.", nodeId);
173             writeDevice(nodeId, modifiedDevice);
174         }
175     }
176
177     void asForceListedDevice(final String id, final PublicKey serverKey) {
178         NodeId nid = new NodeId(id);
179         Device device = newDevice(id, serverKey, Device1.DeviceStatus.DISCONNECTED);
180         writeDevice(nid, device);
181     }
182
183     void asUnlistedDevice(final String id, final PublicKey serverKey) {
184         NodeId nid = new NodeId(id);
185         Device device = newDevice(id, serverKey, Device1.DeviceStatus.FAILEDNOTALLOWED);
186         writeDevice(nid, device);
187     }
188
189     private static Device newDevice(final String id, final PublicKey serverKey, final Device1.DeviceStatus status) {
190         String sshEncodedKey = serverKey.toString();
191         try {
192             sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
193         } catch (IOException e) {
194             LOG.warn("Unable to encode public key to ssh format.", e);
195         }
196         return new DeviceBuilder()
197                 .setUniqueId(id)
198                 .withKey(new DeviceKey(id))
199                 .setSshHostKey(sshEncodedKey)
200                 .addAugmentation(new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDNOTALLOWED).build())
201                 .build();
202     }
203
204     private Device readAndGetDevice(final NodeId nodeId) {
205         return readDevice(nodeId).orElse(null);
206     }
207
208     private Optional<Device> readDevice(final NodeId nodeId) {
209         try (ReadTransaction opTx = dataBroker.newReadOnlyTransaction()) {
210             InstanceIdentifier<Device> deviceIID = buildDeviceInstanceIdentifier(nodeId);
211             return opTx.read(LogicalDatastoreType.OPERATIONAL, deviceIID).get();
212         } catch (InterruptedException | ExecutionException e) {
213             return Optional.empty();
214         }
215     }
216
217     private void writeDevice(final NodeId nodeId, final Device modifiedDevice) {
218         WriteTransaction opTx = dataBroker.newWriteOnlyTransaction();
219         opTx.merge(LogicalDatastoreType.OPERATIONAL, buildDeviceInstanceIdentifier(nodeId), modifiedDevice);
220         commit(opTx, modifiedDevice.key());
221     }
222
223     private static InstanceIdentifier<Device> buildDeviceInstanceIdentifier(final NodeId nodeId) {
224         return InstanceIdentifier.create(NetconfCallhomeServer.class)
225                 .child(AllowedDevices.class)
226                 .child(Device.class, new DeviceKey(nodeId.getValue()));
227     }
228
229     private static Device withConnectedStatus(final Device opDev) {
230         return deviceWithStatus(opDev, Device1.DeviceStatus.CONNECTED);
231     }
232
233     private static Device withFailedStatus(final Device opDev) {
234         return deviceWithStatus(opDev, DeviceStatus.FAILED);
235     }
236
237     private static Device withDisconnectedStatus(final Device opDev) {
238         return deviceWithStatus(opDev, DeviceStatus.DISCONNECTED);
239     }
240
241     private static Device withFailedAuthStatus(final Device opDev) {
242         return deviceWithStatus(opDev, DeviceStatus.FAILEDAUTHFAILURE);
243     }
244
245     private static Device deviceWithStatus(final Device opDev, final DeviceStatus status) {
246         return new DeviceBuilder()
247                 .setUniqueId(opDev.getUniqueId())
248                 .setSshHostKey(opDev.getSshHostKey())
249                 .addAugmentation(new Device1Builder().setDeviceStatus(status).build())
250                 .build();
251     }
252
253     private void setDeviceStatus(final Device device) {
254         WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
255         InstanceIdentifier<Device> deviceIId = InstanceIdentifier.create(NetconfCallhomeServer.class)
256                         .child(AllowedDevices.class)
257                         .child(Device.class, device.key());
258
259         tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIId, device);
260         commit(tx, device.key());
261     }
262
263     private static void commit(final WriteTransaction tx, final DeviceKey device) {
264         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
265             @Override
266             public void onSuccess(final CommitInfo result) {
267                 LOG.debug("Device {} committed", device);
268             }
269
270             @Override
271             public void onFailure(final Throwable cause) {
272                 LOG.warn("Failed to commit device {}", device, cause);
273             }
274         }, MoreExecutors.directExecutor());
275     }
276
277     private AllowedDevices getDevices() {
278         try (ReadTransaction rxTransaction = dataBroker.newReadOnlyTransaction()) {
279             return rxTransaction.read(LogicalDatastoreType.OPERATIONAL, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES)
280                     .get().orElse(null);
281         } catch (ExecutionException | InterruptedException e) {
282             LOG.error("Error trying to read the whitelist devices", e);
283             return null;
284         }
285     }
286
287     private Collection<Device> getDevicesAsList() {
288         AllowedDevices devices = getDevices();
289         return devices == null ? Collections.emptyList() : devices.nonnullDevice().values();
290     }
291
292     @Override
293     public void reportFailedAuth(final PublicKey sshKey) {
294         AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder();
295
296         for (Device device : getDevicesAsList()) {
297             String keyString = device.getSshHostKey();
298             if (keyString == null) {
299                 LOG.info("Whitelist device {} does not have a host key, skipping it", device.getUniqueId());
300                 continue;
301             }
302
303             try {
304                 PublicKey pubKey = decoder.decodePublicKey(keyString);
305                 if (sshKey.getAlgorithm().equals(pubKey.getAlgorithm()) && sshKey.equals(pubKey)) {
306                     Device failedDevice = withFailedAuthStatus(device);
307                     if (failedDevice == null) {
308                         return;
309                     }
310                     LOG.info("Setting auth failed status for callhome device id:{}.", failedDevice.getUniqueId());
311                     setDeviceStatus(failedDevice);
312                     return;
313                 }
314             } catch (GeneralSecurityException e) {
315                 LOG.error("Failed decoding a device key with host key: {}", keyString, e);
316                 return;
317             }
318         }
319
320         LOG.error("No match found for the failed auth device (should have been filtered by whitelist). Key: {}",
321                 sshKey);
322     }
323
324     @Override
325     public void close() {
326         reg.close();
327     }
328 }