Replace whitelist with allowlist
[netconf.git] / netconf / callhome-provider / src / main / java / org / opendaylight / netconf / callhome / mount / CallhomeStatusReporter.java
index 3db60e9fca57f591c24ebb0c52ae9162a897fd0c..fb09da6015bb7580e82d7cc3e3860d374d8d3a85 100644 (file)
@@ -5,40 +5,44 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.netconf.callhome.mount;
 
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.MoreExecutors;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
+import java.util.Collections;
+import java.util.Optional;
 import java.util.concurrent.ExecutionException;
-import javax.annotation.Nonnull;
-import org.opendaylight.controller.md.sal.binding.api.DataBroker;
-import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
-import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
-import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
-import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
-import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
-import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
-import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
-import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataObjectModification;
+import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.netconf.callhome.protocol.AuthorizedKeysDecoder;
 import org.opendaylight.netconf.callhome.protocol.StatusRecorder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1.DeviceStatus;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1Builder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.network.topology.topology.topology.types.TopologyNetconf;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.NetconfCallhomeServer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.AllowedDevices;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.allowed.devices.Device;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.allowed.devices.DeviceBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.allowed.devices.DeviceKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.allowed.devices.device.Transport;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.allowed.devices.device.transport.Ssh;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.allowed.devices.device.transport.SshBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.allowed.devices.device.transport.ssh.SshClientParams;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev201015.netconf.callhome.server.allowed.devices.device.transport.ssh.SshClientParamsBuilder;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
@@ -51,7 +55,7 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusRecorder, AutoCloseable {
+final class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusRecorder, AutoCloseable {
     private static final InstanceIdentifier<Topology> NETCONF_TOPO_IID =
             InstanceIdentifier.create(NetworkTopology.class).child(Topology.class,
                     new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName())));
@@ -63,13 +67,13 @@ class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusReco
 
     CallhomeStatusReporter(final DataBroker broker) {
         this.dataBroker = broker;
-        this.reg = dataBroker.registerDataTreeChangeListener(new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
+        this.reg = dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL,
             NETCONF_TOPO_IID.child(Node.class)), this);
     }
 
     @Override
