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