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