e26d2dbde8c1f6be87949d11cbb687a704f6f0a0
[netconf.git] / apps / callhome-provider / src / main / java / org / opendaylight / netconf / callhome / mount / IetfZeroTouchCallHomeServerProvider.java
1 /*
2  * Copyright (c) 2016 Brocade Communication Systems and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.callhome.mount;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import io.netty.channel.nio.NioEventLoopGroup;
17 import java.io.IOException;
18 import java.net.InetSocketAddress;
19 import java.util.Collection;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Optional;
23 import java.util.Set;
24 import java.util.concurrent.ExecutionException;
25 import org.opendaylight.mdsal.binding.api.DataBroker;
26 import org.opendaylight.mdsal.binding.api.DataObjectModification;
27 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
28 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
29 import org.opendaylight.mdsal.binding.api.DataTreeModification;
30 import org.opendaylight.mdsal.binding.api.ReadTransaction;
31 import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
32 import org.opendaylight.mdsal.binding.api.WriteTransaction;
33 import org.opendaylight.mdsal.common.api.CommitInfo;
34 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
35 import org.opendaylight.netconf.callhome.protocol.NetconfCallHomeServer;
36 import org.opendaylight.netconf.callhome.protocol.NetconfCallHomeServerBuilder;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.NetconfCallhomeServer;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.AllowedDevices;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.Device;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.Device.DeviceStatus;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.DeviceBuilder;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.DeviceKey;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.device.Transport;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.device.transport.Ssh;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.device.transport.SshBuilder;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.device.transport.Tls;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.device.transport.ssh.SshClientParams;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev230428.netconf.callhome.server.allowed.devices.device.transport.ssh.SshClientParamsBuilder;
49 import org.opendaylight.yangtools.concepts.ListenerRegistration;
50 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
51 import org.opendaylight.yangtools.yang.common.Uint16;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 public final class IetfZeroTouchCallHomeServerProvider
56     implements AutoCloseable, DataTreeChangeListener<AllowedDevices> {
57     private static final String APPNAME = "CallHomeServer";
58     static final InstanceIdentifier<AllowedDevices> ALL_DEVICES = InstanceIdentifier.create(NetconfCallhomeServer.class)
59             .child(AllowedDevices.class);
60
61     private static final Logger LOG = LoggerFactory.getLogger(IetfZeroTouchCallHomeServerProvider.class);
62
63     private final DataBroker dataBroker;
64     private final CallHomeMountDispatcher mountDispacher;
65     private final CallHomeAuthProviderImpl authProvider;
66     private final CallhomeStatusReporter statusReporter;
67     private final int port;
68
69     private NetconfCallHomeServer server;
70     private ListenerRegistration<IetfZeroTouchCallHomeServerProvider> listenerReg = null;
71
72     public IetfZeroTouchCallHomeServerProvider(final DataBroker dataBroker,
73             final CallHomeMountDispatcher mountDispacher) {
74         this(dataBroker, mountDispacher, Uint16.valueOf(4334));
75     }
76
77     public IetfZeroTouchCallHomeServerProvider(final DataBroker dataBroker,
78             final CallHomeMountDispatcher mountDispacher, final Uint16 port) {
79         this.dataBroker = requireNonNull(dataBroker);
80         this.mountDispacher = requireNonNull(mountDispacher);
81
82         LOG.info("Setting port for call home server to {}", port);
83         this.port = port.toJava();
84
85         // FIXME: these should be separate components
86         authProvider = new CallHomeAuthProviderImpl(dataBroker);
87         statusReporter = new CallhomeStatusReporter(dataBroker);
88
89         LOG.info("Initializing provider for {}", APPNAME);
90
91         // Register itself as a listener to changes in Devices subtree
92         try {
93             initializeServer();
94         } catch (IOException | Configuration.ConfigurationException e) {
95             LOG.error("Unable to successfully initialize", e);
96             return;
97         }
98
99         listenerReg = dataBroker.registerDataTreeChangeListener(
100             DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, ALL_DEVICES), this);
101         LOG.info("Initialization complete for {}", APPNAME);
102     }
103
104     private void initializeServer() throws IOException {
105         LOG.info("Initializing Call Home server instance");
106         NetconfCallHomeServerBuilder builder = new NetconfCallHomeServerBuilder(authProvider, mountDispacher,
107             statusReporter);
108         if (port > 0) {
109             builder.setBindAddress(new InetSocketAddress(port));
110         }
111         builder.setNettyGroup(new NioEventLoopGroup());
112         server = builder.build();
113         server.bind();
114         mountDispacher.createTopology();
115         LOG.info("Initialization complete for Call Home server instance");
116     }
117
118     @VisibleForTesting
119     void assertValid(final Object obj, final String description) {
120         if (obj == null) {
121             throw new IllegalStateException(
122                 "Failed to find " + description + " in IetfZeroTouchCallHomeProvider.initialize()");
123         }
124     }
125
126     @Override
127     public void close() {
128         authProvider.close();
129         statusReporter.close();
130
131         // FIXME unbind the server
132         if (listenerReg != null) {
133             listenerReg.close();
134         }
135         if (server != null) {
136             server.close();
137         }
138
139         LOG.info("Successfully closed provider for {}", APPNAME);
140     }
141
142     @Override
143     public void onDataTreeChanged(final Collection<DataTreeModification<AllowedDevices>> changes) {
144         // In case of any changes to the devices datatree, register the changed values with callhome server
145         // As of now, no way to add a new callhome client key to the CallHomeAuthorization instance since
146         // its created under CallHomeAuthorizationProvider.
147         // Will have to redesign a bit here.
148         // CallHomeAuthorization.
149         final ListenableFuture<Optional<AllowedDevices>> devicesFuture;
150         try (ReadTransaction roConfigTx = dataBroker.newReadOnlyTransaction()) {
151             devicesFuture = roConfigTx.read(LogicalDatastoreType.CONFIGURATION,
152                 IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
153         }
154
155         Set<InstanceIdentifier<?>> deletedDevices = new HashSet<>();
156         for (DataTreeModification<AllowedDevices> change : changes) {
157             DataObjectModification<AllowedDevices> rootNode = change.getRootNode();
158             switch (rootNode.getModificationType()) {
159                 case DELETE:
160                     deletedDevices.add(change.getRootPath().getRootIdentifier());
161                     break;
162                 default:
163                     break;
164             }
165         }
166
167         handleDeletedDevices(deletedDevices);
168
169         try {
170             for (Device confDevice : getReadDevices(devicesFuture)) {
171                 readAndUpdateStatus(confDevice);
172             }
173         } catch (ExecutionException | InterruptedException e) {
174             LOG.error("Error trying to read the whitelist devices", e);
175         }
176     }
177
178     private void handleDeletedDevices(final Set<InstanceIdentifier<?>> deletedDevices) {
179         if (deletedDevices.isEmpty()) {
180             return;
181         }
182
183         WriteTransaction opTx = dataBroker.newWriteOnlyTransaction();
184
185         for (InstanceIdentifier<?> removedIID : deletedDevices) {
186             LOG.info("Deleting the entry for callhome device {}", removedIID);
187             opTx.delete(LogicalDatastoreType.OPERATIONAL, removedIID);
188         }
189
190         opTx.commit().addCallback(new FutureCallback<CommitInfo>() {
191             @Override
192             public void onSuccess(final CommitInfo result) {
193                 LOG.debug("Device deletions committed");
194             }
195
196             @Override
197             public void onFailure(final Throwable cause) {
198                 LOG.warn("Failed to commit device deletions", cause);
199             }
200         }, MoreExecutors.directExecutor());
201     }
202
203     private static Collection<Device> getReadDevices(final ListenableFuture<Optional<AllowedDevices>> devicesFuture)
204             throws InterruptedException, ExecutionException {
205         return devicesFuture.get().map(AllowedDevices::nonnullDevice).orElse(Map.of()).values();
206     }
207
208     private void readAndUpdateStatus(final Device cfgDevice) throws InterruptedException, ExecutionException {
209         InstanceIdentifier<Device> deviceIID = InstanceIdentifier.create(NetconfCallhomeServer.class)
210                 .child(AllowedDevices.class).child(Device.class, new DeviceKey(cfgDevice.getUniqueId()));
211
212         ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
213         ListenableFuture<Optional<Device>> deviceFuture = tx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
214
215         final DeviceStatus devStatus = deviceFuture.get()
216             .map(Device::getDeviceStatus)
217             .orElse(DeviceStatus.DISCONNECTED);
218
219         final Device opDevice = createOperationalDevice(cfgDevice, devStatus);
220         tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIID, opDevice);
221         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
222             @Override
223             public void onSuccess(final CommitInfo result) {
224                 LOG.debug("Device {} status update committed", cfgDevice.key());
225             }
226
227             @Override
228             public void onFailure(final Throwable cause) {
229                 LOG.warn("Failed to commit device {} status update", cfgDevice.key(), cause);
230             }
231         }, MoreExecutors.directExecutor());
232     }
233
234     private static Device createOperationalDevice(final Device cfgDevice, final DeviceStatus devStatus) {
235         final DeviceBuilder deviceBuilder = new DeviceBuilder()
236             .setUniqueId(cfgDevice.getUniqueId())
237             .setDeviceStatus(devStatus);
238         if (cfgDevice.getTransport() instanceof Ssh ssh) {
239             final String hostKey = ssh.getSshClientParams().getHostKey();
240             final SshClientParams params = new SshClientParamsBuilder().setHostKey(hostKey).build();
241             final Transport sshTransport = new SshBuilder().setSshClientParams(params).build();
242             deviceBuilder.setTransport(sshTransport);
243         } else if (cfgDevice.getTransport() instanceof Tls) {
244             deviceBuilder.setTransport(cfgDevice.getTransport());
245         } else if (cfgDevice.getSshHostKey() != null) {
246             deviceBuilder.setSshHostKey(cfgDevice.getSshHostKey());
247         }
248         return deviceBuilder.build();
249     }
250 }