2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.bgp.rib.impl;
10 import static java.util.Objects.requireNonNull;
12 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13 import com.google.common.base.Verify;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableMap;
16 import com.google.common.collect.ImmutableSet;
17 import com.google.common.util.concurrent.FluentFuture;
18 import com.google.common.util.concurrent.FutureCallback;
19 import com.google.common.util.concurrent.MoreExecutors;
20 import java.util.Collection;
21 import java.util.List;
23 import java.util.Optional;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.atomic.LongAdder;
27 import javax.annotation.Nonnull;
28 import javax.annotation.concurrent.GuardedBy;
29 import javax.annotation.concurrent.NotThreadSafe;
30 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
31 import org.opendaylight.controller.md.sal.dom.api.ClusteredDOMDataTreeChangeListener;
32 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
33 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
34 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
35 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
36 import org.opendaylight.mdsal.common.api.CommitInfo;
37 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
38 import org.opendaylight.protocol.bgp.parser.impl.message.update.CommunityUtil;
39 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
40 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContext;
41 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
42 import org.opendaylight.protocol.bgp.rib.impl.spi.RibOutRefresh;
43 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesInstalledCounters;
44 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesReceivedCounters;
45 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
46 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy;
47 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters;
48 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.ClientRouteTargetContrainCache;
49 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.RouteTargetMembeshipUtil;
50 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.types.rev151009.AfiSafiType;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.Attributes;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.attributes.Communities;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.AdjRibIn;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.EffectiveRibIn;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.route.target.constrain.rev180618.RouteTargetConstrainSubsequentAddressFamily;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.route.target.constrain.rev180618.route.target.constrain.routes.route.target.constrain.routes.RouteTargetConstrainRoute;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv4AddressFamily;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv6AddressFamily;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.MplsLabeledVpnSubsequentAddressFamily;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.RouteTarget;
65 import org.opendaylight.yangtools.concepts.ListenerRegistration;
66 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
67 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
68 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
69 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
70 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
71 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
72 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
73 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
74 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
75 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
80 * Implementation of the BGP import policy. Listens on peer's Adj-RIB-In, inspects all inbound
81 * routes in the context of the advertising peer's role and applies the inbound policy.
84 * Inbound policy is applied as follows:
87 * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
88 * 2) check if a route is admissible based on attributes attached to it, as well as the
89 * advertising peer's role
90 * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
93 final class EffectiveRibInWriter implements PrefixesReceivedCounters, PrefixesInstalledCounters,
94 AutoCloseable, ClusteredDOMDataTreeChangeListener {
96 private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
97 static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME);
98 private static final TablesKey IVP4_VPN_TABLE_KEY = new TablesKey(Ipv4AddressFamily.class,
99 MplsLabeledVpnSubsequentAddressFamily.class);
100 private static final TablesKey IVP6_VPN_TABLE_KEY = new TablesKey(Ipv6AddressFamily.class,
101 MplsLabeledVpnSubsequentAddressFamily.class);
102 private static final ImmutableList<Communities> STALE_LLGR_COMMUNUTIES = ImmutableList.of(
103 StaleCommunities.STALE_LLGR);
104 private static final Attributes STALE_LLGR_ATTRIBUTES = new org.opendaylight.yang.gen.v1.urn.opendaylight.params
105 .xml.ns.yang.bgp.message.rev180329.path.attributes.AttributesBuilder()
106 .setCommunities(STALE_LLGR_COMMUNUTIES)
109 private final RIBSupportContextRegistry registry;
110 private final YangInstanceIdentifier peerIId;
111 private final YangInstanceIdentifier effRibTables;
112 private final DOMDataTreeChangeService service;
113 private final List<RouteTarget> rtMemberships;
114 private final RibOutRefresh vpnTableRefresher;
115 private final ClientRouteTargetContrainCache rtCache;
116 private ListenerRegistration<?> reg;
117 private DOMTransactionChain chain;
118 private final Map<TablesKey, LongAdder> prefixesReceived;
119 private final Map<TablesKey, LongAdder> prefixesInstalled;
120 private final BGPRibRoutingPolicy ribPolicies;
121 private final BGPRouteEntryImportParameters peerImportParameters;
122 private final BGPTableTypeRegistryConsumer tableTypeRegistry;
124 private FluentFuture<? extends CommitInfo> submitted;
125 private boolean rtMembershipsUpdated;
127 EffectiveRibInWriter(
128 final BGPRouteEntryImportParameters peer,
130 final DOMTransactionChain chain,
131 final YangInstanceIdentifier peerIId,
132 final Set<TablesKey> tables,
133 final BGPTableTypeRegistryConsumer tableTypeRegistry,
134 final List<RouteTarget> rtMemberships,
135 final ClientRouteTargetContrainCache rtCache) {
136 this.registry = requireNonNull(rib.getRibSupportContext());
137 this.chain = requireNonNull(chain);
138 this.peerIId = requireNonNull(peerIId);
139 this.effRibTables = this.peerIId.node(EffectiveRibIn.QNAME);
140 this.prefixesInstalled = buildPrefixesTables(tables);
141 this.prefixesReceived = buildPrefixesTables(tables);
142 this.ribPolicies = requireNonNull(rib.getRibPolicies());
143 this.service = requireNonNull(rib.getService());
144 this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
145 this.peerImportParameters = peer;
146 this.rtMemberships = rtMemberships;
147 this.rtCache = rtCache;
148 this.vpnTableRefresher = rib;
152 final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL,
153 this.peerIId.node(AdjRibIn.QNAME).node(Tables.QNAME));
154 LOG.debug("Registered Effective RIB on {}", this.peerIId);
155 this.reg = requireNonNull(this.service).registerDataTreeChangeListener(treeId, this);
158 private static Map<TablesKey, LongAdder> buildPrefixesTables(final Set<TablesKey> tables) {
159 final ImmutableMap.Builder<TablesKey, LongAdder> b = ImmutableMap.builder();
160 tables.forEach(table -> b.put(table, new LongAdder()));
165 public synchronized void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
166 if (this.chain == null) {
167 LOG.trace("Chain closed. Ignoring Changes : {}", changes);
171 LOG.trace("Data changed called to effective RIB. Change : {}", changes);
172 DOMDataWriteTransaction tx = null;
173 for (final DataTreeCandidate tc : changes) {
174 final YangInstanceIdentifier rootPath = tc.getRootPath();
175 final DataTreeCandidateNode root = tc.getRootNode();
176 for (final DataTreeCandidateNode table : root.getChildNodes()) {
178 tx = this.chain.newWriteOnlyTransaction();
180 changeDataTree(tx, rootPath, root, table);
184 final FluentFuture<? extends CommitInfo> future = tx.commit();
185 this.submitted = future;
186 future.addCallback(new FutureCallback<CommitInfo>() {
188 public void onSuccess(final CommitInfo result) {
189 LOG.trace("Successful commit");
193 public void onFailure(final Throwable trw) {
194 LOG.error("Failed commit", trw);
196 }, MoreExecutors.directExecutor());
198 //Refresh VPN Table if RT Memberships were updated
199 if (this.rtMembershipsUpdated) {
200 this.vpnTableRefresher.refreshTable(IVP4_VPN_TABLE_KEY, this.peerImportParameters.getFromPeerId());
201 this.vpnTableRefresher.refreshTable(IVP6_VPN_TABLE_KEY, this.peerImportParameters.getFromPeerId());
202 this.rtMembershipsUpdated = false;
207 private void changeDataTree(
208 final DOMDataWriteTransaction tx,
209 final YangInstanceIdentifier rootPath,
210 final DataTreeCandidateNode root,
211 final DataTreeCandidateNode table) {
212 final PathArgument lastArg = table.getIdentifier();
213 Verify.verify(lastArg instanceof NodeIdentifierWithPredicates,
214 "Unexpected type %s in path %s", lastArg.getClass(), rootPath);
215 final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
216 final ModificationType modificationType = root.getModificationType();
217 final RIBSupport ribSupport = this.registry.getRIBSupport(tableKey);
218 final TablesKey tk = ribSupport.getTablesKey();
219 final YangInstanceIdentifier effectiveTablePath = effectiveTablePath(tableKey);
221 switch (modificationType) {
224 processTableChildren(tx, ribSupport, effectiveTablePath, table.getChildNodes());
225 LOG.debug("Delete Effective Table {} modification type {}, ", effectiveTablePath, modificationType);
226 tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
230 final RIBSupportContext ctx = this.registry.getRIBSupportContext(tk);
231 LOG.trace("Create Empty table {}", tk);
232 ctx.createEmptyTableStructure(tx, effectiveTablePath);
233 processTableChildren(tx, ribSupport, effectiveTablePath, table.getChildNodes());
235 case SUBTREE_MODIFIED:
236 processTableChildren(tx, ribSupport, effectiveTablePath, table.getChildNodes());
239 LOG.info("Ignoring spurious notification on {} data {}", rootPath, table);
242 LOG.warn("Ignoring unhandled root {}", table);
247 private void processTableChildren(
248 final DOMDataWriteTransaction tx,
249 final RIBSupport ribSupport,
250 final YangInstanceIdentifier effectiveTablePath,
251 final Collection<DataTreeCandidateNode> children) {
252 for (final DataTreeCandidateNode child : children) {
253 final PathArgument childIdentifier = child.getIdentifier();
254 final Optional<NormalizedNode<?, ?>> childDataAfter = child.getDataAfter();
255 LOG.debug("Process table {} type {}, dataAfter {}, dataBefore {}", childIdentifier, child
256 .getModificationType(), childDataAfter, child.getDataBefore());
257 final YangInstanceIdentifier routesPath = effectiveTablePath.node(childIdentifier);
258 switch (child.getModificationType()) {
260 LOG.debug("Route deleted. routeId={}", routesPath);
261 processTableChildrenDelete(child, childIdentifier, tx, ribSupport, routesPath);
264 LOG.debug("Route disappeared. routeId={}", routesPath);
265 processTableChildrenDelete(child, childIdentifier, tx, ribSupport, routesPath);
270 case SUBTREE_MODIFIED:
271 processModifiedRouteTables(child, childIdentifier, tx, ribSupport, routesPath, childDataAfter);
275 writeRouteTables(child, childIdentifier, tx, ribSupport, routesPath, childDataAfter);
278 LOG.warn("Ignoring unhandled child {}", child);
284 private void processTableChildrenDelete(
285 final DataTreeCandidateNode child,
286 final PathArgument childIdentifier,
287 final DOMDataWriteTransaction tx,
288 final RIBSupport ribSupport,
289 final YangInstanceIdentifier routesPath) {
290 if (TABLE_ROUTES.equals(childIdentifier)) {
291 final Collection<DataTreeCandidateNode> changedRoutes = ribSupport.changedRoutes(child);
292 if (!changedRoutes.isEmpty()) {
293 for (final DataTreeCandidateNode route : changedRoutes) {
294 final NormalizedNode<?, ?> routeBefore = route.getDataBefore().orElse(null);
295 if (routeBefore != null) {
296 final YangInstanceIdentifier routePath = ribSupport.routePath(routesPath, route.getIdentifier());
297 handleRouteTarget(ModificationType.DELETE, ribSupport, routePath, routeBefore);
298 final TablesKey tablesKey = ribSupport.getTablesKey();
299 CountersUtil.decrement(this.prefixesInstalled.get(tablesKey), tablesKey);
304 tx.delete(LogicalDatastoreType.OPERATIONAL, routesPath);
307 private void processModifiedRouteTables(
308 final DataTreeCandidateNode child,
309 final PathArgument childIdentifier,
310 final DOMDataWriteTransaction tx,
311 final RIBSupport ribSupport,
312 final YangInstanceIdentifier routesPath,
313 final Optional<NormalizedNode<?, ?>> childDataAfter) {
314 if (TABLE_ROUTES.equals(childIdentifier)) {
315 final Collection<DataTreeCandidateNode> changedRoutes = ribSupport.changedRoutes(child);
316 if (!changedRoutes.isEmpty()) {
317 for (final DataTreeCandidateNode route : changedRoutes) {
318 processRoute(tx, ribSupport, routesPath.getParent(), route);
322 tx.put(LogicalDatastoreType.OPERATIONAL, routesPath, childDataAfter.get());
326 private void writeRouteTables(
327 final DataTreeCandidateNode child,
328 final PathArgument childIdentifier,
329 final DOMDataWriteTransaction tx,
330 final RIBSupport ribSupport,
331 final YangInstanceIdentifier routesPath,
332 final Optional<NormalizedNode<?, ?>> childDataAfter) {
333 if (TABLE_ROUTES.equals(childIdentifier)) {
334 final Collection<DataTreeCandidateNode> changedRoutes = ribSupport.changedRoutes(child);
335 if (!changedRoutes.isEmpty()) {
336 tx.put(LogicalDatastoreType.OPERATIONAL, routesPath, childDataAfter.get());
337 // Routes are special, as they may end up being filtered. The previous put conveniently
338 // ensured that we have them in at target, so a subsequent delete will not fail :)
339 for (final DataTreeCandidateNode route : changedRoutes) {
340 processRoute(tx, ribSupport, routesPath.getParent(), route);
346 private void processRoute(
347 final DOMDataWriteTransaction tx,
348 final RIBSupport ribSupport,
349 final YangInstanceIdentifier routesPath,
350 final DataTreeCandidateNode route) {
351 LOG.debug("Process route {}", route.getIdentifier());
352 final YangInstanceIdentifier routePath = ribSupport.routePath(routesPath, route.getIdentifier());
353 final TablesKey tablesKey = ribSupport.getTablesKey();
354 switch (route.getModificationType()) {
357 deleteRoute(tx, ribSupport, routePath, route.getDataBefore().orElse(null), tablesKey);
363 case SUBTREE_MODIFIED:
365 CountersUtil.increment(this.prefixesReceived.get(tablesKey), tablesKey);
366 // Lookup per-table attributes from RIBSupport
367 final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(route.getDataAfter(),
368 ribSupport.routeAttributesIdentifier()).orElse(null);
369 final Attributes routeAttrs = ribSupport.attributeFromContainerNode(advertisedAttrs);
370 final Optional<Attributes> optEffAtt;
371 // In case we want to add LLGR_STALE we do not process route through policies since it may be
372 // considered as received with LLGR_STALE from peer which is not true.
373 final boolean longLivedStale = false;
374 if (longLivedStale) {
375 // LLGR procedures are in effect. If the route is tagged with NO_LLGR, it needs to be removed.
376 final List<Communities> effCommunities = routeAttrs.getCommunities();
377 if (effCommunities != null && effCommunities.contains(CommunityUtil.NO_LLGR)) {
378 deleteRoute(tx, ribSupport, routePath, route.getDataBefore().orElse(null), tablesKey);
381 optEffAtt = Optional.of(wrapLongLivedStale(routeAttrs));
383 final Class<? extends AfiSafiType> afiSafiType
384 = tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey()).get();
385 optEffAtt = this.ribPolicies
386 .applyImportPolicies(this.peerImportParameters, routeAttrs, afiSafiType);
388 if (!optEffAtt.isPresent()) {
389 deleteRoute(tx, ribSupport, routePath, route.getDataBefore().orElse(null), tablesKey);
392 final NormalizedNode<?, ?> routeChanged = route.getDataAfter().get();
393 handleRouteTarget(ModificationType.WRITE, ribSupport, routePath, routeChanged);
394 tx.put(LogicalDatastoreType.OPERATIONAL, routePath, routeChanged);
395 CountersUtil.increment(this.prefixesInstalled.get(tablesKey), tablesKey);
397 final YangInstanceIdentifier attPath = routePath.node(ribSupport.routeAttributesIdentifier());
398 final ContainerNode finalAttribute = ribSupport.attributeToContainerNode(attPath, optEffAtt.get());
399 tx.put(LogicalDatastoreType.OPERATIONAL, attPath, finalAttribute);
402 LOG.warn("Ignoring unhandled route {}", route);
407 private void deleteRoute(
408 final DOMDataWriteTransaction tx,
409 final RIBSupport ribSupport,
410 final YangInstanceIdentifier routeIdPath,
411 final NormalizedNode<?, ?> route,
412 final TablesKey tablesKey) {
413 handleRouteTarget(ModificationType.DELETE, ribSupport, routeIdPath, route);
414 tx.delete(LogicalDatastoreType.OPERATIONAL, routeIdPath);
415 LOG.debug("Route deleted. routeId={}", routeIdPath);
416 CountersUtil.decrement(this.prefixesInstalled.get(tablesKey), tablesKey);
419 private void handleRouteTarget(
420 final ModificationType modificationType,
421 final RIBSupport ribSupport,
422 final YangInstanceIdentifier routeIdPath,
423 final NormalizedNode<?, ?> route) {
424 if (ribSupport.getSafi() == RouteTargetConstrainSubsequentAddressFamily.class) {
425 final RouteTargetConstrainRoute rtc =
426 (RouteTargetConstrainRoute) ribSupport.fromNormalizedNode(routeIdPath, route);
427 final RouteTarget rtMembership = RouteTargetMembeshipUtil.getRT(rtc);
428 if (ModificationType.DELETE == modificationType) {
429 if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
430 this.rtCache.uncacheRoute(rtc);
432 this.rtMemberships.remove(rtMembership);
434 if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
435 this.rtCache.cacheRoute(rtc);
437 this.rtMemberships.add(rtMembership);
439 this.rtMembershipsUpdated = true;
443 @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
444 private static Attributes wrapLongLivedStale(final Attributes attrs) {
446 return STALE_LLGR_ATTRIBUTES;
449 final List<Communities> oldCommunities = attrs.getCommunities();
450 final List<Communities> newCommunities;
451 if (oldCommunities != null) {
452 if (oldCommunities.contains(StaleCommunities.STALE_LLGR)) {
455 newCommunities = StaleCommunities.create(oldCommunities);
457 newCommunities = STALE_LLGR_COMMUNUTIES;
460 return new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329
461 .path.attributes.AttributesBuilder(attrs).setCommunities(newCommunities).build();
464 private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates tableKey) {
465 return this.effRibTables.node(Tables.QNAME).node(tableKey);
469 public synchronized void close() {
470 if (this.reg != null) {
474 if (this.submitted != null) {
476 this.submitted.get();
477 } catch (final InterruptedException | ExecutionException throwable) {
478 LOG.error("Write routes failed", throwable);
481 if (this.chain != null) {
485 this.prefixesReceived.values().forEach(LongAdder::reset);
486 this.prefixesInstalled.values().forEach(LongAdder::reset);
490 public long getPrefixedReceivedCount(final TablesKey tablesKey) {
491 final LongAdder counter = this.prefixesReceived.get(tablesKey);
492 if (counter == null) {
495 return counter.longValue();
499 public Set<TablesKey> getTableKeys() {
500 return ImmutableSet.copyOf(this.prefixesReceived.keySet());
504 public boolean isSupported(final TablesKey tablesKey) {
505 return this.prefixesReceived.containsKey(tablesKey);
509 public long getPrefixedInstalledCount(final TablesKey tablesKey) {
510 final LongAdder counter = this.prefixesInstalled.get(tablesKey);
511 if (counter == null) {
514 return counter.longValue();
518 public long getTotalPrefixesInstalled() {
519 return this.prefixesInstalled.values().stream().mapToLong(LongAdder::longValue).sum();