Modernize bgp-rib-impl
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / config / DefaultBgpDeployer.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech s.r.o. 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.protocol.bgp.rib.impl.config;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.cache.CacheBuilder;
14 import com.google.common.cache.CacheLoader;
15 import com.google.common.cache.LoadingCache;
16 import com.google.common.util.concurrent.FluentFuture;
17 import com.google.common.util.concurrent.FutureCallback;
18 import com.google.common.util.concurrent.MoreExecutors;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Optional;
23 import java.util.concurrent.ExecutionException;
24 import java.util.stream.Collectors;
25 import javax.annotation.PostConstruct;
26 import javax.annotation.PreDestroy;
27 import javax.inject.Inject;
28 import javax.inject.Singleton;
29 import org.checkerframework.checker.lock.qual.GuardedBy;
30 import org.opendaylight.mdsal.binding.api.DataBroker;
31 import org.opendaylight.mdsal.binding.api.DataObjectModification;
32 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
33 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
34 import org.opendaylight.mdsal.binding.api.DataTreeModification;
35 import org.opendaylight.mdsal.binding.api.ReadTransaction;
36 import org.opendaylight.mdsal.binding.api.RpcProviderService;
37 import org.opendaylight.mdsal.binding.api.WriteTransaction;
38 import org.opendaylight.mdsal.common.api.CommitInfo;
39 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
40 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
41 import org.opendaylight.mdsal.singleton.api.ClusterSingletonServiceProvider;
42 import org.opendaylight.protocol.bgp.openconfig.routing.policy.spi.BGPRibRoutingPolicyFactory;
43 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
44 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPDispatcher;
45 import org.opendaylight.protocol.bgp.rib.impl.spi.CodecsRegistry;
46 import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
47 import org.opendaylight.protocol.bgp.rib.spi.state.BGPStateProviderRegistry;
48 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.peer.group.PeerGroup;
49 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.peer.group.PeerGroupKey;
50 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.top.Bgp;
51 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.top.bgp.Global;
52 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.top.bgp.Neighbors;
53 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.top.bgp.PeerGroups;
54 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.OpenconfigNetworkInstanceData;
55 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.NetworkInstances;
56 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.NetworkInstance;
57 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.NetworkInstanceBuilder;
58 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.NetworkInstanceKey;
59 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.network.instance.Protocols;
60 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.network.instance.ProtocolsBuilder;
61 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.network.instance.protocols.Protocol;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev180329.NetworkInstanceProtocol;
63 import org.opendaylight.yangtools.concepts.Registration;
64 import org.opendaylight.yangtools.yang.binding.DataObject;
65 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 @Singleton
70 // Non-final because of Mockito.spy()
71 public class DefaultBgpDeployer implements DataTreeChangeListener<Bgp>, PeerGroupConfigLoader, AutoCloseable {
72     private static final Logger LOG = LoggerFactory.getLogger(DefaultBgpDeployer.class);
73
74     private final InstanceIdentifier<NetworkInstance> networkInstanceIId;
75     private final BGPTableTypeRegistryConsumer tableTypeRegistry;
76     private final ClusterSingletonServiceProvider provider;
77     private final RpcProviderService rpcRegistry;
78     private final RIBExtensionConsumerContext ribExtensionConsumerContext;
79     private final BGPDispatcher bgpDispatcher;
80     private final BGPRibRoutingPolicyFactory routingPolicyFactory;
81     private final BGPStateProviderRegistry stateProviderRegistry;
82     private final CodecsRegistry codecsRegistry;
83     private final DOMDataBroker domDataBroker;
84     private final DataBroker dataBroker;
85
86     @GuardedBy("this")
87     private final Map<InstanceIdentifier<Bgp>, BGPClusterSingletonService> bgpCss = new HashMap<>();
88     private final LoadingCache<InstanceIdentifier<PeerGroup>, Optional<PeerGroup>> peerGroups =
89         CacheBuilder.newBuilder().build(new CacheLoader<InstanceIdentifier<PeerGroup>, Optional<PeerGroup>>() {
90             @Override
91             public Optional<PeerGroup> load(final InstanceIdentifier<PeerGroup> key)
92                     throws ExecutionException, InterruptedException {
93                 return loadPeerGroup(key);
94             }
95         });
96     private final String networkInstanceName;
97     private Registration registration;
98     @GuardedBy("this")
99     private boolean closed;
100
101     @Inject
102     public DefaultBgpDeployer(final String networkInstanceName,
103                               final ClusterSingletonServiceProvider provider,
104                               final RpcProviderService rpcRegistry,
105                               final RIBExtensionConsumerContext ribExtensionConsumerContext,
106                               final BGPDispatcher bgpDispatcher,
107                               final BGPRibRoutingPolicyFactory routingPolicyFactory,
108                               final CodecsRegistry codecsRegistry,
109                               final DOMDataBroker domDataBroker,
110                               final DataBroker dataBroker,
111                               final BGPTableTypeRegistryConsumer tableTypeRegistry,
112                               final BGPStateProviderRegistry stateProviderRegistry) {
113         this.dataBroker = requireNonNull(dataBroker);
114         this.provider = requireNonNull(provider);
115         this.networkInstanceName = requireNonNull(networkInstanceName);
116         this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
117         this.stateProviderRegistry = requireNonNull(stateProviderRegistry);
118         this.rpcRegistry = requireNonNull(rpcRegistry);
119         this.ribExtensionConsumerContext = requireNonNull(ribExtensionConsumerContext);
120         this.bgpDispatcher = requireNonNull(bgpDispatcher);
121         this.routingPolicyFactory = requireNonNull(routingPolicyFactory);
122         this.codecsRegistry = requireNonNull(codecsRegistry);
123         this.domDataBroker = requireNonNull(domDataBroker);
124         networkInstanceIId =
125             InstanceIdentifier.builderOfInherited(OpenconfigNetworkInstanceData.class, NetworkInstances.class).build()
126                 .child(NetworkInstance.class, new NetworkInstanceKey(this.networkInstanceName));
127         initializeNetworkInstance(dataBroker, networkInstanceIId).addCallback(new FutureCallback<CommitInfo>() {
128             @Override
129             public void onSuccess(final CommitInfo result) {
130                 LOG.debug("Network Instance {} initialized successfully.", networkInstanceName);
131             }
132
133             @Override
134             public void onFailure(final Throwable throwable) {
135                 LOG.error("Failed to initialize Network Instance {}.", networkInstanceName, throwable);
136             }
137         }, MoreExecutors.directExecutor());
138     }
139
140     @PostConstruct
141     // Split out of constructor to support partial mocking
142     public synchronized void init() {
143         registration = dataBroker.registerTreeChangeListener(
144                 DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION,
145                         networkInstanceIId.child(Protocols.class).child(Protocol.class)
146                                 .augmentation(NetworkInstanceProtocol.class).child(Bgp.class)), this);
147         LOG.info("BGP Deployer {} started.", networkInstanceName);
148     }
149
150     private Optional<PeerGroup> loadPeerGroup(final InstanceIdentifier<PeerGroup> peerGroupIid)
151             throws ExecutionException, InterruptedException {
152         final FluentFuture<Optional<PeerGroup>> future;
153         try (ReadTransaction tx = dataBroker.newReadOnlyTransaction()) {
154             future = tx.read(LogicalDatastoreType.CONFIGURATION, peerGroupIid);
155         }
156         return future.get();
157     }
158
159     @Override
160     public synchronized void onDataTreeChanged(final List<DataTreeModification<Bgp>> changes) {
161         if (closed) {
162             LOG.trace("BGP Deployer was already closed, skipping changes.");
163             return;
164         }
165
166         for (var dataTreeModification : changes) {
167             final InstanceIdentifier<Bgp> rootIdentifier = dataTreeModification.getRootPath().path();
168             final DataObjectModification<Bgp> rootNode = dataTreeModification.getRootNode();
169             final List<DataObjectModification<? extends DataObject>> deletedConfig = rootNode.modifiedChildren()
170                 .stream()
171                 .filter(mod -> mod.modificationType() == DataObjectModification.ModificationType.DELETE)
172                 .collect(Collectors.toList());
173             final List<DataObjectModification<? extends DataObject>> changedConfig = rootNode.modifiedChildren()
174                 .stream()
175                 .filter(mod -> mod.modificationType() != DataObjectModification.ModificationType.DELETE)
176                 .collect(Collectors.toList());
177             handleDeletions(deletedConfig, rootIdentifier);
178             handleModifications(changedConfig, rootIdentifier);
179         }
180     }
181
182     private void handleModifications(final List<DataObjectModification<? extends DataObject>> changedConfig,
183                                      final InstanceIdentifier<Bgp> rootIdentifier) {
184         final List<DataObjectModification<? extends DataObject>> globalMod = changedConfig.stream()
185                 .filter(mod -> mod.dataType().equals(Global.class))
186                 .collect(Collectors.toList());
187         final List<DataObjectModification<? extends DataObject>> peerMod = changedConfig.stream()
188                 .filter(mod -> !mod.dataType().equals(Global.class))
189                 .collect(Collectors.toList());
190         if (!globalMod.isEmpty()) {
191             handleGlobalChange(globalMod, rootIdentifier);
192         }
193         if (!peerMod.isEmpty()) {
194             handlePeersChange(peerMod, rootIdentifier);
195         }
196     }
197
198     private void handleDeletions(final List<DataObjectModification<? extends DataObject>> deletedConfig,
199                                  final InstanceIdentifier<Bgp> rootIdentifier) {
200         final List<DataObjectModification<? extends DataObject>> globalMod = deletedConfig.stream()
201                 .filter(mod -> mod.dataType().equals(Global.class))
202                 .collect(Collectors.toList());
203         final List<DataObjectModification<? extends DataObject>> peerMod = deletedConfig.stream()
204                 .filter(mod -> !mod.dataType().equals(Global.class))
205                 .collect(Collectors.toList());
206         if (!globalMod.isEmpty()) {
207             handleGlobalChange(globalMod, rootIdentifier);
208         }
209         if (!peerMod.isEmpty()) {
210             handlePeersChange(peerMod, rootIdentifier);
211         }
212     }
213
214     private void handleGlobalChange(
215             final List<DataObjectModification<? extends DataObject>> config,
216             final InstanceIdentifier<Bgp> rootIdentifier) {
217         for (final DataObjectModification<? extends DataObject> dataObjectModification : config) {
218             onGlobalChanged((DataObjectModification<Global>) dataObjectModification, rootIdentifier);
219         }
220     }
221
222     private void handlePeersChange(
223             final List<DataObjectModification<? extends DataObject>> config,
224             final InstanceIdentifier<Bgp> rootIdentifier) {
225         for (final DataObjectModification<? extends DataObject> dataObjectModification : config) {
226             if (dataObjectModification.dataType().equals(Neighbors.class)) {
227                 onNeighborsChanged((DataObjectModification<Neighbors>) dataObjectModification, rootIdentifier);
228             } else if (dataObjectModification.dataType().equals(PeerGroups.class)) {
229                 rebootNeighbors((DataObjectModification<PeerGroups>) dataObjectModification);
230             }
231         }
232     }
233
234     private synchronized void rebootNeighbors(final DataObjectModification<PeerGroups> dataObjectModification) {
235         PeerGroups extPeerGroups = dataObjectModification.dataAfter();
236         if (extPeerGroups == null) {
237             extPeerGroups = dataObjectModification.dataBefore();
238         }
239         if (extPeerGroups == null) {
240             return;
241         }
242         for (final PeerGroup peerGroup : extPeerGroups.nonnullPeerGroup().values()) {
243             bgpCss.values().forEach(css -> css.restartPeerGroup(peerGroup.getPeerGroupName()));
244         }
245     }
246
247     @Override
248     @PreDestroy
249     @SuppressWarnings("checkstyle:illegalCatch")
250     public synchronized void close() {
251         LOG.info("Closing BGP Deployer.");
252         if (registration != null) {
253             registration.close();
254             registration = null;
255         }
256         closed = true;
257
258         bgpCss.values().iterator().forEachRemaining(service -> {
259             try {
260                 service.close();
261             } catch (Exception e) {
262                 LOG.warn("Failed to close BGP Cluster Singleton Service.", e);
263             }
264         });
265     }
266
267     private static FluentFuture<? extends CommitInfo> initializeNetworkInstance(
268             final DataBroker dataBroker, final InstanceIdentifier<NetworkInstance> networkInstance) {
269         final WriteTransaction wTx = dataBroker.newWriteOnlyTransaction();
270         wTx.merge(LogicalDatastoreType.CONFIGURATION, networkInstance,
271                 new NetworkInstanceBuilder().setName(networkInstance.firstKeyOf(NetworkInstance.class).getName())
272                         .setProtocols(new ProtocolsBuilder().build()).build());
273         return wTx.commit();
274     }
275
276     synchronized void onGlobalChanged(final DataObjectModification<Global> dataObjectModification,
277                                       final InstanceIdentifier<Bgp> bgpInstanceIdentifier) {
278         getBgpClusterSingleton(bgpInstanceIdentifier).onGlobalChanged(dataObjectModification);
279     }
280
281     synchronized void onNeighborsChanged(final DataObjectModification<Neighbors> dataObjectModification,
282                                          final InstanceIdentifier<Bgp> bgpInstanceIdentifier) {
283         getBgpClusterSingleton(bgpInstanceIdentifier).onNeighborsChanged(dataObjectModification);
284     }
285
286     @VisibleForTesting
287     synchronized BGPClusterSingletonService getBgpClusterSingleton(
288             final InstanceIdentifier<Bgp> bgpInstanceIdentifier) {
289         BGPClusterSingletonService old = bgpCss.get(bgpInstanceIdentifier);
290         if (old == null) {
291             old = new BGPClusterSingletonService(this, provider, tableTypeRegistry,
292                     rpcRegistry, ribExtensionConsumerContext, bgpDispatcher, routingPolicyFactory,
293                     codecsRegistry, stateProviderRegistry, domDataBroker, bgpInstanceIdentifier);
294             bgpCss.put(bgpInstanceIdentifier, old);
295         }
296         return old;
297     }
298
299     @Override
300     public PeerGroup getPeerGroup(final InstanceIdentifier<Bgp> bgpIid, final String peerGroupName) {
301         final InstanceIdentifier<PeerGroup> peerGroupsIid = bgpIid.child(PeerGroups.class)
302                 .child(PeerGroup.class, new PeerGroupKey(peerGroupName));
303         return peerGroups.getUnchecked(peerGroupsIid).orElse(null);
304     }
305
306 }