-    public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Node>> changes) {
-        for (DataTreeModification<Node> change: changes) {
+    public void onDataTreeChanged(final Collection<DataTreeModification<Node>> changes) {
+        for (DataTreeModification<Node> change : changes) {
             final DataObjectModification<Node> rootNode = change.getRootNode();
             final InstanceIdentifier<Node> identifier = change.getRootPath().getRootIdentifier();
             switch (rootNode.getModificationType()) {
@@ -188,109 +192,130 @@ class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusReco
     }
 
     private static Device newDevice(final String id, final PublicKey serverKey, final Device1.DeviceStatus status) {
+        // used only for netconf devices that are connected via SSH transport and global credentials
         String sshEncodedKey = serverKey.toString();
         try {
             sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
         } catch (IOException e) {
             LOG.warn("Unable to encode public key to ssh format.", e);
         }
-        Device1 d1 = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDNOTALLOWED).build();
-        DeviceBuilder builder = new DeviceBuilder()
+        final SshClientParams sshParams = new SshClientParamsBuilder().setHostKey(sshEncodedKey).build();
+        final Transport transport = new SshBuilder().setSshClientParams(sshParams).build();
+        return new DeviceBuilder()
                 .setUniqueId(id)
                 .withKey(new DeviceKey(id))
-                .setSshHostKey(sshEncodedKey)
-                .addAugmentation(Device1.class, d1);
-
-        return builder.build();
+                .setTransport(transport)
+                .addAugmentation(new Device1Builder().setDeviceStatus(status).build())
+                .build();
     }
 
     private Device readAndGetDevice(final NodeId nodeId) {
-        return readDevice(nodeId).orNull();
+        return readDevice(nodeId).orElse(null);
     }
 
-    @Nonnull
     private Optional<Device> readDevice(final NodeId nodeId) {
-        ReadOnlyTransaction opTx = dataBroker.newReadOnlyTransaction();
-
-        InstanceIdentifier<Device> deviceIID = buildDeviceInstanceIdentifier(nodeId);
-        ListenableFuture<Optional<Device>> devFuture = opTx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
-        try {
-            return devFuture.get();
+        try (ReadTransaction opTx = dataBroker.newReadOnlyTransaction()) {
+            InstanceIdentifier<Device> deviceIID = buildDeviceInstanceIdentifier(nodeId);
+            return opTx.read(LogicalDatastoreType.OPERATIONAL, deviceIID).get();
         } catch (InterruptedException | ExecutionException e) {
-            return Optional.absent();
+            return Optional.empty();
         }
     }
 
     private void writeDevice(final NodeId nodeId, final Device modifiedDevice) {
-        ReadWriteTransaction opTx = dataBroker.newReadWriteTransaction();
+        WriteTransaction opTx = dataBroker.newWriteOnlyTransaction();
         opTx.merge(LogicalDatastoreType.OPERATIONAL, buildDeviceInstanceIdentifier(nodeId), modifiedDevice);
-        opTx.commit();
+        commit(opTx, modifiedDevice.key());
     }
 
     private static InstanceIdentifier<Device> buildDeviceInstanceIdentifier(final NodeId nodeId) {
         return InstanceIdentifier.create(NetconfCallhomeServer.class)
-                .child(AllowedDevices.class)
-                .child(Device.class, new DeviceKey(nodeId.getValue()));
+            .child(AllowedDevices.class)
+            .child(Device.class, new DeviceKey(nodeId.getValue()));
     }
 
     private static Device withConnectedStatus(final Device opDev) {
-        Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.CONNECTED).build();
-        return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
-                .setSshHostKey(opDev.getSshHostKey()).build();
+        return deviceWithStatus(opDev, Device1.DeviceStatus.CONNECTED);
     }
 
     private static Device withFailedStatus(final Device opDev) {
-        Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILED).build();
-        return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
-                .setSshHostKey(opDev.getSshHostKey()).build();
+        return deviceWithStatus(opDev, DeviceStatus.FAILED);
     }
 
     private static Device withDisconnectedStatus(final Device opDev) {
-        Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
-        return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
-                .setSshHostKey(opDev.getSshHostKey()).build();
+        return deviceWithStatus(opDev, DeviceStatus.DISCONNECTED);
     }
 
     private static Device withFailedAuthStatus(final Device opDev) {
-        Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDAUTHFAILURE).build();
-        return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
-                .setSshHostKey(opDev.getSshHostKey()).build();
+        return deviceWithStatus(opDev, DeviceStatus.FAILEDAUTHFAILURE);
+    }
+
+    private static Device deviceWithStatus(final Device opDev, final DeviceStatus status) {
+        final DeviceBuilder deviceBuilder = new DeviceBuilder()
+            .setUniqueId(opDev.getUniqueId())
+            .addAugmentation(new Device1Builder().setDeviceStatus(status).build());
+        if (opDev.getTransport() != null) {
+            deviceBuilder.setTransport(opDev.getTransport());
+        } else {
+            deviceBuilder.setSshHostKey(opDev.getSshHostKey());
+        }
+        return deviceBuilder.build();
     }
 
     private void setDeviceStatus(final Device device) {
         WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
-        InstanceIdentifier<Device> deviceIId =
-                InstanceIdentifier.create(NetconfCallhomeServer.class)
+        InstanceIdentifier<Device> deviceIId = InstanceIdentifier.create(NetconfCallhomeServer.class)
                         .child(AllowedDevices.class)
                         .child(Device.class, device.key());
 
         tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIId, device);
-        tx.commit();
+        commit(tx, device.key());
+    }
+
+    private static void commit(final WriteTransaction tx, final DeviceKey device) {
+        tx.commit().addCallback(new FutureCallback<CommitInfo>() {
+            @Override
+            public void onSuccess(final CommitInfo result) {
+                LOG.debug("Device {} committed", device);
+            }
+
+            @Override
+            public void onFailure(final Throwable cause) {
+                LOG.warn("Failed to commit device {}", device, cause);
+            }
+        }, MoreExecutors.directExecutor());
     }
 
     private AllowedDevices getDevices() {
-        ReadOnlyTransaction rxTransaction = dataBroker.newReadOnlyTransaction();
-        ListenableFuture<Optional<AllowedDevices>> devicesFuture =
-                rxTransaction.read(LogicalDatastoreType.OPERATIONAL, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
-        try {
-            return devicesFuture.get().orNull();
+        try (ReadTransaction rxTransaction = dataBroker.newReadOnlyTransaction()) {
+            return rxTransaction.read(LogicalDatastoreType.OPERATIONAL, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES)
+                    .get().orElse(null);
         } catch (ExecutionException | InterruptedException e) {
-            LOG.error("Error trying to read the whitelist devices: {}", e);
+            LOG.error("Error trying to read the allowlist devices", e);
             return null;
         }
     }
 
-    private List<Device> getDevicesAsList() {
+    private Collection<Device> getDevicesAsList() {
         AllowedDevices devices = getDevices();
-        return devices == null ? new ArrayList<>() : devices.getDevice();
+        return devices == null ? Collections.emptyList() : devices.nonnullDevice().values();
     }
 
     @Override
     public void reportFailedAuth(final PublicKey sshKey) {
         AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder();
 
-        for (Device device : getDevicesAsList()) {
-            String keyString = device.getSshHostKey();
+        for (final Device device : getDevicesAsList()) {
+            final String keyString;
+            if (device.getTransport() instanceof Ssh) {
+                keyString = ((Ssh) device.getTransport()).getSshClientParams().getHostKey();
+            } else {
+                keyString = device.getSshHostKey();
+            }
+            if (keyString == null) {
+                LOG.info("Allowlist device {} does not have a host key, skipping it", device.getUniqueId());
+                continue;
+            }
 
             try {
                 PublicKey pubKey = decoder.decodePublicKey(keyString);
@@ -309,7 +334,7 @@ class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusReco
             }
         }
 
-        LOG.error("No match found for the failed auth device (should have been filtered by whitelist). Key: {}",
+        LOG.error("No match found for the failed auth device (should have been filtered by allowlist). Key: {}",
                 sshKey);
     }