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