Merge "BUG-7529: Karaf 4 Migration"
[netconf.git] / netconf / callhome-provider / src / main / java / org / opendaylight / netconf / callhome / mount / IetfZeroTouchCallHomeServerProvider.java
index 26373359e351b820db8af18fe397f32dcf17bf2f..bcc5707b140668d20982b802db0334739337b116 100644 (file)
 
 package org.opendaylight.netconf.callhome.mount;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
 import java.io.IOException;
 import java.io.File;
 import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorizationProvider;
 import org.opendaylight.netconf.callhome.protocol.NetconfCallHomeServer;
 import org.opendaylight.netconf.callhome.protocol.NetconfCallHomeServerBuilder;
+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.Device1Builder;
+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.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class IetfZeroTouchCallHomeServerProvider implements AutoCloseable {
+
+public class IetfZeroTouchCallHomeServerProvider implements AutoCloseable, DataChangeListener
+{
+    private static final String APPNAME = "CallHomeServer";
+    static final InstanceIdentifier<AllowedDevices> ALL_DEVICES = InstanceIdentifier.create(NetconfCallhomeServer.class).child(AllowedDevices.class);
+
     private static final Logger LOG = LoggerFactory.getLogger(IetfZeroTouchCallHomeServerProvider.class);
-    private static final String appName = "CallHomeServer";
 
     private final DataBroker dataBroker;
     private final CallHomeMountDispatcher mountDispacher;
+    private CallHomeAuthProviderImpl authProvider ;
 
     protected NetconfCallHomeServer server;
 
+    private ListenerRegistration<IetfZeroTouchCallHomeServerProvider> listenerReg = null;
 
     private static final String CALL_HOME_PORT_KEY = "DefaultCallHomePort";
-
-    private final CallHomeAuthProviderImpl authProvider;
-
-
-    static String configurationPath = "etc"+File.pathSeparator+"ztp-callhome-config.cfg";
-
+    private static String configurationPath = "etc"+File.pathSeparator+"ztp-callhome-config.cfg";
     private int port = 0; // 0 = use default in NetconfCallHomeBuilder
+    private CallhomeStatusReporter statusReporter;
 
     public IetfZeroTouchCallHomeServerProvider(DataBroker dataBroker, CallHomeMountDispatcher mountDispacher) {
         this.dataBroker = dataBroker;
         this.mountDispacher = mountDispacher;
         this.authProvider = new CallHomeAuthProviderImpl(dataBroker);
+        this.statusReporter = new CallhomeStatusReporter(dataBroker);
     }
 
     public void init() {
         // Register itself as a listener to changes in Devices subtree
         try {
-            LOG.info("Initializing provider for {}", appName);
+            LOG.info("Initializing provider for {}", APPNAME);
             loadConfigurableValues(configurationPath);
             initializeServer();
-            LOG.info("Initialization complete for {}", appName);
-        } catch (IndexOutOfBoundsException | Configuration.ConfigurationException e) {
+            dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,ALL_DEVICES,this, AsyncDataBroker.DataChangeScope.SUBTREE);
+            LOG.info( "Initialization complete for {}", APPNAME);
+        }
+        catch(IOException | Configuration.ConfigurationException e) {
             LOG.error("Unable to successfully initialize", e);
         }
     }
 
-    void loadConfigurableValues(String configurationPath) throws Configuration.ConfigurationException {
+    private void loadConfigurableValues(String configurationPath)
+            throws Configuration.ConfigurationException {
         try {
             Configuration configuration = new Configuration(configurationPath);
             port = configuration.getAsPort(CALL_HOME_PORT_KEY);
-        } catch (Exception e) {
+        }
+        catch(Configuration.ConfigurationException e) {
             LOG.error("Problem trying to load configuration values from {}", configurationPath, e);
         }
     }
 
     private CallHomeAuthorizationProvider getCallHomeAuthorization() {
-        return authProvider;
+        return new CallHomeAuthProviderImpl(dataBroker);
     }
 
-    private void initializeServer() {
-        LOG.info("Initializing Call Home server instance");
-        CallHomeAuthorizationProvider auth =  getCallHomeAuthorization();
-        NetconfCallHomeServerBuilder builder = new NetconfCallHomeServerBuilder(auth, mountDispacher);
-
+    private void initializeServer() throws IOException {
+        LOG.info( "Initializing Call Home server instance");
+        CallHomeAuthorizationProvider provider = this.getCallHomeAuthorization();
+        NetconfCallHomeServerBuilder builder = new NetconfCallHomeServerBuilder(
+                provider, mountDispacher, statusReporter);
         if (port > 0)
             builder.setBindAddress(new InetSocketAddress(port));
         server = builder.build();
-        try {
-            server.bind();
-            mountDispacher.createTopology();
-            LOG.info("Initialization complete for Call Home server instance");
-        } catch (IOException e) {
-            throw new IllegalStateException("Unable to create Call Home Server.",e);
-        }
+        server.bind();
+        mountDispacher.createTopology();
+        LOG.info( "Initialization complete for Call Home server instance");
+    }
+
+    @VisibleForTesting
+    void assertValid(Object obj, String description) {
+        if (obj == null)
+            throw new RuntimeException(String.format("Failed to find %s in IetfZeroTouchCallHomeProvider.initialize()", description));
     }
 
     @Override
     public void close() throws Exception {
         authProvider.close();
-        if (server != null) {
+        statusReporter.close();
+
+        // FIXME unbind the server
+        if ( this.listenerReg != null ) {
+            listenerReg.close();
+        }
+        if(server != null ) {
             server.close();
         }
 
-        LOG.info("Successfully closed provider for {}", appName);
+        LOG.info("Successfully closed provider for {}", APPNAME);
     }
 
+    @Override
+    public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
+
+        // In case of any changes to the devices datatree, register the changed values with callhome server
+        // As of now, no way to add a new callhome client key to the CallHomeAuthorization instance since
+        // its created under CallHomeAuthorizationProvider.
+        // Will have to redesign a bit here.
+        // CallHomeAuthorization.
+        ReadOnlyTransaction roConfigTx = dataBroker.newReadOnlyTransaction();
+        CheckedFuture<Optional<AllowedDevices>, ReadFailedException> devicesFuture =
+                roConfigTx.read(LogicalDatastoreType.CONFIGURATION, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
+
+        if (hasDeletedDevices(change))
+            handleDeletedDevices(change);
+
+        try {
+            for(Device confDevice : getReadDevices(devicesFuture)) {
+                readAndUpdateStatus(confDevice);
+            }
+        }
+        catch(ReadFailedException e) {
+            LOG.error("Error trying to read the whitelist devices: {}", e);
+        }
+    }
+
+    private boolean hasDeletedDevices(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
+        return change.getRemovedPaths() != null;
+    }
+
+    private void handleDeletedDevices(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
+        checkArgument(change.getRemovedPaths() != null);
+
+        ReadWriteTransaction opTx = dataBroker.newReadWriteTransaction();
+
+        Set<InstanceIdentifier<?>> removedDevices = change.getRemovedPaths();
+        int numRemoved = removedDevices.size();
+
+        Iterator<InstanceIdentifier<?>> iterator = removedDevices.iterator();
+        while(iterator.hasNext()){
+            InstanceIdentifier<?> removedIID = iterator.next();
+            LOG.info("Deleting the entry for callhome device {}",removedIID);
+            opTx.delete(LogicalDatastoreType.OPERATIONAL, removedIID);
+        }
+
+        if (numRemoved > 0)
+            opTx.submit();
+    }
+
+    private List<Device> getReadDevices(CheckedFuture<Optional<AllowedDevices>, ReadFailedException> devicesFuture)
+            throws ReadFailedException {
+        Optional<AllowedDevices> opt = devicesFuture.checkedGet();
+        if (opt.isPresent()) {
+            AllowedDevices confDevices = opt.get();
+            if (confDevices != null) {
+                LOG.debug("Read {} devices", confDevices.getDevice().size());
+                return confDevices.getDevice();
+            }
+        }
+
+        LOG.debug("Failed to read devices");
+        return new ArrayList<>();
+    }
+
+    private void readAndUpdateStatus(Device cfgDevice) throws ReadFailedException {
+        InstanceIdentifier<Device> deviceIID  = InstanceIdentifier.create(NetconfCallhomeServer.class)
+                .child(AllowedDevices.class)
+                .child(Device.class, new DeviceKey(cfgDevice.getUniqueId()));
+
+        ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
+        CheckedFuture<Optional<Device>, ReadFailedException> deviceFuture = tx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
+
+        Optional<Device> opDevGet = deviceFuture.checkedGet();
+        Device1 devStatus = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
+        if(opDevGet.isPresent()) {
+            Device opDevice = opDevGet.get();
+            devStatus = opDevice.getAugmentation(Device1.class);
+        }
+
+        Device newOpDevice = new DeviceBuilder()
+                .addAugmentation(Device1.class, devStatus)
+                .setSshHostKey(cfgDevice.getSshHostKey())
+                .setUniqueId(cfgDevice.getUniqueId()).build();
+
+        cfgDevice = newOpDevice;
+
+        tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIID, cfgDevice);
+        tx.submit();
+    }
 }