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