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.List;
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;
55 class CallhomeStatusReporter implements DataChangeListener, StatusRecorder, AutoCloseable {
56 private static final InstanceIdentifier<Topology> NETCONF_TOPO_IID =
57 InstanceIdentifier.create(NetworkTopology.class).child(Topology.class,
58 new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName())));
60 private static final Logger LOG = LoggerFactory.getLogger(CallhomeStatusReporter.class);
62 private final DataBroker dataBroker;
63 private final ListenerRegistration<DataChangeListener> reg;
65 CallhomeStatusReporter(DataBroker broker) {
66 this.dataBroker = broker;
67 this.reg = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL,
68 NETCONF_TOPO_IID.child(Node.class), this, AsyncDataBroker.DataChangeScope.SUBTREE);
72 public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
73 for (InstanceIdentifier<?> removedPath : change.getRemovedPaths()) {
74 if (removedPath.getTargetType() != NetconfNode.class) {
78 final NodeId nodeId = getNodeId(removedPath);
80 handleDisconnectedNetconfNode(nodeId);
85 for (Map.Entry<InstanceIdentifier<?>, DataObject> entry : change.getUpdatedData().entrySet()) {
86 if (entry.getKey().getTargetType() == NetconfNode.class) {
87 NodeId nodeId = getNodeId(entry.getKey());
89 NetconfNode nnode = (NetconfNode) entry.getValue();
90 handledNetconfNode(nodeId, nnode);
97 private NodeId getNodeId(final InstanceIdentifier<?> path) {
98 NodeKey key = path.firstKeyOf(Node.class);
99 return key != null ? key.getNodeId() : null;
102 private void handledNetconfNode(NodeId nodeId, NetconfNode nnode) {
103 NetconfNodeConnectionStatus.ConnectionStatus csts = nnode.getConnectionStatus();
107 handleConnectedNetconfNode(nodeId);
111 case UnableToConnect: {
112 handleUnableToConnectNetconfNode(nodeId);
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());
123 Device opDev = readAndGetDevice(nodeId);
125 LOG.warn("No corresponding callhome device found - exiting.");
127 Device modifiedDevice = withConnectedStatus(opDev);
128 if (modifiedDevice == null) {
131 LOG.info("Setting successful status for callhome device id:{}.", nodeId);
132 writeDevice(nodeId, modifiedDevice);
136 private void handleDisconnectedNetconfNode(NodeId nodeId) {
137 LOG.debug("NETCONF Node: {} disconnected", nodeId.getValue());
139 Device opDev = readAndGetDevice(nodeId);
141 LOG.warn("No corresponding callhome device found - exiting.");
143 Device modifiedDevice = withDisconnectedStatus(opDev);
144 if (modifiedDevice == null) {
147 LOG.info("Setting disconnected status for callhome device id:{}.", nodeId);
148 writeDevice(nodeId, modifiedDevice);
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());
158 Device opDev = readAndGetDevice(nodeId);
160 LOG.warn("No corresponding callhome device found - exiting.");
162 Device modifiedDevice = withFailedStatus(opDev);
163 if (modifiedDevice == null) {
166 LOG.info("Setting failed status for callhome device id:{}.", nodeId);
167 writeDevice(nodeId, modifiedDevice);
171 void asForceListedDevice(String id, PublicKey serverKey) {
172 NodeId nid = new NodeId(id);
173 Device device = newDevice(id, serverKey, Device1.DeviceStatus.DISCONNECTED);
174 writeDevice(nid, device);
177 void asUnlistedDevice(String id, PublicKey serverKey) {
178 NodeId nid = new NodeId(id);
179 Device device = newDevice(id, serverKey, Device1.DeviceStatus.FAILEDNOTALLOWED);
180 writeDevice(nid, device);
183 private Device newDevice(String id, PublicKey serverKey, Device1.DeviceStatus status) {
184 String sshEncodedKey = serverKey.toString();
186 sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
187 } catch (IOException e) {
188 LOG.warn("Unable to encode public key to ssh format.", e);
190 Device1 d1 = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDNOTALLOWED).build();
191 DeviceBuilder builder = new DeviceBuilder()
193 .setKey(new DeviceKey(id))
194 .setSshHostKey(sshEncodedKey)
195 .addAugmentation(Device1.class, d1);
197 return builder.build();
200 private Device readAndGetDevice(NodeId nodeId) {
201 Optional<Device> opDevGet = readDevice(nodeId);
202 if (opDevGet != null) {
203 if (opDevGet.isPresent()) {
204 return opDevGet.get();
211 private Optional<Device> readDevice(NodeId nodeId) {
212 ReadOnlyTransaction opTx = dataBroker.newReadOnlyTransaction();
214 InstanceIdentifier<Device> deviceIID = buildDeviceInstanceIdentifier(nodeId);
215 CheckedFuture<Optional<Device>, ReadFailedException> devFuture =
216 opTx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
219 return devFuture.checkedGet();
220 } catch (ReadFailedException e) {
225 private void writeDevice(NodeId nodeId, Device modifiedDevice) {
226 ReadWriteTransaction opTx = dataBroker.newReadWriteTransaction();
227 opTx.merge(LogicalDatastoreType.OPERATIONAL, buildDeviceInstanceIdentifier(nodeId), modifiedDevice);
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()));
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();
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();
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();
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();
261 private void setDeviceStatus(Device device) {
262 WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
263 InstanceIdentifier<Device> deviceIId =
264 InstanceIdentifier.create(NetconfCallhomeServer.class)
265 .child(AllowedDevices.class)
266 .child(Device.class, device.getKey());
268 tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIId, device);
272 private AllowedDevices getDevices() {
273 ReadOnlyTransaction rxTransaction = dataBroker.newReadOnlyTransaction();
274 CheckedFuture<Optional<AllowedDevices>, ReadFailedException> devicesFuture =
275 rxTransaction.read(LogicalDatastoreType.OPERATIONAL, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
278 Optional<AllowedDevices> opt = devicesFuture.checkedGet();
279 if (opt.isPresent()) {
280 AllowedDevices devices = opt.get();
283 } catch (ReadFailedException e) {
284 LOG.error("Error trying to read the whitelist devices: {}", e);
290 private List<Device> getDevicesAsList() {
291 AllowedDevices devices = getDevices();
292 return (devices == null) ? new ArrayList<Device>() : devices.getDevice();
296 public void reportFailedAuth(PublicKey sshKey) {
297 AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder();
299 for (Device device : getDevicesAsList()) {
300 String keyString = device.getSshHostKey();
303 PublicKey pubKey = decoder.decodePublicKey(keyString);
304 if (sshKey.getAlgorithm().equals(pubKey.getAlgorithm()) && sshKey.equals(pubKey)) {
305 Device failedDevice = withFailedAuthStatus(device);
306 if (failedDevice == null) {
309 LOG.info("Setting auth failed status for callhome device id:{}.", failedDevice.getUniqueId());
310 setDeviceStatus(failedDevice);
313 } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) {
314 LOG.error("Failed decoding a device key with host key: {} {}", keyString, e);
319 LOG.error("No match found for the failed auth device (should have been filtered by whitelist). Key: {}",
324 public void close() throws Exception {