2 * Copyright (c) 2016 Cisco Systems, Inc. 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.topology.singleton.impl;
10 import static java.util.Objects.requireNonNull;
12 import akka.actor.ActorSystem;
13 import akka.util.Timeout;
14 import com.google.common.annotations.VisibleForTesting;
15 import com.google.common.util.concurrent.FutureCallback;
16 import com.google.common.util.concurrent.MoreExecutors;
17 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
18 import java.time.Duration;
19 import java.util.Collection;
21 import java.util.concurrent.ConcurrentHashMap;
22 import javax.annotation.PreDestroy;
23 import javax.inject.Inject;
24 import javax.inject.Singleton;
25 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
26 import org.opendaylight.controller.cluster.ActorSystemProvider;
27 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
28 import org.opendaylight.mdsal.binding.api.DataBroker;
29 import org.opendaylight.mdsal.binding.api.DataObjectModification;
30 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
31 import org.opendaylight.mdsal.binding.api.DataTreeModification;
32 import org.opendaylight.mdsal.binding.api.RpcProviderService;
33 import org.opendaylight.mdsal.binding.api.WriteTransaction;
34 import org.opendaylight.mdsal.common.api.CommitInfo;
35 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
36 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
37 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
38 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
39 import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier;
40 import org.opendaylight.netconf.client.NetconfClientFactory;
41 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
42 import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
43 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
44 import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
45 import org.opendaylight.netconf.common.NetconfTimer;
46 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologySetup;
47 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologyUtils;
48 import org.opendaylight.netconf.topology.spi.NetconfClientConfigurationBuilderFactory;
49 import org.opendaylight.netconf.topology.spi.NetconfNodeUtils;
50 import org.opendaylight.netconf.topology.spi.NetconfTopologyRPCProvider;
51 import org.opendaylight.netconf.topology.spi.NetconfTopologySchemaAssembler;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev231121.NetconfNode;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
55 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
56 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
57 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
58 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
59 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
60 import org.opendaylight.yangtools.concepts.ListenerRegistration;
61 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
62 import org.opendaylight.yangtools.yang.common.Uint16;
63 import org.osgi.service.component.annotations.Activate;
64 import org.osgi.service.component.annotations.Component;
65 import org.osgi.service.component.annotations.Deactivate;
66 import org.osgi.service.component.annotations.Reference;
67 import org.osgi.service.metatype.annotations.AttributeDefinition;
68 import org.osgi.service.metatype.annotations.Designate;
69 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
74 @Component(service = { }, configurationPid = "org.opendaylight.netconf.topology.singleton")
75 @Designate(ocd = NetconfTopologyManager.Configuration.class)
76 // Non-final for testing
77 public class NetconfTopologyManager implements ClusteredDataTreeChangeListener<Node>, AutoCloseable {
78 @ObjectClassDefinition
79 public @interface Configuration {
80 @AttributeDefinition(min = "1", description = "Name of the Network Topology instance to manage")
81 String topology$_$id() default "topology-netconf";
83 @AttributeDefinition(min = "0", max = "65535",
84 description = "Idle time in seconds after which write transaction is cancelled automatically. If 0, "
85 + "automatic cancellation is turned off.")
86 int write$_$transaction$_$idle$_$timeout() default 0;
89 private static final Logger LOG = LoggerFactory.getLogger(NetconfTopologyManager.class);
91 private final Map<InstanceIdentifier<Node>, NetconfTopologyContext> contexts = new ConcurrentHashMap<>();
92 private final Map<InstanceIdentifier<Node>, ClusterSingletonServiceRegistration>
93 clusterRegistrations = new ConcurrentHashMap<>();
95 private final BaseNetconfSchemas baseSchemas;
96 private final DataBroker dataBroker;
97 private final ClusterSingletonServiceProvider clusterSingletonServiceProvider;
98 private final NetconfTimer timer;
99 private final NetconfTopologySchemaAssembler schemaAssembler;
100 private final ActorSystem actorSystem;
101 private final NetconfClientFactory clientFactory;
102 private final String topologyId;
103 private final Duration writeTxIdleTimeout;
104 private final DOMMountPointService mountPointService;
105 private final DeviceActionFactory deviceActionFactory;
106 private final NetconfClientConfigurationBuilderFactory builderFactory;
107 private final SchemaResourceManager resourceManager;
109 private ListenerRegistration<NetconfTopologyManager> dataChangeListenerRegistration;
110 private NetconfTopologyRPCProvider rpcProvider;
113 public NetconfTopologyManager(@Reference final BaseNetconfSchemas baseSchemas,
114 @Reference final DataBroker dataBroker,
115 @Reference final ClusterSingletonServiceProvider clusterSingletonServiceProvider,
116 @Reference final NetconfTimer timer,
117 @Reference final NetconfTopologySchemaAssembler schemaAssembler,
118 @Reference final ActorSystemProvider actorSystemProvider,
119 @Reference(target = "(type=netconf-client-factory)") final NetconfClientFactory clientFactory,
120 @Reference final DOMMountPointService mountPointService,
121 @Reference final AAAEncryptionService encryptionService,
122 @Reference final RpcProviderService rpcProviderService,
123 @Reference final DeviceActionFactory deviceActionFactory,
124 @Reference final SchemaResourceManager resourceManager,
125 @Reference final NetconfClientConfigurationBuilderFactory builderFactory,
126 final Configuration configuration) {
127 this(baseSchemas, dataBroker, clusterSingletonServiceProvider, timer, schemaAssembler,
128 actorSystemProvider.getActorSystem(), clientFactory, mountPointService, encryptionService,
129 rpcProviderService, deviceActionFactory, resourceManager, builderFactory, configuration.topology$_$id(),
130 Uint16.valueOf(configuration.write$_$transaction$_$idle$_$timeout()));
134 public NetconfTopologyManager(final BaseNetconfSchemas baseSchemas, final DataBroker dataBroker,
135 final ClusterSingletonServiceProvider clusterSingletonServiceProvider, final NetconfTimer timer,
136 final NetconfTopologySchemaAssembler schemaAssembler, final ActorSystemProvider actorSystemProvider,
137 final NetconfClientFactory clientFactory, final DOMMountPointService mountPointService,
138 final AAAEncryptionService encryptionService, final RpcProviderService rpcProviderService,
139 final DeviceActionFactory deviceActionFactory, final SchemaResourceManager resourceManager,
140 final NetconfClientConfigurationBuilderFactory builderFactory) {
141 this(baseSchemas, dataBroker, clusterSingletonServiceProvider, timer, schemaAssembler,
142 actorSystemProvider.getActorSystem(), clientFactory, mountPointService, encryptionService,
143 rpcProviderService, deviceActionFactory, resourceManager, builderFactory,
144 NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, Uint16.ZERO);
147 @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR",
148 justification = "Non-final for mocking, but we register for DTCL and that leaks 'this'")
149 public NetconfTopologyManager(final BaseNetconfSchemas baseSchemas, final DataBroker dataBroker,
150 final ClusterSingletonServiceProvider clusterSingletonServiceProvider, final NetconfTimer timer,
151 final NetconfTopologySchemaAssembler schemaAssembler, final ActorSystem actorSystem,
152 final NetconfClientFactory clientFactory, final DOMMountPointService mountPointService,
153 final AAAEncryptionService encryptionService, final RpcProviderService rpcProviderService,
154 final DeviceActionFactory deviceActionFactory, final SchemaResourceManager resourceManager,
155 final NetconfClientConfigurationBuilderFactory builderFactory, final String topologyId,
156 final Uint16 writeTransactionIdleTimeout) {
157 this.baseSchemas = requireNonNull(baseSchemas);
158 this.dataBroker = requireNonNull(dataBroker);
159 this.clusterSingletonServiceProvider = requireNonNull(clusterSingletonServiceProvider);
160 this.timer = requireNonNull(timer);
161 this.schemaAssembler = requireNonNull(schemaAssembler);
162 this.actorSystem = requireNonNull(actorSystem);
163 this.clientFactory = requireNonNull(clientFactory);
164 this.topologyId = requireNonNull(topologyId);
165 writeTxIdleTimeout = Duration.ofSeconds(writeTransactionIdleTimeout.toJava());
166 this.mountPointService = mountPointService;
167 this.deviceActionFactory = requireNonNull(deviceActionFactory);
168 this.resourceManager = requireNonNull(resourceManager);
169 this.builderFactory = requireNonNull(builderFactory);
171 dataChangeListenerRegistration = registerDataTreeChangeListener();
172 rpcProvider = new NetconfTopologyRPCProvider(rpcProviderService, dataBroker, encryptionService, topologyId);
176 public void onDataTreeChanged(final Collection<DataTreeModification<Node>> changes) {
177 for (final DataTreeModification<Node> change : changes) {
178 final DataObjectModification<Node> rootNode = change.getRootNode();
179 final InstanceIdentifier<Node> dataModifIdent = change.getRootPath().getRootIdentifier();
180 final NodeId nodeId = NetconfTopologyUtils.getNodeId(rootNode.getIdentifier());
181 switch (rootNode.getModificationType()) {
182 case SUBTREE_MODIFIED:
183 LOG.debug("Config for node {} updated", nodeId);
184 refreshNetconfDeviceContext(dataModifIdent, rootNode.getDataAfter());
187 if (contexts.containsKey(dataModifIdent)) {
188 LOG.debug("RemoteDevice{{}} was already configured, reconfiguring node...", nodeId);
189 refreshNetconfDeviceContext(dataModifIdent, rootNode.getDataAfter());
191 LOG.debug("Config for node {} created", nodeId);
192 startNetconfDeviceContext(dataModifIdent, rootNode.getDataAfter());
196 LOG.debug("Config for node {} deleted", nodeId);
197 stopNetconfDeviceContext(dataModifIdent);
200 LOG.warn("Unknown operation for {}.", nodeId);
205 private void refreshNetconfDeviceContext(final InstanceIdentifier<Node> instanceIdentifier, final Node node) {
206 final NetconfTopologyContext context = contexts.get(instanceIdentifier);
207 context.refresh(createSetup(instanceIdentifier, node));
210 // ClusterSingletonServiceRegistration registerClusterSingletonService method throws a Runtime exception if there
211 // are problems with registration and client has to deal with it. Only thing we can do if this error occurs is to
212 // retry registration several times and log the error.
213 // TODO change to a specific documented Exception when changed in ClusterSingletonServiceProvider
214 @SuppressWarnings("checkstyle:IllegalCatch")
215 private void startNetconfDeviceContext(final InstanceIdentifier<Node> instanceIdentifier, final Node node) {
216 final NetconfNode netconfNode = requireNonNull(node.augmentation(NetconfNode.class));
218 final Timeout actorResponseWaitTime = Timeout.create(
219 Duration.ofSeconds(netconfNode.getActorResponseWaitTime().toJava()));
221 final ServiceGroupIdentifier serviceGroupIdent =
222 ServiceGroupIdentifier.create(instanceIdentifier.toString());
224 final NetconfTopologyContext newNetconfTopologyContext = newNetconfTopologyContext(
225 createSetup(instanceIdentifier, node), serviceGroupIdent, actorResponseWaitTime, deviceActionFactory);
230 final ClusterSingletonServiceRegistration clusterSingletonServiceRegistration =
231 clusterSingletonServiceProvider.registerClusterSingletonService(newNetconfTopologyContext);
232 clusterRegistrations.put(instanceIdentifier, clusterSingletonServiceRegistration);
233 contexts.put(instanceIdentifier, newNetconfTopologyContext);
235 } catch (final RuntimeException e) {
236 LOG.warn("Unable to register cluster singleton service {}, trying again", newNetconfTopologyContext, e);
239 LOG.error("Unable to register cluster singleton service {} - done trying, closing topology context",
240 newNetconfTopologyContext, e);
241 close(newNetconfTopologyContext);
248 private void stopNetconfDeviceContext(final InstanceIdentifier<Node> instanceIdentifier) {
249 final NetconfTopologyContext netconfTopologyContext = contexts.remove(instanceIdentifier);
250 if (netconfTopologyContext != null) {
251 close(clusterRegistrations.remove(instanceIdentifier));
252 close(netconfTopologyContext);
257 protected NetconfTopologyContext newNetconfTopologyContext(final NetconfTopologySetup setup,
258 final ServiceGroupIdentifier serviceGroupIdent, final Timeout actorResponseWaitTime,
259 final DeviceActionFactory deviceActionFact) {
260 return new NetconfTopologyContext(resourceManager, mountPointService, builderFactory, deviceActionFactory,
261 actorResponseWaitTime, serviceGroupIdent, setup);
267 public void close() {
268 if (rpcProvider != null) {
272 if (dataChangeListenerRegistration != null) {
273 dataChangeListenerRegistration.close();
274 dataChangeListenerRegistration = null;
277 contexts.values().forEach(NetconfTopologyManager::close);
278 clusterRegistrations.values().forEach(NetconfTopologyManager::close);
281 clusterRegistrations.clear();
284 @SuppressWarnings("checkstyle:IllegalCatch")
285 private static void close(final AutoCloseable closeable) {
288 } catch (Exception e) {
289 LOG.warn("Error closing {}", closeable, e);
293 private ListenerRegistration<NetconfTopologyManager> registerDataTreeChangeListener() {
294 final WriteTransaction wtx = dataBroker.newWriteOnlyTransaction();
295 // FIXME: how does this play out with lifecycle? In a cluster, someone needs to ensure this call happens, but
296 // also we need to to make sure config -> oper is properly synchronized. Non-clustered case relies on
297 // oper being transient and perhaps on a put() instead, how do we handle that in the clustered case?
298 wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
299 .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
300 .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
301 wtx.commit().addCallback(new FutureCallback<CommitInfo>() {
303 public void onSuccess(final CommitInfo result) {
304 LOG.debug("topology initialization successful");
308 public void onFailure(final Throwable throwable) {
309 LOG.error("Unable to initialize netconf-topology", throwable);
311 }, MoreExecutors.directExecutor());
313 LOG.debug("Registering datastore listener");
314 return dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
315 NetconfTopologyUtils.createTopologyListPath(topologyId).child(Node.class)), this);
318 private NetconfTopologySetup createSetup(final InstanceIdentifier<Node> instanceIdentifier, final Node node) {
319 final NetconfNode netconfNode = node.augmentation(NetconfNode.class);
320 final RemoteDeviceId deviceId = NetconfNodeUtils.toRemoteDeviceId(node.getNodeId(), netconfNode);
322 return NetconfTopologySetup.builder()
323 .setClusterSingletonServiceProvider(clusterSingletonServiceProvider)
324 .setBaseSchemas(baseSchemas)
325 .setDataBroker(dataBroker)
326 .setInstanceIdentifier(instanceIdentifier)
328 .setActorSystem(actorSystem)
330 .setSchemaAssembler(schemaAssembler)
331 .setTopologyId(topologyId)
332 .setNetconfClientFactory(clientFactory)
333 .setSchemaResourceDTO(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(), deviceId))
334 .setIdleTimeout(writeTxIdleTimeout)