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