Merge "BUG-7529: Karaf 4 Migration"
[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 static com.google.common.base.Preconditions.checkArgument;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.base.Optional;
15 import com.google.common.util.concurrent.CheckedFuture;
16 import java.io.IOException;
17 import java.io.File;
18 import java.net.InetSocketAddress;
19 import java.util.ArrayList;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Set;
23 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
24 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
25 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
26 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
27 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
28 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
30 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
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.DataObject;
43 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47
48 public class IetfZeroTouchCallHomeServerProvider implements AutoCloseable, DataChangeListener
49 {
50     private static final String APPNAME = "CallHomeServer";
51     static final InstanceIdentifier<AllowedDevices> ALL_DEVICES = InstanceIdentifier.create(NetconfCallhomeServer.class).child(AllowedDevices.class);
52
53     private static final Logger LOG = LoggerFactory.getLogger(IetfZeroTouchCallHomeServerProvider.class);
54
55     private final DataBroker dataBroker;
56     private final CallHomeMountDispatcher mountDispacher;
57     private CallHomeAuthProviderImpl authProvider ;
58
59     protected NetconfCallHomeServer server;
60
61     private ListenerRegistration<IetfZeroTouchCallHomeServerProvider> listenerReg = null;
62
63     private static final String CALL_HOME_PORT_KEY = "DefaultCallHomePort";
64     private static String configurationPath = "etc"+File.pathSeparator+"ztp-callhome-config.cfg";
65     private int port = 0; // 0 = use default in NetconfCallHomeBuilder
66     private CallhomeStatusReporter statusReporter;
67
68     public IetfZeroTouchCallHomeServerProvider(DataBroker dataBroker, CallHomeMountDispatcher mountDispacher) {
69         this.dataBroker = dataBroker;
70         this.mountDispacher = mountDispacher;
71         this.authProvider = new CallHomeAuthProviderImpl(dataBroker);
72         this.statusReporter = new CallhomeStatusReporter(dataBroker);
73     }
74
75     public void init() {
76         // Register itself as a listener to changes in Devices subtree
77         try {
78             LOG.info("Initializing provider for {}", APPNAME);
79             loadConfigurableValues(configurationPath);
80             initializeServer();
81             dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,ALL_DEVICES,this, AsyncDataBroker.DataChangeScope.SUBTREE);
82             LOG.info( "Initialization complete for {}", APPNAME);
83         }
84         catch(IOException | Configuration.ConfigurationException e) {
85             LOG.error("Unable to successfully initialize", e);
86         }
87     }
88
89     private void loadConfigurableValues(String configurationPath)
90             throws Configuration.ConfigurationException {
91         try {
92             Configuration configuration = new Configuration(configurationPath);
93             port = configuration.getAsPort(CALL_HOME_PORT_KEY);
94         }
95         catch(Configuration.ConfigurationException e) {
96             LOG.error("Problem trying to load configuration values from {}", configurationPath, e);
97         }
98     }
99
100     private CallHomeAuthorizationProvider getCallHomeAuthorization() {
101         return new CallHomeAuthProviderImpl(dataBroker);
102     }
103
104     private void initializeServer() throws IOException {
105         LOG.info( "Initializing Call Home server instance");
106         CallHomeAuthorizationProvider provider = this.getCallHomeAuthorization();
107         NetconfCallHomeServerBuilder builder = new NetconfCallHomeServerBuilder(
108                 provider, mountDispacher, statusReporter);
109         if (port > 0)
110             builder.setBindAddress(new InetSocketAddress(port));
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(Object obj, String description) {
119         if (obj == null)
120             throw new RuntimeException(String.format("Failed to find %s in IetfZeroTouchCallHomeProvider.initialize()", description));
121     }
122
123     @Override
124     public void close() throws Exception {
125         authProvider.close();
126         statusReporter.close();
127
128         // FIXME unbind the server
129         if ( this.listenerReg != null ) {
130             listenerReg.close();
131         }
132         if(server != null ) {
133             server.close();
134         }
135
136         LOG.info("Successfully closed provider for {}", APPNAME);
137     }
138
139     @Override
140     public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
141
142         // In case of any changes to the devices datatree, register the changed values with callhome server
143         // As of now, no way to add a new callhome client key to the CallHomeAuthorization instance since
144         // its created under CallHomeAuthorizationProvider.
145         // Will have to redesign a bit here.
146         // CallHomeAuthorization.
147         ReadOnlyTransaction roConfigTx = dataBroker.newReadOnlyTransaction();
148         CheckedFuture<Optional<AllowedDevices>, ReadFailedException> devicesFuture =
149                 roConfigTx.read(LogicalDatastoreType.CONFIGURATION, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
150
151         if (hasDeletedDevices(change))
152             handleDeletedDevices(change);
153
154         try {
155             for(Device confDevice : getReadDevices(devicesFuture)) {
156                 readAndUpdateStatus(confDevice);
157             }
158         }
159         catch(ReadFailedException e) {
160             LOG.error("Error trying to read the whitelist devices: {}", e);
161         }
162     }
163
164     private boolean hasDeletedDevices(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
165         return change.getRemovedPaths() != null;
166     }
167
168     private void handleDeletedDevices(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
169         checkArgument(change.getRemovedPaths() != null);
170
171         ReadWriteTransaction opTx = dataBroker.newReadWriteTransaction();
172
173         Set<InstanceIdentifier<?>> removedDevices = change.getRemovedPaths();
174         int numRemoved = removedDevices.size();
175
176         Iterator<InstanceIdentifier<?>> iterator = removedDevices.iterator();
177         while(iterator.hasNext()){
178             InstanceIdentifier<?> removedIID = iterator.next();
179             LOG.info("Deleting the entry for callhome device {}",removedIID);
180             opTx.delete(LogicalDatastoreType.OPERATIONAL, removedIID);
181         }
182
183         if (numRemoved > 0)
184             opTx.submit();
185     }
186
187     private List<Device> getReadDevices(CheckedFuture<Optional<AllowedDevices>, ReadFailedException> devicesFuture)
188             throws ReadFailedException {
189         Optional<AllowedDevices> opt = devicesFuture.checkedGet();
190         if (opt.isPresent()) {
191             AllowedDevices confDevices = opt.get();
192             if (confDevices != null) {
193                 LOG.debug("Read {} devices", confDevices.getDevice().size());
194                 return confDevices.getDevice();
195             }
196         }
197
198         LOG.debug("Failed to read devices");
199         return new ArrayList<>();
200     }
201
202     private void readAndUpdateStatus(Device cfgDevice) throws ReadFailedException {
203         InstanceIdentifier<Device> deviceIID  = InstanceIdentifier.create(NetconfCallhomeServer.class)
204                 .child(AllowedDevices.class)
205                 .child(Device.class, new DeviceKey(cfgDevice.getUniqueId()));
206
207         ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
208         CheckedFuture<Optional<Device>, ReadFailedException> deviceFuture = tx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
209
210         Optional<Device> opDevGet = deviceFuture.checkedGet();
211         Device1 devStatus = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
212         if(opDevGet.isPresent()) {
213             Device opDevice = opDevGet.get();
214             devStatus = opDevice.getAugmentation(Device1.class);
215         }
216
217         Device newOpDevice = new DeviceBuilder()
218                 .addAugmentation(Device1.class, devStatus)
219                 .setSshHostKey(cfgDevice.getSshHostKey())
220                 .setUniqueId(cfgDevice.getUniqueId()).build();
221
222         cfgDevice = newOpDevice;
223
224         tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIID, cfgDevice);
225         tx.submit();
226     }
227 }