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 java.util.Collection;
15 import java.util.concurrent.ConcurrentHashMap;
16 import java.util.concurrent.ConcurrentMap;
17 import org.checkerframework.checker.lock.qual.GuardedBy;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.bgpcep.pcep.server.PceServerProvider;
20 import org.opendaylight.bgpcep.programming.spi.InstructionSchedulerFactory;
21 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
22 import org.opendaylight.mdsal.binding.api.DataBroker;
23 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
24 import org.opendaylight.mdsal.binding.api.DataTreeModification;
25 import org.opendaylight.mdsal.binding.api.RpcProviderService;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
28 import org.opendaylight.protocol.pcep.PCEPDispatcher;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.TopologyTypes1;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.topology.pcep.type.TopologyPcep;
31 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
32 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
33 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
34 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.TopologyTypes;
35 import org.opendaylight.yangtools.concepts.Registration;
36 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * Primary entrypoint into this component. Once an instance of this class is instantiated, it will subscribe to
42 * changes to the configuration datastore. There it filters only topologies which have {@link TopologyPcep} type and for
43 * each one of those instantiates a cluster-wide singleton to handle lifecycle of services attached to that topology.
45 public final class PCEPTopologyTracker
46 implements PCEPTopologyProviderDependencies, ClusteredDataTreeChangeListener<TopologyPcep>, AutoCloseable {
47 private static final Logger LOG = LoggerFactory.getLogger(PCEPTopologyTracker.class);
49 // Services we are using
50 final @NonNull InstructionSchedulerFactory instructionSchedulerFactory;
51 final @NonNull ClusterSingletonServiceProvider singletonService;
52 private final @NonNull TopologySessionStatsRegistry stateRegistry;
53 private final @NonNull RpcProviderService rpcProviderRegistry;
54 private final @NonNull PceServerProvider pceServerProvider;
55 private final @NonNull PCEPDispatcher pcepDispatcher;
56 private final @NonNull DataBroker dataBroker;
58 // We are reusing our monitor as the universal lock. We have to account for three distinct threads competing for
60 // 1) the typical DTCL callback thread invoking onDataTreeChanged()
61 // 2) instance cleanup thread invoking finishDestroy()
62 // 3) framework shutdown thread invoking close()
64 // We need to track not only instances which are deemed alive by the class, but also all instances for which cleanup
65 // has not finished yet, so close() can properly wait for cleanup to finish.
67 // Since close() will terminate the DTCL subscription, the synchronization between 1) and 3) is rather trivial.
69 // The interaction between DTCL and cleanup is tricky. DTCL can report rapid create/destroy/create events and
70 // cleanup is asynchronous and when the dust settles we need to end up in the corrected overall state (created or
73 // In order to achieve that without risking deadlocks, instances are tracked using a concurrent map and each
74 // 'create' edge allocates a new PCEPTopologyInstance object.
75 private final ConcurrentMap<TopologyKey, PCEPTopologySingleton> instances = new ConcurrentHashMap<>();
77 private Registration reg;
79 public PCEPTopologyTracker(final DataBroker dataBroker, final ClusterSingletonServiceProvider singletonService,
80 final RpcProviderService rpcProviderRegistry, final PCEPDispatcher pcepDispatcher,
81 final InstructionSchedulerFactory instructionSchedulerFactory,
82 final TopologySessionStatsRegistry stateRegistry, final PceServerProvider pceServerProvider) {
83 this.dataBroker = requireNonNull(dataBroker);
84 this.singletonService = requireNonNull(singletonService);
85 this.rpcProviderRegistry = requireNonNull(rpcProviderRegistry);
86 this.pcepDispatcher = requireNonNull(pcepDispatcher);
87 this.instructionSchedulerFactory = requireNonNull(instructionSchedulerFactory);
88 this.stateRegistry = requireNonNull(stateRegistry);
89 this.pceServerProvider = requireNonNull(pceServerProvider);
91 reg = dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
92 InstanceIdentifier.builder(NetworkTopology.class).child(Topology.class).child(TopologyTypes.class)
93 .augmentation(TopologyTypes1.class).child(TopologyPcep.class).build()), this);
94 LOG.info("PCEP Topology tracker initialized");
98 public PCEPDispatcher getPCEPDispatcher() {
99 return pcepDispatcher;
103 public RpcProviderService getRpcProviderRegistry() {
104 return rpcProviderRegistry;
108 public DataBroker getDataBroker() {
113 public TopologySessionStatsRegistry getStateRegistry() {
114 return stateRegistry;
118 public PceServerProvider getPceServerProvider() {
119 return pceServerProvider;
123 public synchronized void close() {
125 // Already closed, bail out
129 LOG.info("PCEP Topology tracker shutting down");
133 // First pass: destroy all tracked instances
134 instances.values().forEach(PCEPTopologySingleton::destroy);
135 // Second pass: wait for cleanup
136 instances.values().forEach(PCEPTopologySingleton::awaitCleanup);
137 LOG.info("PCEP Topology tracker shut down");
141 public synchronized void onDataTreeChanged(final Collection<DataTreeModification<TopologyPcep>> changes) {
143 // Registration has been terminated, do not process any changes
147 for (var change : changes) {
148 final var root = change.getRootNode();
149 switch (root.getModificationType()) {
151 // We only care if the topology has been newly introduced, not when its details have changed
152 if (root.getDataBefore() == null) {
153 createInstance(extractTopologyKey(change));
157 destroyInstance(extractTopologyKey(change));
165 private void createInstance(final @NonNull TopologyKey topology) {
166 final var existing = instances.remove(topology);
167 final PCEPTopologySingleton instance;
168 if (existing == null) {
169 LOG.info("Creating topology instance for {}", friendlyId(topology));
170 instance = new PCEPTopologySingleton(this, topology);
172 LOG.info("Resurrecting topology instance for {}", friendlyId(topology));
173 instance = existing.resurrect();
175 instances.put(topology, instance);
178 private void destroyInstance(final @NonNull TopologyKey topology) {
179 final var existing = instances.get(topology);
180 if (existing != null) {
181 LOG.info("Destroying topology instance for {}", friendlyId(topology));
184 LOG.warn("Attempted to destroy non-existent topology instance for {}", friendlyId(topology));
188 void finishDestroy(final TopologyKey topology, final PCEPTopologySingleton instance) {
189 if (instances.remove(topology, instance)) {
190 LOG.info("Destroyed topology instance of {}", friendlyId(topology));
194 private static @NonNull TopologyKey extractTopologyKey(final DataTreeModification<?> change) {
195 final var path = change.getRootPath().getRootIdentifier();
196 return verifyNotNull(path.firstKeyOf(Topology.class), "No topology key in %s", path);