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