2 * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.bgpcep.pcep.topology.provider;
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;
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.List;
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.DataBroker;
31 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
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.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.topology.pcep.rev220730.TopologyTypes1;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.network.topology.topology.topology.types.TopologyPcep;
42 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
43 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
44 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
45 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.TopologyTypes;
46 import org.opendaylight.yangtools.concepts.Registration;
47 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
48 import org.osgi.service.component.annotations.Activate;
49 import org.osgi.service.component.annotations.Component;
50 import org.osgi.service.component.annotations.Deactivate;
51 import org.osgi.service.component.annotations.Reference;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
56 * Primary entrypoint into this component. Once an instance of this class is instantiated, it will subscribe to
57 * changes to the configuration datastore. There it filters only topologies which have {@link TopologyPcep} type and for
58 * each one of those instantiates a cluster-wide singleton to handle lifecycle of services attached to that topology.
61 @Component(service = { })
62 public final class PCEPTopologyTracker
63 implements PCEPTopologyProviderDependencies, DataTreeChangeListener<TopologyPcep>, AutoCloseable {
64 private static final Logger LOG = LoggerFactory.getLogger(PCEPTopologyTracker.class);
66 // Services we are using
67 final @NonNull InstructionSchedulerFactory instructionSchedulerFactory;
68 final @NonNull ClusterSingletonServiceProvider singletonService;
69 private final @NonNull RpcProviderService rpcProviderRegistry;
70 private final @NonNull PceServerProvider pceServerProvider;
71 private final @NonNull MessageRegistry messageRegistry;
72 private final @NonNull PCEPDispatcher pcepDispatcher;
73 private final @NonNull DataBroker dataBroker;
75 // Timer used for RPC timeouts and session statistics scheduling
76 private final @NonNull HashedWheelTimer privateTimer = new HashedWheelTimer();
77 private final @NonNull Timer timer = new Timer() {
79 public Timeout newTimeout(final TimerTask task, final long delay, final TimeUnit unit) {
80 return privateTimer.newTimeout(task, delay, unit);
84 public Set<Timeout> stop() {
85 // Do not allow the timer to be shut down
86 throw new UnsupportedOperationException();
90 // Statistics provider
91 private final @NonNull TopologyStatsProvider statsProvider;
93 private final @NonNull TopologyStatsRpc statsRpcs;
95 // We are reusing our monitor as the universal lock. We have to account for three distinct threads competing for
97 // 1) the typical DTCL callback thread invoking onDataTreeChanged()
98 // 2) instance cleanup thread invoking finishDestroy()
99 // 3) framework shutdown thread invoking close()
101 // We need to track not only instances which are deemed alive by the class, but also all instances for which cleanup
102 // has not finished yet, so close() can properly wait for cleanup to finish.
104 // Since close() will terminate the DTCL subscription, the synchronization between 1) and 3) is rather trivial.
106 // The interaction between DTCL and cleanup is tricky. DTCL can report rapid create/destroy/create events and
107 // cleanup is asynchronous and when the dust settles we need to end up in the corrected overall state (created or
110 // In order to achieve that without risking deadlocks, instances are tracked using a concurrent map and each
111 // 'create' edge allocates a new PCEPTopologyInstance object.
112 private final ConcurrentMap<TopologyKey, PCEPTopologySingleton> instances = new ConcurrentHashMap<>();
114 private Registration reg;
118 public PCEPTopologyTracker(@Reference final DataBroker dataBroker,
119 @Reference final ClusterSingletonServiceProvider singletonService,
120 @Reference final RpcProviderService rpcProviderRegistry,
121 @Reference final PCEPExtensionConsumerContext extensions, @Reference final PCEPDispatcher pcepDispatcher,
122 @Reference final InstructionSchedulerFactory instructionSchedulerFactory,
123 @Reference final PceServerProvider pceServerProvider) {
124 this.dataBroker = requireNonNull(dataBroker);
125 this.singletonService = requireNonNull(singletonService);
126 this.rpcProviderRegistry = requireNonNull(rpcProviderRegistry);
127 messageRegistry = extensions.getMessageHandlerRegistry();
128 this.pcepDispatcher = requireNonNull(pcepDispatcher);
129 this.instructionSchedulerFactory = requireNonNull(instructionSchedulerFactory);
130 this.pceServerProvider = requireNonNull(pceServerProvider);
131 statsProvider = new TopologyStatsProvider(timer);
132 statsRpcs = new TopologyStatsRpc(dataBroker, rpcProviderRegistry);
134 reg = dataBroker.registerTreeChangeListener(DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION,
135 InstanceIdentifier.builder(NetworkTopology.class).child(Topology.class).child(TopologyTypes.class)
136 .augmentation(TopologyTypes1.class).child(TopologyPcep.class).build()), this);
137 LOG.info("PCEP Topology tracker initialized");
141 public MessageRegistry getMessageRegistry() {
142 return messageRegistry;
146 public PCEPDispatcher getPCEPDispatcher() {
147 return pcepDispatcher;
151 public RpcProviderService getRpcProviderRegistry() {
152 return rpcProviderRegistry;
156 public DataBroker getDataBroker() {
161 public SessionStateRegistry getStateRegistry() {
162 return statsProvider;
166 public PceServerProvider getPceServerProvider() {
167 return pceServerProvider;
171 public Timer getTimer() {
178 public synchronized void close() {
180 // Already closed, bail out
184 LOG.info("PCEP Topology tracker shutting down");
190 // First pass: destroy all tracked instances
191 instances.values().forEach(PCEPTopologySingleton::destroy);
192 // Second pass: wait for cleanup
193 instances.values().forEach(PCEPTopologySingleton::awaitCleanup);
196 final var cancelledTasks = privateTimer.stop().size();
197 if (cancelledTasks != 0) {
198 LOG.warn("Stopped timer with {} remaining tasks", cancelledTasks);
201 statsProvider.shutdown();
202 LOG.info("PCEP Topology tracker shut down");
206 public synchronized void onDataTreeChanged(final List<DataTreeModification<TopologyPcep>> changes) {
208 // Registration has been terminated, do not process any changes
212 for (var change : changes) {
213 final var root = change.getRootNode();
214 switch (root.modificationType()) {
216 // We only care if the topology has been newly introduced, not when its details have changed
217 if (root.dataBefore() == null) {
218 createInstance(extractTopologyKey(change));
222 destroyInstance(extractTopologyKey(change));
230 private void createInstance(final @NonNull TopologyKey topology) {
231 final var existing = instances.remove(topology);
232 final PCEPTopologySingleton instance;
233 if (existing == null) {
234 LOG.info("Creating topology instance for {}", friendlyId(topology));
235 instance = new PCEPTopologySingleton(this, topology);
237 LOG.info("Resurrecting topology instance for {}", friendlyId(topology));
238 instance = existing.resurrect();
240 instances.put(topology, instance);
243 private void destroyInstance(final @NonNull TopologyKey topology) {
244 final var existing = instances.get(topology);
245 if (existing != null) {
246 LOG.info("Destroying topology instance for {}", friendlyId(topology));
249 LOG.warn("Attempted to destroy non-existent topology instance for {}", friendlyId(topology));
253 void finishDestroy(final TopologyKey topology, final PCEPTopologySingleton instance) {
254 if (instances.remove(topology, instance)) {
255 LOG.info("Destroyed topology instance of {}", friendlyId(topology));
259 private static @NonNull TopologyKey extractTopologyKey(final DataTreeModification<?> change) {
260 final var path = change.getRootPath().path();
261 return verifyNotNull(path.firstKeyOf(Topology.class), "No topology key in %s", path);