Eliminate SchemaResourcesDTO
[netconf.git] / apps / netconf-topology-singleton / src / main / java / org / opendaylight / netconf / topology / singleton / impl / NetconfTopologyManager.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. 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 package org.opendaylight.netconf.topology.singleton.impl;
9
10 import static java.util.Objects.requireNonNull;
11
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.List;
20 import java.util.Map;
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.DataBroker;
28 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
29 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
30 import org.opendaylight.mdsal.binding.api.DataTreeModification;
31 import org.opendaylight.mdsal.binding.api.RpcProviderService;
32 import org.opendaylight.mdsal.binding.api.WriteTransaction;
33 import org.opendaylight.mdsal.common.api.CommitInfo;
34 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
35 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
36 import org.opendaylight.mdsal.singleton.api.ClusterSingletonServiceProvider;
37 import org.opendaylight.mdsal.singleton.api.ServiceGroupIdentifier;
38 import org.opendaylight.netconf.client.NetconfClientFactory;
39 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
40 import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
41 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
42 import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
43 import org.opendaylight.netconf.common.NetconfTimer;
44 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologySetup;
45 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologyUtils;
46 import org.opendaylight.netconf.topology.spi.NetconfClientConfigurationBuilderFactory;
47 import org.opendaylight.netconf.topology.spi.NetconfNodeUtils;
48 import org.opendaylight.netconf.topology.spi.NetconfTopologyRPCProvider;
49 import org.opendaylight.netconf.topology.spi.NetconfTopologySchemaAssembler;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev231121.NetconfNode;
51 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
52 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
55 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
56 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
57 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
58 import org.opendaylight.yangtools.concepts.Registration;
59 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
60 import org.opendaylight.yangtools.yang.common.Uint16;
61 import org.osgi.service.component.annotations.Activate;
62 import org.osgi.service.component.annotations.Component;
63 import org.osgi.service.component.annotations.Deactivate;
64 import org.osgi.service.component.annotations.Reference;
65 import org.osgi.service.metatype.annotations.AttributeDefinition;
66 import org.osgi.service.metatype.annotations.Designate;
67 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 @Singleton
72 @Component(service = { }, configurationPid = "org.opendaylight.netconf.topology.singleton")
73 @Designate(ocd = NetconfTopologyManager.Configuration.class)
74 // Non-final for testing
75 public class NetconfTopologyManager implements DataTreeChangeListener<Node>, AutoCloseable {
76     @ObjectClassDefinition
77     public @interface Configuration {
78         @AttributeDefinition(min = "1", description = "Name of the Network Topology instance to manage")
79         String topology$_$id() default "topology-netconf";
80
81         @AttributeDefinition(min = "0", max = "65535",
82             description = "Idle time in seconds after which write transaction is cancelled automatically. If 0, "
83                 + "automatic cancellation is turned off.")
84         int write$_$transaction$_$idle$_$timeout() default 0;
85     }
86
87     private static final Logger LOG = LoggerFactory.getLogger(NetconfTopologyManager.class);
88
89     private final Map<InstanceIdentifier<Node>, NetconfTopologyContext> contexts = new ConcurrentHashMap<>();
90     private final Map<InstanceIdentifier<Node>, Registration> clusterRegistrations = new ConcurrentHashMap<>();
91
92     private final BaseNetconfSchemaProvider baseSchemaProvider;
93     private final DataBroker dataBroker;
94     private final ClusterSingletonServiceProvider clusterSingletonServiceProvider;
95     private final NetconfTimer timer;
96     private final NetconfTopologySchemaAssembler schemaAssembler;
97     private final ActorSystem actorSystem;
98     private final NetconfClientFactory clientFactory;
99     private final String topologyId;
100     private final Duration writeTxIdleTimeout;
101     private final DOMMountPointService mountPointService;
102     private final DeviceActionFactory deviceActionFactory;
103     private final NetconfClientConfigurationBuilderFactory builderFactory;
104     private final SchemaResourceManager resourceManager;
105
106     private Registration dataChangeListenerRegistration;
107     private NetconfTopologyRPCProvider rpcProvider;
108
109     @Activate
110     public NetconfTopologyManager(@Reference final BaseNetconfSchemaProvider baseSchemaProvider,
111             @Reference final DataBroker dataBroker,
112             @Reference final ClusterSingletonServiceProvider clusterSingletonServiceProvider,
113             @Reference final NetconfTimer timer,
114             @Reference final NetconfTopologySchemaAssembler schemaAssembler,
115             @Reference final ActorSystemProvider actorSystemProvider,
116             @Reference final NetconfClientFactory clientFactory,
117             @Reference final DOMMountPointService mountPointService,
118             @Reference final AAAEncryptionService encryptionService,
119             @Reference final RpcProviderService rpcProviderService,
120             @Reference final DeviceActionFactory deviceActionFactory,
121             @Reference final SchemaResourceManager resourceManager,
122             @Reference final NetconfClientConfigurationBuilderFactory builderFactory,
123             final Configuration configuration) {
124         this(baseSchemaProvider, dataBroker, clusterSingletonServiceProvider, timer, schemaAssembler,
125             actorSystemProvider.getActorSystem(), clientFactory, mountPointService, encryptionService,
126             rpcProviderService, deviceActionFactory, resourceManager, builderFactory, configuration.topology$_$id(),
127             Uint16.valueOf(configuration.write$_$transaction$_$idle$_$timeout()));
128     }
129
130     @Inject
131     public NetconfTopologyManager(final BaseNetconfSchemaProvider baseSchemaProvider, final DataBroker dataBroker,
132             final ClusterSingletonServiceProvider clusterSingletonServiceProvider, final NetconfTimer timer,
133             final NetconfTopologySchemaAssembler schemaAssembler, final ActorSystemProvider actorSystemProvider,
134             final NetconfClientFactory clientFactory, final DOMMountPointService mountPointService,
135             final AAAEncryptionService encryptionService, final RpcProviderService rpcProviderService,
136             final DeviceActionFactory deviceActionFactory, final SchemaResourceManager resourceManager,
137             final NetconfClientConfigurationBuilderFactory builderFactory) {
138         this(baseSchemaProvider, dataBroker, clusterSingletonServiceProvider, timer, schemaAssembler,
139             actorSystemProvider.getActorSystem(), clientFactory, mountPointService, encryptionService,
140             rpcProviderService, deviceActionFactory, resourceManager, builderFactory,
141             NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, Uint16.ZERO);
142     }
143
144     @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR",
145         justification = "Non-final for mocking, but we register for DTCL and that leaks 'this'")
146     public NetconfTopologyManager(final BaseNetconfSchemaProvider baseSchemaProvider, final DataBroker dataBroker,
147             final ClusterSingletonServiceProvider clusterSingletonServiceProvider, final NetconfTimer timer,
148             final NetconfTopologySchemaAssembler schemaAssembler, final ActorSystem actorSystem,
149             final NetconfClientFactory clientFactory, final DOMMountPointService mountPointService,
150             final AAAEncryptionService encryptionService, final RpcProviderService rpcProviderService,
151             final DeviceActionFactory deviceActionFactory, final SchemaResourceManager resourceManager,
152             final NetconfClientConfigurationBuilderFactory builderFactory, final String topologyId,
153             final Uint16 writeTransactionIdleTimeout) {
154         this.baseSchemaProvider = requireNonNull(baseSchemaProvider);
155         this.dataBroker = requireNonNull(dataBroker);
156         this.clusterSingletonServiceProvider = requireNonNull(clusterSingletonServiceProvider);
157         this.timer = requireNonNull(timer);
158         this.schemaAssembler = requireNonNull(schemaAssembler);
159         this.actorSystem = requireNonNull(actorSystem);
160         this.clientFactory = requireNonNull(clientFactory);
161         this.topologyId = requireNonNull(topologyId);
162         writeTxIdleTimeout = Duration.ofSeconds(writeTransactionIdleTimeout.toJava());
163         this.mountPointService = mountPointService;
164         this.deviceActionFactory = requireNonNull(deviceActionFactory);
165         this.resourceManager = requireNonNull(resourceManager);
166         this.builderFactory = requireNonNull(builderFactory);
167
168         dataChangeListenerRegistration = registerDataTreeChangeListener();
169         rpcProvider = new NetconfTopologyRPCProvider(rpcProviderService, dataBroker, encryptionService, topologyId);
170     }
171
172     @Override
173     public void onDataTreeChanged(final List<DataTreeModification<Node>> changes) {
174         for (var change : changes) {
175             final var rootNode = change.getRootNode();
176             final var dataModifIdent = change.getRootPath().path();
177             final NodeId nodeId = NetconfTopologyUtils.getNodeId(rootNode.step());
178             switch (rootNode.modificationType()) {
179                 case SUBTREE_MODIFIED:
180                     LOG.debug("Config for node {} updated", nodeId);
181                     refreshNetconfDeviceContext(dataModifIdent, rootNode.dataAfter());
182                     break;
183                 case WRITE:
184                     if (contexts.containsKey(dataModifIdent)) {
185                         LOG.debug("RemoteDevice{{}} was already configured, reconfiguring node...", nodeId);
186                         refreshNetconfDeviceContext(dataModifIdent, rootNode.dataAfter());
187                     } else {
188                         LOG.debug("Config for node {} created", nodeId);
189                         startNetconfDeviceContext(dataModifIdent, rootNode.dataAfter());
190                     }
191                     break;
192                 case DELETE:
193                     LOG.debug("Config for node {} deleted", nodeId);
194                     stopNetconfDeviceContext(dataModifIdent);
195                     break;
196                 default:
197                     LOG.warn("Unknown operation for {}.", nodeId);
198             }
199         }
200     }
201
202     private void refreshNetconfDeviceContext(final InstanceIdentifier<Node> instanceIdentifier, final Node node) {
203         final NetconfTopologyContext context = contexts.get(instanceIdentifier);
204         context.refresh(createSetup(instanceIdentifier, node));
205     }
206
207     // ClusterSingletonServiceRegistration registerClusterSingletonService method throws a Runtime exception if there
208     // are problems with registration and client has to deal with it. Only thing we can do if this error occurs is to
209     // retry registration several times and log the error.
210     // TODO change to a specific documented Exception when changed in ClusterSingletonServiceProvider
211     @SuppressWarnings("checkstyle:IllegalCatch")
212     private void startNetconfDeviceContext(final InstanceIdentifier<Node> instanceIdentifier, final Node node) {
213         final NetconfNode netconfNode = requireNonNull(node.augmentation(NetconfNode.class));
214
215         final Timeout actorResponseWaitTime = Timeout.create(
216                 Duration.ofSeconds(netconfNode.getActorResponseWaitTime().toJava()));
217
218         final ServiceGroupIdentifier serviceGroupIdent = new ServiceGroupIdentifier(instanceIdentifier.toString());
219
220         final NetconfTopologyContext newNetconfTopologyContext = newNetconfTopologyContext(
221             createSetup(instanceIdentifier, node), serviceGroupIdent, actorResponseWaitTime, deviceActionFactory);
222
223         int tries = 3;
224         while (true) {
225             try {
226                 final var clusterSingletonServiceRegistration =
227                         clusterSingletonServiceProvider.registerClusterSingletonService(newNetconfTopologyContext);
228                 clusterRegistrations.put(instanceIdentifier, clusterSingletonServiceRegistration);
229                 contexts.put(instanceIdentifier, newNetconfTopologyContext);
230                 break;
231             } catch (final RuntimeException e) {
232                 LOG.warn("Unable to register cluster singleton service {}, trying again", newNetconfTopologyContext, e);
233
234                 if (--tries <= 0) {
235                     LOG.error("Unable to register cluster singleton service {} - done trying, closing topology context",
236                             newNetconfTopologyContext, e);
237                     close(newNetconfTopologyContext);
238                     break;
239                 }
240             }
241         }
242     }
243
244     private void stopNetconfDeviceContext(final InstanceIdentifier<Node> instanceIdentifier) {
245         final NetconfTopologyContext netconfTopologyContext = contexts.remove(instanceIdentifier);
246         if (netconfTopologyContext != null) {
247             close(clusterRegistrations.remove(instanceIdentifier));
248             close(netconfTopologyContext);
249         }
250     }
251
252     @VisibleForTesting
253     protected NetconfTopologyContext newNetconfTopologyContext(final NetconfTopologySetup setup,
254             final ServiceGroupIdentifier serviceGroupIdent, final Timeout actorResponseWaitTime,
255             final DeviceActionFactory deviceActionFact) {
256         return new NetconfTopologyContext(resourceManager, mountPointService, builderFactory, deviceActionFactory,
257             actorResponseWaitTime, serviceGroupIdent, setup);
258     }
259
260     @PreDestroy
261     @Deactivate
262     @Override
263     public void close() {
264         if (rpcProvider != null) {
265             rpcProvider.close();
266             rpcProvider = null;
267         }
268         if (dataChangeListenerRegistration != null) {
269             dataChangeListenerRegistration.close();
270             dataChangeListenerRegistration = null;
271         }
272
273         contexts.values().forEach(NetconfTopologyManager::close);
274         clusterRegistrations.values().forEach(NetconfTopologyManager::close);
275
276         contexts.clear();
277         clusterRegistrations.clear();
278     }
279
280     @SuppressWarnings("checkstyle:IllegalCatch")
281     private static void close(final AutoCloseable closeable) {
282         try {
283             closeable.close();
284         } catch (Exception e) {
285             LOG.warn("Error closing {}", closeable, e);
286         }
287     }
288
289     private Registration registerDataTreeChangeListener() {
290         final WriteTransaction wtx = dataBroker.newWriteOnlyTransaction();
291         // FIXME: how does this play out with lifecycle? In a cluster, someone needs to ensure this call happens, but
292         //        also we need to to make sure config -> oper is properly synchronized. Non-clustered case relies on
293         //        oper being transient and perhaps on a put() instead, how do we handle that in the clustered case?
294         wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
295             .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
296             .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
297         wtx.commit().addCallback(new FutureCallback<CommitInfo>() {
298             @Override
299             public void onSuccess(final CommitInfo result) {
300                 LOG.debug("topology initialization successful");
301             }
302
303             @Override
304             public void onFailure(final Throwable throwable) {
305                 LOG.error("Unable to initialize netconf-topology", throwable);
306             }
307         }, MoreExecutors.directExecutor());
308
309         LOG.debug("Registering datastore listener");
310         return dataBroker.registerTreeChangeListener(DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION,
311             NetconfTopologyUtils.createTopologyListPath(topologyId).child(Node.class)), this);
312     }
313
314     private NetconfTopologySetup createSetup(final InstanceIdentifier<Node> instanceIdentifier, final Node node) {
315         final NetconfNode netconfNode = node.augmentation(NetconfNode.class);
316         final RemoteDeviceId deviceId = NetconfNodeUtils.toRemoteDeviceId(node.getNodeId(), netconfNode);
317
318         return NetconfTopologySetup.builder()
319             .setClusterSingletonServiceProvider(clusterSingletonServiceProvider)
320             .setBaseSchemaProvider(baseSchemaProvider)
321             .setDataBroker(dataBroker)
322             .setInstanceIdentifier(instanceIdentifier)
323             .setNode(node)
324             .setActorSystem(actorSystem)
325             .setTimer(timer)
326             .setSchemaAssembler(schemaAssembler)
327             .setTopologyId(topologyId)
328             .setNetconfClientFactory(clientFactory)
329             .setDeviceSchemaProvider(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(),
330                 deviceId))
331             .setIdleTimeout(writeTxIdleTimeout)
332             .build();
333     }
334 }