2 * Copyright (c) 2016 Brocade Communication Systems and others. All rights reserved.
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
8 package org.opendaylight.netconf.callhome.mount;
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;
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.rev161109.NetconfCallhomeServer;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceBuilder;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceKey;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.Transport;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.transport.Ssh;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.transport.SshBuilder;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.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.rev161109.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.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
53 public class IetfZeroTouchCallHomeServerProvider implements AutoCloseable, DataTreeChangeListener<AllowedDevices> {
54 private static final String APPNAME = "CallHomeServer";
55 static final InstanceIdentifier<AllowedDevices> ALL_DEVICES = InstanceIdentifier.create(NetconfCallhomeServer.class)
56 .child(AllowedDevices.class);
58 private static final Logger LOG = LoggerFactory.getLogger(IetfZeroTouchCallHomeServerProvider.class);
60 private final DataBroker dataBroker;
61 private final CallHomeMountDispatcher mountDispacher;
62 private final CallHomeAuthProviderImpl authProvider;
64 protected NetconfCallHomeServer server;
66 private ListenerRegistration<IetfZeroTouchCallHomeServerProvider> listenerReg = null;
68 private static final String CALL_HOME_PORT_KEY = "DefaultCallHomePort";
69 private int port = 0; // 0 = use default in NetconfCallHomeBuilder
70 private final CallhomeStatusReporter statusReporter;
72 public IetfZeroTouchCallHomeServerProvider(final DataBroker dataBroker,
73 final CallHomeMountDispatcher mountDispacher) {
74 this.dataBroker = dataBroker;
75 this.mountDispacher = mountDispacher;
76 this.authProvider = new CallHomeAuthProviderImpl(dataBroker);
77 this.statusReporter = new CallhomeStatusReporter(dataBroker);
81 // Register itself as a listener to changes in Devices subtree
83 LOG.info("Initializing provider for {}", APPNAME);
85 listenerReg = dataBroker.registerDataTreeChangeListener(
86 DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, ALL_DEVICES), this);
87 LOG.info("Initialization complete for {}", APPNAME);
88 } catch (IOException | Configuration.ConfigurationException e) {
89 LOG.error("Unable to successfully initialize", e);
93 public void setPort(final String portStr) {
95 Configuration configuration = new Configuration();
96 configuration.set(CALL_HOME_PORT_KEY, portStr);
97 port = configuration.getAsPort(CALL_HOME_PORT_KEY);
98 LOG.info("Setting port for call home server to {}", portStr);
99 } catch (Configuration.ConfigurationException e) {
100 LOG.error("Problem trying to set port for call home server {}", portStr, e);
104 private CallHomeAuthorizationProvider getCallHomeAuthorization() {
105 return new CallHomeAuthProviderImpl(dataBroker);
108 private void initializeServer() throws IOException {
109 LOG.info("Initializing Call Home server instance");
110 CallHomeAuthorizationProvider provider = this.getCallHomeAuthorization();
111 NetconfCallHomeServerBuilder builder = new NetconfCallHomeServerBuilder(provider, mountDispacher,
114 builder.setBindAddress(new InetSocketAddress(port));
116 builder.setNettyGroup(new NioEventLoopGroup());
117 server = builder.build();
119 mountDispacher.createTopology();
120 LOG.info("Initialization complete for Call Home server instance");
124 void assertValid(final Object obj, final String description) {
126 throw new RuntimeException(
127 String.format("Failed to find %s in IetfZeroTouchCallHomeProvider.initialize()", description));
132 public void close() {
133 authProvider.close();
134 statusReporter.close();
136 // FIXME unbind the server
137 if (this.listenerReg != null) {
140 if (server != null) {
144 LOG.info("Successfully closed provider for {}", APPNAME);
148 public void onDataTreeChanged(final Collection<DataTreeModification<AllowedDevices>> changes) {
149 // In case of any changes to the devices datatree, register the changed values with callhome server
150 // As of now, no way to add a new callhome client key to the CallHomeAuthorization instance since
151 // its created under CallHomeAuthorizationProvider.
152 // Will have to redesign a bit here.
153 // CallHomeAuthorization.
154 final ListenableFuture<Optional<AllowedDevices>> devicesFuture;
155 try (ReadTransaction roConfigTx = dataBroker.newReadOnlyTransaction()) {
156 devicesFuture = roConfigTx.read(LogicalDatastoreType.CONFIGURATION,
157 IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
160 Set<InstanceIdentifier<?>> deletedDevices = new HashSet<>();
161 for (DataTreeModification<AllowedDevices> change : changes) {
162 DataObjectModification<AllowedDevices> rootNode = change.getRootNode();
163 switch (rootNode.getModificationType()) {
165 deletedDevices.add(change.getRootPath().getRootIdentifier());
172 handleDeletedDevices(deletedDevices);
175 for (Device confDevice : getReadDevices(devicesFuture)) {
176 readAndUpdateStatus(confDevice);
178 } catch (ExecutionException | InterruptedException e) {
179 LOG.error("Error trying to read the whitelist devices", e);
183 private void handleDeletedDevices(final Set<InstanceIdentifier<?>> deletedDevices) {
184 if (deletedDevices.isEmpty()) {
188 WriteTransaction opTx = dataBroker.newWriteOnlyTransaction();
190 for (InstanceIdentifier<?> removedIID : deletedDevices) {
191 LOG.info("Deleting the entry for callhome device {}", removedIID);
192 opTx.delete(LogicalDatastoreType.OPERATIONAL, removedIID);
195 opTx.commit().addCallback(new FutureCallback<CommitInfo>() {
197 public void onSuccess(final CommitInfo result) {
198 LOG.debug("Device deletions committed");
202 public void onFailure(final Throwable cause) {
203 LOG.warn("Failed to commit device deletions", cause);
205 }, MoreExecutors.directExecutor());
208 private static Collection<Device> getReadDevices(final ListenableFuture<Optional<AllowedDevices>> devicesFuture)
209 throws InterruptedException, ExecutionException {
210 return devicesFuture.get().map(AllowedDevices::nonnullDevice).orElse(Collections.emptyMap()).values();
213 private void readAndUpdateStatus(final Device cfgDevice) throws InterruptedException, ExecutionException {
214 InstanceIdentifier<Device> deviceIID = InstanceIdentifier.create(NetconfCallhomeServer.class)
215 .child(AllowedDevices.class).child(Device.class, new DeviceKey(cfgDevice.getUniqueId()));
217 ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
218 ListenableFuture<Optional<Device>> deviceFuture = tx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
220 final Device1 devStatus;
221 Optional<Device> opDevGet = deviceFuture.get();
222 if (opDevGet.isPresent()) {
223 devStatus = opDevGet.get().augmentation(Device1.class);
225 devStatus = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
228 final Device opDevice = createOperationalDevice(cfgDevice, devStatus);
229 tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIID, opDevice);
230 tx.commit().addCallback(new FutureCallback<CommitInfo>() {
232 public void onSuccess(final CommitInfo result) {
233 LOG.debug("Device {} status update committed", cfgDevice.key());
237 public void onFailure(final Throwable cause) {
238 LOG.warn("Failed to commit device {} status update", cfgDevice.key(), cause);
240 }, MoreExecutors.directExecutor());
243 private Device createOperationalDevice(final Device cfgDevice, final Device1 devStatus) {
244 final DeviceBuilder deviceBuilder = new DeviceBuilder()
245 .addAugmentation(devStatus)
246 .setUniqueId(cfgDevice.getUniqueId());
247 if (cfgDevice.getTransport() instanceof Ssh) {
248 final String hostKey = ((Ssh) cfgDevice.getTransport()).getSshClientParams().getHostKey();
249 final SshClientParams params = new SshClientParamsBuilder().setHostKey(hostKey).build();
250 final Transport sshTransport = new SshBuilder().setSshClientParams(params).build();
251 deviceBuilder.setTransport(sshTransport);
252 } else if (cfgDevice.getSshHostKey() != null) {
253 deviceBuilder.setSshHostKey(cfgDevice.getSshHostKey());
255 return deviceBuilder.build();