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