e8b96e671b47b724bd05d35ab061db9e476fb6b5
[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
9 package org.opendaylight.netconf.callhome.mount;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Optional;
13 import com.google.common.util.concurrent.ListenableFuture;
14 import java.io.IOException;
15 import java.net.InetSocketAddress;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.concurrent.ExecutionException;
22 import javax.annotation.Nonnull;
23 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
24 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
25 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
26 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
27 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
28 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
29 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
30 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
31 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorizationProvider;
32 import org.opendaylight.netconf.callhome.protocol.NetconfCallHomeServer;
33 import org.opendaylight.netconf.callhome.protocol.NetconfCallHomeServerBuilder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1Builder;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceBuilder;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceKey;
41 import org.opendaylight.yangtools.concepts.ListenerRegistration;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 public class IetfZeroTouchCallHomeServerProvider implements AutoCloseable, DataTreeChangeListener<AllowedDevices> {
47     private static final String APPNAME = "CallHomeServer";
48     static final InstanceIdentifier<AllowedDevices> ALL_DEVICES = InstanceIdentifier.create(NetconfCallhomeServer.class)
49             .child(AllowedDevices.class);
50
51     private static final Logger LOG = LoggerFactory.getLogger(IetfZeroTouchCallHomeServerProvider.class);
52
53     private final DataBroker dataBroker;
54     private final CallHomeMountDispatcher mountDispacher;
55     private final CallHomeAuthProviderImpl authProvider;
56
57     protected NetconfCallHomeServer server;
58
59     private ListenerRegistration<IetfZeroTouchCallHomeServerProvider> listenerReg = null;
60
61     private static final String CALL_HOME_PORT_KEY = "DefaultCallHomePort";
62     private int port = 0; // 0 = use default in NetconfCallHomeBuilder
63     private final CallhomeStatusReporter statusReporter;
64
65     public IetfZeroTouchCallHomeServerProvider(DataBroker dataBroker, CallHomeMountDispatcher mountDispacher) {
66         this.dataBroker = dataBroker;
67         this.mountDispacher = mountDispacher;
68         this.authProvider = new CallHomeAuthProviderImpl(dataBroker);
69         this.statusReporter = new CallhomeStatusReporter(dataBroker);
70     }
71
72     public void init() {
73         // Register itself as a listener to changes in Devices subtree
74         try {
75             LOG.info("Initializing provider for {}", APPNAME);
76             initializeServer();
77             listenerReg = dataBroker.registerDataTreeChangeListener(
78                     new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, ALL_DEVICES), this);
79             LOG.info("Initialization complete for {}", APPNAME);
80         } catch (IOException | Configuration.ConfigurationException e) {
81             LOG.error("Unable to successfully initialize", e);
82         }
83     }
84
85     public void setPort(String portStr) {
86         try {
87             Configuration configuration = new Configuration();
88             configuration.set(CALL_HOME_PORT_KEY, portStr);
89             port = configuration.getAsPort(CALL_HOME_PORT_KEY);
90             LOG.info("Setting port for call home server to {}", portStr);
91         } catch (Configuration.ConfigurationException e) {
92             LOG.error("Problem trying to set port for call home server {}", portStr, e);
93         }
94     }
95
96     private CallHomeAuthorizationProvider getCallHomeAuthorization() {
97         return new CallHomeAuthProviderImpl(dataBroker);
98     }
99
100     private void initializeServer() throws IOException {
101         LOG.info("Initializing Call Home server instance");
102         CallHomeAuthorizationProvider provider = this.getCallHomeAuthorization();
103         NetconfCallHomeServerBuilder builder = new NetconfCallHomeServerBuilder(provider, mountDispacher,
104                                                                                 statusReporter);
105         if (port > 0) {
106             builder.setBindAddress(new InetSocketAddress(port));
107         }
108         server = builder.build();
109         server.bind();
110         mountDispacher.createTopology();
111         LOG.info("Initialization complete for Call Home server instance");
112     }
113
114     @VisibleForTesting
115     void assertValid(Object obj, String description) {
116         if (obj == null) {
117             throw new RuntimeException(
118                     String.format("Failed to find %s in IetfZeroTouchCallHomeProvider.initialize()", description));
119         }
120     }
121
122     @Override
123     public void close() throws Exception {
124         authProvider.close();
125         statusReporter.close();
126
127         // FIXME unbind the server
128         if (this.listenerReg != null) {
129             listenerReg.close();
130         }
131         if (server != null) {
132             server.close();
133         }
134
135         LOG.info("Successfully closed provider for {}", APPNAME);
136     }
137
138     @Override
139     public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<AllowedDevices>> changes) {
140         // In case of any changes to the devices datatree, register the changed values with callhome server
141         // As of now, no way to add a new callhome client key to the CallHomeAuthorization instance since
142         // its created under CallHomeAuthorizationProvider.
143         // Will have to redesign a bit here.
144         // CallHomeAuthorization.
145         ReadOnlyTransaction roConfigTx = dataBroker.newReadOnlyTransaction();
146         ListenableFuture<Optional<AllowedDevices>> devicesFuture = roConfigTx
147                 .read(LogicalDatastoreType.CONFIGURATION, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
148
149         Set<InstanceIdentifier<?>> deletedDevices = new HashSet<>();
150         for (DataTreeModification<AllowedDevices> change : changes) {
151             DataObjectModification<AllowedDevices> rootNode = change.getRootNode();
152             switch (rootNode.getModificationType()) {
153                 case DELETE:
154                     deletedDevices.add(change.getRootPath().getRootIdentifier());
155                     break;
156                 default:
157                     break;
158             }
159         }
160
161         handleDeletedDevices(deletedDevices);
162
163         try {
164             for (Device confDevice : getReadDevices(devicesFuture)) {
165                 readAndUpdateStatus(confDevice);
166             }
167         } catch (ExecutionException | InterruptedException e) {
168             LOG.error("Error trying to read the whitelist devices: {}", e);
169         }
170     }
171
172     private void handleDeletedDevices(Set<InstanceIdentifier<?>> deletedDevices) {
173         if (deletedDevices.isEmpty()) {
174             return;
175         }
176
177         ReadWriteTransaction opTx = dataBroker.newReadWriteTransaction();
178
179         int numRemoved = deletedDevices.size();
180
181         for (InstanceIdentifier<?> removedIID : deletedDevices) {
182             LOG.info("Deleting the entry for callhome device {}", removedIID);
183             opTx.delete(LogicalDatastoreType.OPERATIONAL, removedIID);
184         }
185
186         if (numRemoved > 0) {
187             opTx.submit();
188         }
189     }
190
191     private List<Device> getReadDevices(
192             ListenableFuture<Optional<AllowedDevices>> devicesFuture) throws InterruptedException, ExecutionException {
193         Optional<AllowedDevices> opt = devicesFuture.get();
194         return opt.isPresent() ? opt.get().getDevice() : Collections.emptyList();
195     }
196
197     private void readAndUpdateStatus(Device cfgDevice) throws InterruptedException, ExecutionException {
198         InstanceIdentifier<Device> deviceIID = InstanceIdentifier.create(NetconfCallhomeServer.class)
199                 .child(AllowedDevices.class).child(Device.class, new DeviceKey(cfgDevice.getUniqueId()));
200
201         ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
202         ListenableFuture<Optional<Device>> deviceFuture = tx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
203
204         Optional<Device> opDevGet = deviceFuture.get();
205         Device1 devStatus = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
206         if (opDevGet.isPresent()) {
207             Device opDevice = opDevGet.get();
208             devStatus = opDevice.getAugmentation(Device1.class);
209         }
210
211         cfgDevice = new DeviceBuilder().addAugmentation(Device1.class, devStatus)
212                 .setSshHostKey(cfgDevice.getSshHostKey()).setUniqueId(cfgDevice.getUniqueId()).build();
213
214         tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIID, cfgDevice);
215         tx.submit();
216     }
217 }