Ditch blueprint from pcep-topology-provider
[bgpcep.git] / pcep / topology / topology-provider / src / main / java / org / opendaylight / bgpcep / pcep / topology / provider / PCEPTopologyTracker.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.bgpcep.pcep.topology.provider;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.bgpcep.pcep.topology.provider.TopologyUtils.friendlyId;
13
14 import io.netty.util.HashedWheelTimer;
15 import io.netty.util.Timeout;
16 import io.netty.util.Timer;
17 import io.netty.util.TimerTask;
18 import java.util.Collection;
19 import java.util.Set;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ConcurrentMap;
22 import java.util.concurrent.TimeUnit;
23 import javax.annotation.PreDestroy;
24 import javax.inject.Inject;
25 import javax.inject.Singleton;
26 import org.checkerframework.checker.lock.qual.GuardedBy;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.opendaylight.bgpcep.pcep.server.PceServerProvider;
29 import org.opendaylight.bgpcep.programming.spi.InstructionSchedulerFactory;
30 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
31 import org.opendaylight.mdsal.binding.api.DataBroker;
32 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
33 import org.opendaylight.mdsal.binding.api.DataTreeModification;
34 import org.opendaylight.mdsal.binding.api.RpcProviderService;
35 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
36 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
37 import org.opendaylight.protocol.pcep.MessageRegistry;
38 import org.opendaylight.protocol.pcep.PCEPDispatcher;
39 import org.opendaylight.protocol.pcep.spi.PCEPExtensionConsumerContext;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.topology.stats.rpc.rev190321.PcepTopologyStatsRpcService;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.TopologyTypes1;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.topology.pcep.type.TopologyPcep;
43 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
44 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
45 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
46 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.TopologyTypes;
47 import org.opendaylight.yangtools.concepts.Registration;
48 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
49 import org.osgi.service.component.annotations.Activate;
50 import org.osgi.service.component.annotations.Component;
51 import org.osgi.service.component.annotations.Deactivate;
52 import org.osgi.service.component.annotations.Reference;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * Primary entrypoint into this component. Once an instance of this class is instantiated, it will subscribe to
58  * changes to the configuration datastore. There it filters only topologies which have {@link TopologyPcep} type and for
59  * each one of those instantiates a cluster-wide singleton to handle lifecycle of services attached to that topology.
60  */
61 @Singleton
62 @Component(service = { })
63 public final class PCEPTopologyTracker
64         implements PCEPTopologyProviderDependencies, ClusteredDataTreeChangeListener<TopologyPcep>, AutoCloseable {
65     private static final Logger LOG = LoggerFactory.getLogger(PCEPTopologyTracker.class);
66
67     // Services we are using
68     final @NonNull InstructionSchedulerFactory instructionSchedulerFactory;
69     final @NonNull ClusterSingletonServiceProvider singletonService;
70     private final @NonNull RpcProviderService rpcProviderRegistry;
71     private final @NonNull PceServerProvider pceServerProvider;
72     private final @NonNull MessageRegistry messageRegistry;
73     private final @NonNull PCEPDispatcher pcepDispatcher;
74     private final @NonNull DataBroker dataBroker;
75
76     // Timer used for RPC timeouts and session statistics scheduling
77     private final @NonNull HashedWheelTimer privateTimer = new HashedWheelTimer();
78     private final @NonNull Timer timer = new Timer() {
79         @Override
80         public Timeout newTimeout(final TimerTask task, final long delay, final TimeUnit unit) {
81             return privateTimer.newTimeout(task, delay, unit);
82         }
83
84         @Override
85         public Set<Timeout> stop() {
86             // Do not allow the timer to be shut down
87             throw new UnsupportedOperationException();
88         }
89     };
90
91     // Statistics provider
92     private final @NonNull TopologyStatsProvider statsProvider;
93     // Statistics RPCs
94     private final @NonNull TopologyStatsRpcServiceImpl statsRpcs;
95
96     // We are reusing our monitor as the universal lock. We have to account for three distinct threads competing for
97     // our state:
98     //   1) the typical DTCL callback thread invoking onDataTreeChanged()
99     //   2) instance cleanup thread invoking finishDestroy()
100     //   3) framework shutdown thread invoking close()
101     //
102     // We need to track not only instances which are deemed alive by the class, but also all instances for which cleanup
103     // has not finished yet, so close() can properly wait for cleanup to finish.
104     //
105     // Since close() will terminate the DTCL subscription, the synchronization between 1) and 3) is rather trivial.
106     //
107     // The interaction between DTCL and cleanup is tricky. DTCL can report rapid create/destroy/create events and
108     // cleanup is asynchronous and when the dust settles we need to end up in the corrected overall state (created or
109     // destroyed).
110     //
111     // In order to achieve that without risking deadlocks, instances are tracked using a concurrent map and each
112     // 'create' edge allocates a new PCEPTopologyInstance object.
113     private final ConcurrentMap<TopologyKey, PCEPTopologySingleton> instances = new ConcurrentHashMap<>();
114     @GuardedBy("this")
115     private Registration reg;
116     @GuardedBy("this")
117     private Registration statsReg;
118
119     @Inject
120     @Activate
121     public PCEPTopologyTracker(@Reference final DataBroker dataBroker,
122             @Reference final ClusterSingletonServiceProvider singletonService,
123             @Reference final RpcProviderService rpcProviderRegistry,
124             @Reference final PCEPExtensionConsumerContext extensions, @Reference final PCEPDispatcher pcepDispatcher,
125             @Reference final InstructionSchedulerFactory instructionSchedulerFactory,
126             @Reference final PceServerProvider pceServerProvider) {
127         this.dataBroker = requireNonNull(dataBroker);
128         this.singletonService = requireNonNull(singletonService);
129         this.rpcProviderRegistry = requireNonNull(rpcProviderRegistry);
130         messageRegistry = extensions.getMessageHandlerRegistry();
131         this.pcepDispatcher = requireNonNull(pcepDispatcher);
132         this.instructionSchedulerFactory = requireNonNull(instructionSchedulerFactory);
133         this.pceServerProvider = requireNonNull(pceServerProvider);
134         statsProvider = new TopologyStatsProvider(timer);
135         statsRpcs = new TopologyStatsRpcServiceImpl(dataBroker);
136         statsReg = rpcProviderRegistry.registerRpcImplementation(PcepTopologyStatsRpcService.class, statsRpcs);
137
138         reg = dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
139             InstanceIdentifier.builder(NetworkTopology.class).child(Topology.class).child(TopologyTypes.class)
140                 .augmentation(TopologyTypes1.class).child(TopologyPcep.class).build()), this);
141         LOG.info("PCEP Topology tracker initialized");
142     }
143
144     @Override
145     public MessageRegistry getMessageRegistry() {
146         return messageRegistry;
147     }
148
149     @Override
150     public PCEPDispatcher getPCEPDispatcher() {
151         return pcepDispatcher;
152     }
153
154     @Override
155     public RpcProviderService getRpcProviderRegistry() {
156         return rpcProviderRegistry;
157     }
158
159     @Override
160     public DataBroker getDataBroker() {
161         return dataBroker;
162     }
163
164     @Override
165     public SessionStateRegistry getStateRegistry() {
166         return statsProvider;
167     }
168
169     @Override
170     public PceServerProvider getPceServerProvider() {
171         return pceServerProvider;
172     }
173
174     @Override
175     public Timer getTimer() {
176         return timer;
177     }
178
179     @PreDestroy
180     @Deactivate
181     @Override
182     public synchronized void close() {
183         if (reg == null) {
184             // Already closed, bail out
185             return;
186         }
187
188         LOG.info("PCEP Topology tracker shutting down");
189         reg.close();
190         reg = null;
191
192         statsReg.close();
193         statsReg = null;
194         statsRpcs.close();
195
196         // First pass: destroy all tracked instances
197         instances.values().forEach(PCEPTopologySingleton::destroy);
198         // Second pass: wait for cleanup
199         instances.values().forEach(PCEPTopologySingleton::awaitCleanup);
200
201         // Stop the timer
202         final var cancelledTasks = privateTimer.stop().size();
203         if (cancelledTasks != 0) {
204             LOG.warn("Stopped timer with {} remaining tasks", cancelledTasks);
205         }
206
207         statsProvider.shutdown();
208         LOG.info("PCEP Topology tracker shut down");
209     }
210
211     @Override
212     public synchronized void onDataTreeChanged(final Collection<DataTreeModification<TopologyPcep>> changes) {
213         if (reg == null) {
214             // Registration has been terminated, do not process any changes
215             return;
216         }
217
218         for (var change : changes) {
219             final var root = change.getRootNode();
220             switch (root.getModificationType()) {
221                 case WRITE:
222                     // We only care if the topology has been newly introduced, not when its details have changed
223                     if (root.getDataBefore() == null) {
224                         createInstance(extractTopologyKey(change));
225                     }
226                     break;
227                 case DELETE:
228                     destroyInstance(extractTopologyKey(change));
229                     break;
230                 default:
231                     // No-op
232             }
233         }
234     }
235
236     private void createInstance(final @NonNull TopologyKey topology) {
237         final var existing = instances.remove(topology);
238         final PCEPTopologySingleton instance;
239         if (existing == null) {
240             LOG.info("Creating topology instance for {}", friendlyId(topology));
241             instance = new PCEPTopologySingleton(this, topology);
242         } else {
243             LOG.info("Resurrecting topology instance for {}", friendlyId(topology));
244             instance = existing.resurrect();
245         }
246         instances.put(topology, instance);
247     }
248
249     private void destroyInstance(final @NonNull TopologyKey topology) {
250         final var existing = instances.get(topology);
251         if (existing != null) {
252             LOG.info("Destroying topology instance for {}", friendlyId(topology));
253             existing.destroy();
254         } else {
255             LOG.warn("Attempted to destroy non-existent topology instance for {}", friendlyId(topology));
256         }
257     }
258
259     void finishDestroy(final TopologyKey topology, final PCEPTopologySingleton instance) {
260         if (instances.remove(topology, instance)) {
261             LOG.info("Destroyed topology instance of {}", friendlyId(topology));
262         }
263     }
264
265     private static @NonNull TopologyKey extractTopologyKey(final DataTreeModification<?> change) {
266         final var path = change.getRootPath().getRootIdentifier();
267         return verifyNotNull(path.firstKeyOf(Topology.class), "No topology key in %s", path);
268     }
269 }