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.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.Collection;
20 import java.util.List;
21 import javax.annotation.Nonnull;
22 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
23 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
24 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
25 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
26 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
27 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
28 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
29 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
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.InstanceIdentifier;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
56 class CallhomeStatusReporter implements DataTreeChangeListener<Node>, 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())));
61 private static final Logger LOG = LoggerFactory.getLogger(CallhomeStatusReporter.class);
63 private final DataBroker dataBroker;
64 private final ListenerRegistration<CallhomeStatusReporter> reg;
66 CallhomeStatusReporter(DataBroker broker) {
67 this.dataBroker = broker;
68 this.reg = dataBroker.registerDataTreeChangeListener(new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
69 NETCONF_TOPO_IID.child(Node.class)), this);
73 public void onDataTreeChanged(Collection<DataTreeModification<Node>> changes) {
74 for (DataTreeModification<Node> change: changes) {
75 final DataObjectModification<Node> rootNode = change.getRootNode();
76 final InstanceIdentifier<Node> identifier = change.getRootPath().getRootIdentifier();
77 switch (rootNode.getModificationType()) {
79 case SUBTREE_MODIFIED:
80 if (isNetconfNode(rootNode.getDataAfter())) {
81 NodeId nodeId = getNodeId(identifier);
83 NetconfNode nnode = rootNode.getDataAfter().getAugmentation(NetconfNode.class);
84 handledNetconfNode(nodeId, nnode);
89 if (isNetconfNode(rootNode.getDataBefore())) {
90 final NodeId nodeId = getNodeId(identifier);
92 handleDisconnectedNetconfNode(nodeId);
102 private boolean isNetconfNode(Node node) {
103 return node.getAugmentation(NetconfNode.class) != null;
106 private NodeId getNodeId(final InstanceIdentifier<?> path) {
107 NodeKey key = path.firstKeyOf(Node.class);
108 return key != null ? key.getNodeId() : null;
111 private void handledNetconfNode(NodeId nodeId, NetconfNode nnode) {
112 NetconfNodeConnectionStatus.ConnectionStatus csts = nnode.getConnectionStatus();
116 handleConnectedNetconfNode(nodeId);
120 case UnableToConnect: {
121 handleUnableToConnectNetconfNode(nodeId);
127 private void handleConnectedNetconfNode(NodeId nodeId) {
128 // Fully connected, all services for remote device are
129 // available from the MountPointService.
130 LOG.debug("NETCONF Node: {} is fully connected", nodeId.getValue());
132 Device opDev = readAndGetDevice(nodeId);
134 LOG.warn("No corresponding callhome device found - exiting.");
136 Device modifiedDevice = withConnectedStatus(opDev);
137 if (modifiedDevice == null) {
140 LOG.info("Setting successful status for callhome device id:{}.", nodeId);
141 writeDevice(nodeId, modifiedDevice);
145 private void handleDisconnectedNetconfNode(NodeId nodeId) {
146 LOG.debug("NETCONF Node: {} disconnected", nodeId.getValue());
148 Device opDev = readAndGetDevice(nodeId);
150 LOG.warn("No corresponding callhome device found - exiting.");
152 Device modifiedDevice = withDisconnectedStatus(opDev);
153 if (modifiedDevice == null) {
156 LOG.info("Setting disconnected status for callhome device id:{}.", nodeId);
157 writeDevice(nodeId, modifiedDevice);
161 private void handleUnableToConnectNetconfNode(NodeId nodeId) {
162 // The maximum configured number of reconnect attempts
163 // have been reached. No more reconnects will be
164 // attempted by the Netconf Connector.
165 LOG.debug("NETCONF Node: {} connection failed", nodeId.getValue());
167 Device opDev = readAndGetDevice(nodeId);
169 LOG.warn("No corresponding callhome device found - exiting.");
171 Device modifiedDevice = withFailedStatus(opDev);
172 if (modifiedDevice == null) {
175 LOG.info("Setting failed status for callhome device id:{}.", nodeId);
176 writeDevice(nodeId, modifiedDevice);
180 void asForceListedDevice(String id, PublicKey serverKey) {
181 NodeId nid = new NodeId(id);
182 Device device = newDevice(id, serverKey, Device1.DeviceStatus.DISCONNECTED);
183 writeDevice(nid, device);
186 void asUnlistedDevice(String id, PublicKey serverKey) {
187 NodeId nid = new NodeId(id);
188 Device device = newDevice(id, serverKey, Device1.DeviceStatus.FAILEDNOTALLOWED);
189 writeDevice(nid, device);
192 private Device newDevice(String id, PublicKey serverKey, Device1.DeviceStatus status) {
193 String sshEncodedKey = serverKey.toString();
195 sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
196 } catch (IOException e) {
197 LOG.warn("Unable to encode public key to ssh format.", e);
199 Device1 d1 = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDNOTALLOWED).build();
200 DeviceBuilder builder = new DeviceBuilder()
202 .setKey(new DeviceKey(id))
203 .setSshHostKey(sshEncodedKey)
204 .addAugmentation(Device1.class, d1);
206 return builder.build();
209 private Device readAndGetDevice(NodeId nodeId) {
210 return readDevice(nodeId).orNull();
214 private Optional<Device> readDevice(NodeId nodeId) {
215 ReadOnlyTransaction opTx = dataBroker.newReadOnlyTransaction();
217 InstanceIdentifier<Device> deviceIID = buildDeviceInstanceIdentifier(nodeId);
218 CheckedFuture<Optional<Device>, ReadFailedException> devFuture =
219 opTx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
222 return devFuture.checkedGet();
223 } catch (ReadFailedException e) {
224 return Optional.absent();
228 private void writeDevice(NodeId nodeId, Device modifiedDevice) {
229 ReadWriteTransaction opTx = dataBroker.newReadWriteTransaction();
230 opTx.merge(LogicalDatastoreType.OPERATIONAL, buildDeviceInstanceIdentifier(nodeId), modifiedDevice);
234 private InstanceIdentifier<Device> buildDeviceInstanceIdentifier(NodeId nodeId) {
235 return InstanceIdentifier.create(NetconfCallhomeServer.class)
236 .child(AllowedDevices.class)
237 .child(Device.class, new DeviceKey(nodeId.getValue()));
240 private Device withConnectedStatus(Device opDev) {
241 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.CONNECTED).build();
242 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
243 .setSshHostKey(opDev.getSshHostKey()).build();
246 private Device withFailedStatus(Device opDev) {
247 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILED).build();
248 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
249 .setSshHostKey(opDev.getSshHostKey()).build();
252 private Device withDisconnectedStatus(Device opDev) {
253 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
254 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
255 .setSshHostKey(opDev.getSshHostKey()).build();
258 private Device withFailedAuthStatus(Device opDev) {
259 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDAUTHFAILURE).build();
260 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
261 .setSshHostKey(opDev.getSshHostKey()).build();
264 private void setDeviceStatus(Device device) {
265 WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
266 InstanceIdentifier<Device> deviceIId =
267 InstanceIdentifier.create(NetconfCallhomeServer.class)
268 .child(AllowedDevices.class)
269 .child(Device.class, device.getKey());
271 tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIId, device);
275 private AllowedDevices getDevices() {
276 ReadOnlyTransaction rxTransaction = dataBroker.newReadOnlyTransaction();
277 CheckedFuture<Optional<AllowedDevices>, ReadFailedException> devicesFuture =
278 rxTransaction.read(LogicalDatastoreType.OPERATIONAL, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
281 Optional<AllowedDevices> opt = devicesFuture.checkedGet();
282 if (opt.isPresent()) {
283 AllowedDevices devices = opt.get();
286 } catch (ReadFailedException e) {
287 LOG.error("Error trying to read the whitelist devices: {}", e);
293 private List<Device> getDevicesAsList() {
294 AllowedDevices devices = getDevices();
295 return devices == null ? new ArrayList<>() : devices.getDevice();
299 public void reportFailedAuth(PublicKey sshKey) {
300 AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder();
302 for (Device device : getDevicesAsList()) {
303 String keyString = device.getSshHostKey();
306 PublicKey pubKey = decoder.decodePublicKey(keyString);
307 if (sshKey.getAlgorithm().equals(pubKey.getAlgorithm()) && sshKey.equals(pubKey)) {
308 Device failedDevice = withFailedAuthStatus(device);
309 if (failedDevice == null) {
312 LOG.info("Setting auth failed status for callhome device id:{}.", failedDevice.getUniqueId());
313 setDeviceStatus(failedDevice);
316 } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) {
317 LOG.error("Failed decoding a device key with host key: {} {}", keyString, e);
322 LOG.error("No match found for the failed auth device (should have been filtered by whitelist). Key: {}",
327 public void close() throws Exception {