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
8 package org.opendaylight.netconf.callhome.mount;
10 import com.google.common.util.concurrent.FutureCallback;
11 import com.google.common.util.concurrent.MoreExecutors;
12 import java.io.IOException;
13 import java.security.GeneralSecurityException;
14 import java.security.PublicKey;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.List;
18 import java.util.Optional;
19 import java.util.concurrent.ExecutionException;
20 import org.opendaylight.mdsal.binding.api.DataBroker;
21 import org.opendaylight.mdsal.binding.api.DataObjectModification;
22 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
23 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
24 import org.opendaylight.mdsal.binding.api.DataTreeModification;
25 import org.opendaylight.mdsal.binding.api.ReadTransaction;
26 import org.opendaylight.mdsal.binding.api.WriteTransaction;
27 import org.opendaylight.mdsal.common.api.CommitInfo;
28 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
29 import org.opendaylight.netconf.callhome.protocol.AuthorizedKeysDecoder;
30 import org.opendaylight.netconf.callhome.protocol.StatusRecorder;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1Builder;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.network.topology.topology.topology.types.TopologyNetconf;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceBuilder;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceKey;
41 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
42 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
43 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
44 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
45 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
46 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
47 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
48 import org.opendaylight.yangtools.concepts.ListenerRegistration;
49 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
53 class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusRecorder, AutoCloseable {
54 private static final InstanceIdentifier<Topology> NETCONF_TOPO_IID =
55 InstanceIdentifier.create(NetworkTopology.class).child(Topology.class,
56 new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName())));
58 private static final Logger LOG = LoggerFactory.getLogger(CallhomeStatusReporter.class);
60 private final DataBroker dataBroker;
61 private final ListenerRegistration<CallhomeStatusReporter> reg;
63 CallhomeStatusReporter(final DataBroker broker) {
64 this.dataBroker = broker;
65 this.reg = dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL,
66 NETCONF_TOPO_IID.child(Node.class)), this);
70 public void onDataTreeChanged(final Collection<DataTreeModification<Node>> changes) {
71 for (DataTreeModification<Node> change: changes) {
72 final DataObjectModification<Node> rootNode = change.getRootNode();
73 final InstanceIdentifier<Node> identifier = change.getRootPath().getRootIdentifier();
74 switch (rootNode.getModificationType()) {
76 case SUBTREE_MODIFIED:
77 if (isNetconfNode(rootNode.getDataAfter())) {
78 NodeId nodeId = getNodeId(identifier);
80 NetconfNode nnode = rootNode.getDataAfter().augmentation(NetconfNode.class);
81 handledNetconfNode(nodeId, nnode);
86 if (isNetconfNode(rootNode.getDataBefore())) {
87 final NodeId nodeId = getNodeId(identifier);
89 handleDisconnectedNetconfNode(nodeId);
99 private static boolean isNetconfNode(final Node node) {
100 return node.augmentation(NetconfNode.class) != null;
103 private static NodeId getNodeId(final InstanceIdentifier<?> path) {
104 NodeKey key = path.firstKeyOf(Node.class);
105 return key != null ? key.getNodeId() : null;
108 private void handledNetconfNode(final NodeId nodeId, final NetconfNode nnode) {
109 NetconfNodeConnectionStatus.ConnectionStatus csts = nnode.getConnectionStatus();
113 handleConnectedNetconfNode(nodeId);
117 case UnableToConnect: {
118 handleUnableToConnectNetconfNode(nodeId);
124 private void handleConnectedNetconfNode(final NodeId nodeId) {
125 // Fully connected, all services for remote device are
126 // available from the MountPointService.
127 LOG.debug("NETCONF Node: {} is fully connected", nodeId.getValue());
129 Device opDev = readAndGetDevice(nodeId);
131 LOG.warn("No corresponding callhome device found - exiting.");
133 Device modifiedDevice = withConnectedStatus(opDev);
134 if (modifiedDevice == null) {
137 LOG.info("Setting successful status for callhome device id:{}.", nodeId);
138 writeDevice(nodeId, modifiedDevice);
142 private void handleDisconnectedNetconfNode(final NodeId nodeId) {
143 LOG.debug("NETCONF Node: {} disconnected", nodeId.getValue());
145 Device opDev = readAndGetDevice(nodeId);
147 LOG.warn("No corresponding callhome device found - exiting.");
149 Device modifiedDevice = withDisconnectedStatus(opDev);
150 if (modifiedDevice == null) {
153 LOG.info("Setting disconnected status for callhome device id:{}.", nodeId);
154 writeDevice(nodeId, modifiedDevice);
158 private void handleUnableToConnectNetconfNode(final NodeId nodeId) {
159 // The maximum configured number of reconnect attempts
160 // have been reached. No more reconnects will be
161 // attempted by the Netconf Connector.
162 LOG.debug("NETCONF Node: {} connection failed", nodeId.getValue());
164 Device opDev = readAndGetDevice(nodeId);
166 LOG.warn("No corresponding callhome device found - exiting.");
168 Device modifiedDevice = withFailedStatus(opDev);
169 if (modifiedDevice == null) {
172 LOG.info("Setting failed status for callhome device id:{}.", nodeId);
173 writeDevice(nodeId, modifiedDevice);
177 void asForceListedDevice(final String id, final PublicKey serverKey) {
178 NodeId nid = new NodeId(id);
179 Device device = newDevice(id, serverKey, Device1.DeviceStatus.DISCONNECTED);
180 writeDevice(nid, device);
183 void asUnlistedDevice(final String id, final PublicKey serverKey) {
184 NodeId nid = new NodeId(id);
185 Device device = newDevice(id, serverKey, Device1.DeviceStatus.FAILEDNOTALLOWED);
186 writeDevice(nid, device);
189 private static Device newDevice(final String id, final PublicKey serverKey, final Device1.DeviceStatus status) {
190 String sshEncodedKey = serverKey.toString();
192 sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
193 } catch (IOException e) {
194 LOG.warn("Unable to encode public key to ssh format.", e);
196 Device1 d1 = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDNOTALLOWED).build();
197 DeviceBuilder builder = new DeviceBuilder()
199 .withKey(new DeviceKey(id))
200 .setSshHostKey(sshEncodedKey)
201 .addAugmentation(Device1.class, d1);
203 return builder.build();
206 private Device readAndGetDevice(final NodeId nodeId) {
207 return readDevice(nodeId).orElse(null);
210 private Optional<Device> readDevice(final NodeId nodeId) {
211 try (ReadTransaction opTx = dataBroker.newReadOnlyTransaction()) {
212 InstanceIdentifier<Device> deviceIID = buildDeviceInstanceIdentifier(nodeId);
213 return opTx.read(LogicalDatastoreType.OPERATIONAL, deviceIID).get();
214 } catch (InterruptedException | ExecutionException e) {
215 return Optional.empty();
219 private void writeDevice(final NodeId nodeId, final Device modifiedDevice) {
220 WriteTransaction opTx = dataBroker.newWriteOnlyTransaction();
221 opTx.merge(LogicalDatastoreType.OPERATIONAL, buildDeviceInstanceIdentifier(nodeId), modifiedDevice);
222 commit(opTx, modifiedDevice.key());
225 private static InstanceIdentifier<Device> buildDeviceInstanceIdentifier(final NodeId nodeId) {
226 return InstanceIdentifier.create(NetconfCallhomeServer.class)
227 .child(AllowedDevices.class)
228 .child(Device.class, new DeviceKey(nodeId.getValue()));
231 private static Device withConnectedStatus(final Device opDev) {
232 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.CONNECTED).build();
233 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
234 .setSshHostKey(opDev.getSshHostKey()).build();
237 private static Device withFailedStatus(final Device opDev) {
238 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILED).build();
239 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
240 .setSshHostKey(opDev.getSshHostKey()).build();
243 private static Device withDisconnectedStatus(final Device opDev) {
244 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
245 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
246 .setSshHostKey(opDev.getSshHostKey()).build();
249 private static Device withFailedAuthStatus(final Device opDev) {
250 Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDAUTHFAILURE).build();
251 return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
252 .setSshHostKey(opDev.getSshHostKey()).build();
255 private void setDeviceStatus(final Device device) {
256 WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
257 InstanceIdentifier<Device> deviceIId = InstanceIdentifier.create(NetconfCallhomeServer.class)
258 .child(AllowedDevices.class)
259 .child(Device.class, device.key());
261 tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIId, device);
262 commit(tx, device.key());
265 private static void commit(final WriteTransaction tx, final DeviceKey device) {
266 tx.commit().addCallback(new FutureCallback<CommitInfo>() {
268 public void onSuccess(final CommitInfo result) {
269 LOG.debug("Device {} committed", device);
273 public void onFailure(final Throwable cause) {
274 LOG.warn("Failed to commit device {}", device, cause);
276 }, MoreExecutors.directExecutor());
279 private AllowedDevices getDevices() {
280 try (ReadTransaction rxTransaction = dataBroker.newReadOnlyTransaction()) {
281 return rxTransaction.read(LogicalDatastoreType.OPERATIONAL, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES)
283 } catch (ExecutionException | InterruptedException e) {
284 LOG.error("Error trying to read the whitelist devices", e);
289 private List<Device> getDevicesAsList() {
290 AllowedDevices devices = getDevices();
291 return devices == null ? new ArrayList<>() : devices.getDevice();
295 public void reportFailedAuth(final PublicKey sshKey) {
296 AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder();
298 for (Device device : getDevicesAsList()) {
299 String keyString = device.getSshHostKey();
300 if (keyString == null) {
301 LOG.info("Whitelist device {} does not have a host key, skipping it", device.getUniqueId());
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 (GeneralSecurityException 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() {