2 * Copyright (c) 2016 Brocade Communication Systems and others. All rights reserved.
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
9 package org.opendaylight.netconf.callhome.mount;
11 import com.google.common.base.Optional;
12 import com.google.common.util.concurrent.ListenableFuture;
13 import java.io.IOException;
14 import java.security.GeneralSecurityException;
15 import java.security.PublicKey;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.concurrent.ExecutionException;
20 import javax.annotation.Nonnull;
21 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
22 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
23 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
24 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
25 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
26 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
27 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
28 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
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.InstanceIdentifier;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
54 class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusRecorder, AutoCloseable {
55 private static final InstanceIdentifier<Topology> NETCONF_TOPO_IID =
56 InstanceIdentifier.create(NetworkTopology.class).child(Topology.class,
57 new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName())));
59 private static final Logger LOG = LoggerFactory.getLogger(CallhomeStatusReporter.class);
61 private final DataBroker dataBroker;
62 private final ListenerRegistration<CallhomeStatusReporter> reg;
64 CallhomeStatusReporter(final DataBroker broker) {
65 this.dataBroker = broker;
66 this.reg = dataBroker.registerDataTreeChangeListener(new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
67 NETCONF_TOPO_IID.child(Node.class)), this);
71 public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Node>> changes) {
72 for (DataTreeModification<Node> change: changes) {
73 final DataObjectModification<Node> rootNode = change.getRootNode();
74 final InstanceIdentifier<Node> identifier = change.getRootPath().getRootIdentifier();
75 switch (rootNode.getModificationType()) {
77 case SUBTREE_MODIFIED:
78 if (isNetconfNode(rootNode.getDataAfter())) {
79 NodeId nodeId = getNodeId(identifier);
81 NetconfNode nnode = rootNode.getDataAfter().augmentation(NetconfNode.class);
82 handledNetconfNode(nodeId, nnode);
87 if (isNetconfNode(rootNode.getDataBefore())) {
88 final NodeId nodeId = getNodeId(identifier);
90 handleDisconnectedNetconfNode(nodeId);
100 private static boolean isNetconfNode(final Node node) {
101 return node.augmentation(NetconfNode.class) != null;
104 private static NodeId getNodeId(final InstanceIdentifier<?> path) {
105 NodeKey key = path.firstKeyOf(Node.class);
106 return key != null ? key.getNodeId() : null;
109 private void handledNetconfNode(final NodeId nodeId, final NetconfNode nnode) {
110 NetconfNodeConnectionStatus.ConnectionStatus csts = nnode.getConnectionStatus();
114 handleConnectedNetconfNode(nodeId);
118 case UnableToConnect: {
119 handleUnableToConnectNetconfNode(nodeId);
125 private void handleConnectedNetconfNode(final NodeId nodeId) {
126 // Fully connected, all services for remote device are
127 // available from the MountPointService.
128 LOG.debug("NETCONF Node: {} is fully connected", nodeId.getValue());
130 Device opDev = readAndGetDevice(nodeId);
132 LOG.warn("No corresponding callhome device found - exiting.");
134 Device modifiedDevice = withConnectedStatus(opDev);
135 if (modifiedDevice == null) {
138 LOG.info("Setting successful status for callhome device id:{}.", nodeId);
139 writeDevice(nodeId, modifiedDevice);
143 private void handleDisconnectedNetconfNode(final NodeId nodeId) {
144 LOG.debug("NETCONF Node: {} disconnected", nodeId.getValue());
146 Device opDev = readAndGetDevice(nodeId);
148 LOG.warn("No corresponding callhome device found - exiting.");
150 Device modifiedDevice = withDisconnectedStatus(opDev);
151 if (modifiedDevice == null) {
154 LOG.info("Setting disconnected status for callhome device id:{}.", nodeId);
155 writeDevice(nodeId, modifiedDevice);
159 private void handleUnableToConnectNetconfNode(final NodeId nodeId) {
160 // The maximum configured number of reconnect attempts
161 // have been reached. No more reconnects will be
162 // attempted by the Netconf Connector.
163 LOG.debug("NETCONF Node: {} connection failed", nodeId.getValue());
165 Device opDev = readAndGetDevice(nodeId);
167 LOG.warn("No corresponding callhome device found - exiting.");
169 Device modifiedDevice = withFailedStatus(opDev);
170 if (modifiedDevice == null) {
173 LOG.info("Setting failed status for callhome device id:{}.", nodeId);
174 writeDevice(nodeId, modifiedDevice);
178 void asForceListedDevice(final String id, final PublicKey serverKey) {
179 NodeId nid = new NodeId(id);
180 Device device = newDevice(id, serverKey, Device1.DeviceStatus.DISCONNECTED);
181 writeDevice(nid, device);
184 void asUnlistedDevice(final String id, final PublicKey serverKey) {
185 NodeId nid = new NodeId(id);
186 Device device = newDevice(id, serverKey, Device1.DeviceStatus.FAILEDNOTALLOWED);
187 writeDevice(nid, device);
190 private static Device newDevice(final String id, final PublicKey serverKey, final Device1.DeviceStatus status) {
191 String sshEncodedKey = serverKey.toString();
193 sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
194 } catch (IOException e) {
195 LOG.warn("Unable to encode public key to ssh format.", e);
197 Device1 d1 = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDNOTALLOWED).build();
198 DeviceBuilder builder = new DeviceBuilder()
200 .withKey(new DeviceKey(id))
201 .setSshHostKey(sshEncodedKey)
202 .addAugmentation(Device1.class, d1);
204 return builder.build();
207 private Device readAndGetDevice(final NodeId nodeId) {
208 return readDevice(nodeId).orNull();
212 private Optional<Device> readDevice(final NodeId nodeId) {
213 ReadOnlyTransaction opTx = dataBroker.newReadOnlyTransaction();
215 InstanceIdentifier<Device> deviceIID = buildDeviceInstanceIdentifier(nodeId);
216 ListenableFuture<Optional<Device>> devFuture = opTx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
218 return devFuture.get();
219 } catch (InterruptedException | ExecutionException e) {
220 return Optional.absent();
224 private void writeDevice(final NodeId nodeId, final Device modifiedDevice) {
225 ReadWriteTransaction opTx = dataBroker.newReadWriteTransaction();
226 opTx.merge(LogicalDatastoreType.OPERATIONAL, buildDeviceInstanceIdentifier(nodeId), modifiedDevice);
230 private static InstanceIdentifier<Device> buildDeviceInstanceIdentifier(final NodeId nodeId) {
231 return InstanceIdentifier.create(NetconfCallhomeServer.class)
232 .child(AllowedDevices.class)
233 .child(Device.class, new DeviceKey(nodeId.getValue()));
236 private static Device withConnectedStatus(final Device opDev) {
237 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.CONNECTED).build();
238 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
239 .setSshHostKey(opDev.getSshHostKey()).build();
242 private static Device withFailedStatus(final Device opDev) {
243 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILED).build();
244 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
245 .setSshHostKey(opDev.getSshHostKey()).build();
248 private static Device withDisconnectedStatus(final Device opDev) {
249 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
250 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
251 .setSshHostKey(opDev.getSshHostKey()).build();
254 private static Device withFailedAuthStatus(final Device opDev) {
255 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDAUTHFAILURE).build();
256 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
257 .setSshHostKey(opDev.getSshHostKey()).build();
260 private void setDeviceStatus(final Device device) {
261 WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
262 InstanceIdentifier<Device> deviceIId =
263 InstanceIdentifier.create(NetconfCallhomeServer.class)
264 .child(AllowedDevices.class)
265 .child(Device.class, device.key());
267 tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIId, device);
271 private AllowedDevices getDevices() {
272 ReadOnlyTransaction rxTransaction = dataBroker.newReadOnlyTransaction();
273 ListenableFuture<Optional<AllowedDevices>> devicesFuture =
274 rxTransaction.read(LogicalDatastoreType.OPERATIONAL, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
276 return devicesFuture.get().orNull();
277 } catch (ExecutionException | InterruptedException e) {
278 LOG.error("Error trying to read the whitelist devices: {}", e);
283 private List<Device> getDevicesAsList() {
284 AllowedDevices devices = getDevices();
285 return devices == null ? new ArrayList<>() : devices.getDevice();
289 public void reportFailedAuth(final PublicKey sshKey) {
290 AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder();
292 for (Device device : getDevicesAsList()) {
293 String keyString = device.getSshHostKey();
296 PublicKey pubKey = decoder.decodePublicKey(keyString);
297 if (sshKey.getAlgorithm().equals(pubKey.getAlgorithm()) && sshKey.equals(pubKey)) {
298 Device failedDevice = withFailedAuthStatus(device);
299 if (failedDevice == null) {
302 LOG.info("Setting auth failed status for callhome device id:{}.", failedDevice.getUniqueId());
303 setDeviceStatus(failedDevice);
306 } catch (GeneralSecurityException e) {
307 LOG.error("Failed decoding a device key with host key: {} {}", keyString, e);
312 LOG.error("No match found for the failed auth device (should have been filtered by whitelist). Key: {}",
317 public void close() throws Exception {