Remove DefaultTopologyReference service entry
[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 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.pcep.topology.spi.stats.TopologySessionStatsRegistry;
21 import org.opendaylight.bgpcep.programming.spi.InstructionSchedulerFactory;
22 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
23 import org.opendaylight.mdsal.binding.api.DataBroker;
24 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
25 import org.opendaylight.mdsal.binding.api.DataTreeModification;
26 import org.opendaylight.mdsal.binding.api.RpcProviderService;
27 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
28 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
29 import org.opendaylight.protocol.pcep.PCEPDispatcher;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev200120.TopologyTypes1;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev200120.topology.pcep.type.TopologyPcep;
32 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
33 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
34 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
35 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.TopologyTypes;
36 import org.opendaylight.yangtools.concepts.Registration;
37 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Primary entrypoint into this component. Once an instance of this class is instantiated, it will subscribe to
43  * changes to the configuration datastore. There it filters only topologies which have {@link TopologyPcep} type and for
44  * each one of those instantiates a cluster-wide singleton to handle lifecycle of services attached to that topology.
45  */
46 public final class PCEPTopologyTracker
47         implements PCEPTopologyProviderDependencies, ClusteredDataTreeChangeListener<TopologyPcep>, AutoCloseable {
48     private static final Logger LOG = LoggerFactory.getLogger(PCEPTopologyTracker.class);
49
50     // Services we are using
51     final @NonNull InstructionSchedulerFactory instructionSchedulerFactory;
52     final @NonNull ClusterSingletonServiceProvider singletonService;
53     private final @NonNull TopologySessionStatsRegistry stateRegistry;
54     private final @NonNull RpcProviderService rpcProviderRegistry;
55     private final @NonNull PceServerProvider pceServerProvider;
56     private final @NonNull PCEPDispatcher pcepDispatcher;
57     private final @NonNull DataBroker dataBroker;
58
59     // We are reusing our monitor as the universal lock. We have to account for three distinct threads competing for
60     // our state:
61     //   1) the typical DTCL callback thread invoking onDataTreeChanged()
62     //   2) instance cleanup thread invoking finishDestroy()
63     //   3) framework shutdown thread invoking close()
64     //
65     // We need to track not only instances which are deemed alive by the class, but also all instances for which cleanup
66     // has not finished yet, so close() can properly wait for cleanup to finish.
67     //
68     // Since close() will terminate the DTCL subscription, the synchronization between 1) and 3) is rather trivial.
69     //
70     // The interaction between DTCL and cleanup is tricky. DTCL can report rapid create/destroy/create events and
71     // cleanup is asynchronous and when the dust settles we need to end up in the corrected overall state (created or
72     // destroyed).
73     //
74     // In order to achieve that without risking deadlocks, instances are tracked using a concurrent map and each
75     // 'create' edge allocates a new PCEPTopologyInstance object.
76     private final ConcurrentMap<TopologyKey, PCEPTopologySingleton> instances = new ConcurrentHashMap<>();
77     @GuardedBy("this")
78     private Registration reg;
79
80     public PCEPTopologyTracker(final DataBroker dataBroker, final ClusterSingletonServiceProvider singletonService,
81             final RpcProviderService rpcProviderRegistry, final PCEPDispatcher pcepDispatcher,
82             final InstructionSchedulerFactory instructionSchedulerFactory,
83             final TopologySessionStatsRegistry stateRegistry, final PceServerProvider pceServerProvider) {
84         this.dataBroker = requireNonNull(dataBroker);
85         this.singletonService = requireNonNull(singletonService);
86         this.rpcProviderRegistry = requireNonNull(rpcProviderRegistry);
87         this.pcepDispatcher = requireNonNull(pcepDispatcher);
88         this.instructionSchedulerFactory = requireNonNull(instructionSchedulerFactory);
89         this.stateRegistry = requireNonNull(stateRegistry);
90         this.pceServerProvider = requireNonNull(pceServerProvider);
91
92         reg = dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
93             InstanceIdentifier.builder(NetworkTopology.class).child(Topology.class).child(TopologyTypes.class)
94                 .augmentation(TopologyTypes1.class).child(TopologyPcep.class).build()), this);
95         LOG.info("PCEP Topology tracker initialized");
96     }
97
98     @Override
99     public PCEPDispatcher getPCEPDispatcher() {
100         return pcepDispatcher;
101     }
102
103     @Override
104     public RpcProviderService getRpcProviderRegistry() {
105         return rpcProviderRegistry;
106     }
107
108     @Override
109     public DataBroker getDataBroker() {
110         return dataBroker;
111     }
112
113     @Override
114     public TopologySessionStatsRegistry getStateRegistry() {
115         return stateRegistry;
116     }
117
118     @Override
119     public PceServerProvider getPceServerProvider() {
120         return pceServerProvider;
121     }
122
123     @Override
124     public synchronized void close() {
125         if (reg == null) {
126             // Already closed, bail out
127             return;
128         }
129
130         LOG.info("PCEP Topology tracker shutting down");
131         reg.close();
132         reg = null;
133
134         // First pass: destroy all tracked instances
135         instances.values().forEach(PCEPTopologySingleton::destroy);
136         // Second pass: wait for cleanup
137         instances.values().forEach(PCEPTopologySingleton::awaitCleanup);
138         LOG.info("PCEP Topology tracker shut down");
139     }
140
141     @Override
142     public synchronized void onDataTreeChanged(final Collection<DataTreeModification<TopologyPcep>> changes) {
143         if (reg == null) {
144             // Registration has been terminated, do not process any changes
145             return;
146         }
147
148         for (var change : changes) {
149             final var root = change.getRootNode();
150             switch (root.getModificationType()) {
151                 case WRITE:
152                     // We only care if the topology has been newly introduced, not when its details have changed
153                     if (root.getDataBefore() == null) {
154                         createInstance(extractTopologyKey(change));
155                     }
156                     break;
157                 case DELETE:
158                     destroyInstance(extractTopologyKey(change));
159                     break;
160                 default:
161                     // No-op
162             }
163         }
164     }
165
166     private void createInstance(final @NonNull TopologyKey topology) {
167         final var existing = instances.remove(topology);
168         final PCEPTopologySingleton instance;
169         if (existing == null) {
170             LOG.info("Creating topology instance for {}", friendlyId(topology));
171             instance = new PCEPTopologySingleton(this, topology);
172         } else {
173             LOG.info("Resurrecting topology instance for {}", friendlyId(topology));
174             instance = existing.resurrect();
175         }
176         instances.put(topology, instance);
177     }
178
179     private void destroyInstance(final @NonNull TopologyKey topology) {
180         final var existing = instances.get(topology);
181         if (existing != null) {
182             LOG.info("Destroying topology instance for {}", friendlyId(topology));
183             existing.destroy();
184         } else {
185             LOG.warn("Attempted to destroy non-existent topology instance for {}", friendlyId(topology));
186         }
187     }
188
189     void finishDestroy(final TopologyKey topology, final PCEPTopologySingleton instance) {
190         if (instances.remove(topology, instance)) {
191             LOG.info("Destroyed topology instance of {}", friendlyId(topology));
192         }
193     }
194
195     private static @NonNull TopologyKey extractTopologyKey(final DataTreeModification<?> change) {
196         final var path = change.getRootPath().getRootIdentifier();
197         return verifyNotNull(path.firstKeyOf(Topology.class), "No topology key in %s", path);
198     }
199 }