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