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 com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ADJRIBIN;
13 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.EFFRIBIN;
14 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ROUTES;
15 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.TABLES;
17 import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.ImmutableMap;
19 import com.google.common.collect.ImmutableSet;
20 import com.google.common.util.concurrent.FluentFuture;
21 import com.google.common.util.concurrent.FutureCallback;
22 import com.google.common.util.concurrent.MoreExecutors;
23 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
24 import java.util.Collection;
25 import java.util.List;
27 import java.util.Optional;
29 import java.util.concurrent.ExecutionException;
30 import java.util.concurrent.atomic.LongAdder;
31 import javax.annotation.Nonnull;
32 import javax.annotation.concurrent.GuardedBy;
33 import javax.annotation.concurrent.NotThreadSafe;
34 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
35 import org.opendaylight.controller.md.sal.dom.api.ClusteredDOMDataTreeChangeListener;
36 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
37 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
38 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
39 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
40 import org.opendaylight.mdsal.common.api.CommitInfo;
41 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
42 import org.opendaylight.protocol.bgp.parser.impl.message.update.CommunityUtil;
43 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
44 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContext;
45 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
46 import org.opendaylight.protocol.bgp.rib.impl.spi.RibOutRefresh;
47 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesInstalledCounters;
48 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesReceivedCounters;
49 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
50 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy;
51 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters;
52 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.ClientRouteTargetContrainCache;
53 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.RouteTargetMembeshipUtil;
54 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.types.rev151009.AfiSafiType;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.Attributes;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.attributes.Communities;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
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.NodeIdentifierWithPredicates;
68 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
69 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
70 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
71 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
72 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
73 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
74 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
79 * Implementation of the BGP import policy. Listens on peer's Adj-RIB-In, inspects all inbound
80 * routes in the context of the advertising peer's role and applies the inbound policy.
83 * Inbound policy is applied as follows:
86 * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
87 * 2) check if a route is admissible based on attributes attached to it, as well as the
88 * advertising peer's role
89 * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
92 final class EffectiveRibInWriter implements PrefixesReceivedCounters, PrefixesInstalledCounters,
93 AutoCloseable, ClusteredDOMDataTreeChangeListener {
95 private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
96 private static final TablesKey IVP4_VPN_TABLE_KEY = new TablesKey(Ipv4AddressFamily.class,
97 MplsLabeledVpnSubsequentAddressFamily.class);
98 private static final TablesKey IVP6_VPN_TABLE_KEY = new TablesKey(Ipv6AddressFamily.class,
99 MplsLabeledVpnSubsequentAddressFamily.class);
100 private static final ImmutableList<Communities> STALE_LLGR_COMMUNUTIES = ImmutableList.of(
101 StaleCommunities.STALE_LLGR);
102 private static final Attributes STALE_LLGR_ATTRIBUTES = new org.opendaylight.yang.gen.v1.urn.opendaylight.params
103 .xml.ns.yang.bgp.message.rev180329.path.attributes.AttributesBuilder()
104 .setCommunities(STALE_LLGR_COMMUNUTIES)
107 private final RIBSupportContextRegistry registry;
108 private final YangInstanceIdentifier peerIId;
109 private final YangInstanceIdentifier effRibTables;
110 private final DOMDataTreeChangeService service;
111 private final List<RouteTarget> rtMemberships;
112 private final RibOutRefresh vpnTableRefresher;
113 private final ClientRouteTargetContrainCache rtCache;
114 private ListenerRegistration<?> reg;
115 private DOMTransactionChain chain;
116 private final Map<TablesKey, LongAdder> prefixesReceived;
117 private final Map<TablesKey, LongAdder> prefixesInstalled;
118 private final BGPRibRoutingPolicy ribPolicies;
119 private final BGPRouteEntryImportParameters peerImportParameters;
120 private final BGPTableTypeRegistryConsumer tableTypeRegistry;
122 private FluentFuture<? extends CommitInfo> submitted;
123 private boolean rtMembershipsUpdated;
125 EffectiveRibInWriter(
126 final BGPRouteEntryImportParameters peer,
128 final DOMTransactionChain chain,
129 final YangInstanceIdentifier peerIId,
130 final Set<TablesKey> tables,
131 final BGPTableTypeRegistryConsumer tableTypeRegistry,
132 final List<RouteTarget> rtMemberships,
133 final ClientRouteTargetContrainCache rtCache) {
134 this.registry = requireNonNull(rib.getRibSupportContext());
135 this.chain = requireNonNull(chain);
136 this.peerIId = requireNonNull(peerIId);
137 this.effRibTables = this.peerIId.node(EFFRIBIN);
138 this.prefixesInstalled = buildPrefixesTables(tables);
139 this.prefixesReceived = buildPrefixesTables(tables);
140 this.ribPolicies = requireNonNull(rib.getRibPolicies());
141 this.service = requireNonNull(rib.getService());
142 this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
143 this.peerImportParameters = peer;
144 this.rtMemberships = rtMemberships;
145 this.rtCache = rtCache;
146 this.vpnTableRefresher = rib;
150 final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL,
151 this.peerIId.node(ADJRIBIN).node(TABLES));
152 LOG.debug("Registered Effective RIB on {}", this.peerIId);
153 this.reg = requireNonNull(this.service).registerDataTreeChangeListener(treeId, this);
156 private static Map<TablesKey, LongAdder> buildPrefixesTables(final Set<TablesKey> tables) {
157 final ImmutableMap.Builder<TablesKey, LongAdder> b = ImmutableMap.builder();
158 tables.forEach(table -> b.put(table, new LongAdder()));
163 public synchronized void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
164 if (this.chain == null) {
165 LOG.trace("Chain closed. Ignoring Changes : {}", changes);
169 LOG.trace("Data changed called to effective RIB. Change : {}", changes);
170 DOMDataWriteTransaction tx = null;
171 for (final DataTreeCandidate tc : changes) {
172 final YangInstanceIdentifier rootPath = tc.getRootPath();
173 final DataTreeCandidateNode root = tc.getRootNode();
174 for (final DataTreeCandidateNode table : root.getChildNodes()) {
176 tx = this.chain.newWriteOnlyTransaction();
178 changeDataTree(tx, rootPath, root, table);
183 final FluentFuture<? extends CommitInfo> future = tx.commit();
184 this.submitted = future;
185 future.addCallback(new FutureCallback<CommitInfo>() {
187 public void onSuccess(final CommitInfo result) {
188 LOG.trace("Successful commit");
192 public void onFailure(final Throwable trw) {
193 LOG.error("Failed commit", trw);
195 }, 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(final DOMDataWriteTransaction tx, final YangInstanceIdentifier rootPath,
208 final DataTreeCandidateNode root, final DataTreeCandidateNode table) {
209 final PathArgument lastArg = table.getIdentifier();
210 verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(),
212 final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
213 final RIBSupportContext ribContext = this.registry.getRIBSupportContext(tableKey);
214 if (ribContext == null) {
215 LOG.warn("Table {} is not supported, ignoring event", tableKey);
219 final YangInstanceIdentifier effectiveTablePath = effectiveTablePath(tableKey);
220 final ModificationType modificationType = root.getModificationType();
221 LOG.debug("Effective table {} modification type {}", effectiveTablePath, modificationType);
222 switch (modificationType) {
225 deleteTable(tx, ribContext, effectiveTablePath, table);
229 writeTable(tx, ribContext, effectiveTablePath, table);
231 case SUBTREE_MODIFIED:
232 modifyTable(tx, ribContext, effectiveTablePath, table);
235 LOG.info("Ignoring spurious notification on {} data {}", rootPath, table);
238 LOG.warn("Ignoring unhandled root {}", table);
243 private void deleteTable(final DOMDataWriteTransaction tx, final RIBSupportContext ribContext,
244 final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
245 processTableChildren(tx, ribContext.getRibSupport(), effectiveTablePath, table.getChildNodes());
246 LOG.debug("Delete Effective Table {}", effectiveTablePath);
247 tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
250 private void modifyTable(final DOMDataWriteTransaction tx, final RIBSupportContext ribContext,
251 final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
252 LOG.debug("Modify Effective Table {}", effectiveTablePath);
253 processTableChildren(tx, ribContext.getRibSupport(), effectiveTablePath, table.getChildNodes());
256 private void writeTable(final DOMDataWriteTransaction tx, final RIBSupportContext ribContext,
257 final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
258 LOG.debug("Write Effective Table {}", effectiveTablePath);
259 ribContext.createEmptyTableStructure(tx, effectiveTablePath);
260 processTableChildren(tx, ribContext.getRibSupport(), effectiveTablePath, table.getChildNodes());
263 private void processTableChildren(final DOMDataWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
264 final YangInstanceIdentifier effectiveTablePath, final Collection<DataTreeCandidateNode> children) {
265 for (final DataTreeCandidateNode child : children) {
266 final PathArgument childIdentifier = child.getIdentifier();
267 final ModificationType childModType = child.getModificationType();
268 final Optional<NormalizedNode<?, ?>> childDataAfter = child.getDataAfter();
269 LOG.debug("Process table {} type {}, dataAfter {}, dataBefore {}", childIdentifier, childModType,
270 childDataAfter, child.getDataBefore());
271 final YangInstanceIdentifier routesPath = effectiveTablePath.node(childIdentifier);
272 switch (childModType) {
275 processTableChildrenDelete(child, childIdentifier, tx, ribSupport, routesPath);
280 case SUBTREE_MODIFIED:
281 processModifiedRouteTables(child, childIdentifier, tx, ribSupport, routesPath, childDataAfter);
285 writeRouteTables(child, childIdentifier, tx, ribSupport, routesPath, childDataAfter);
288 LOG.warn("Ignoring unhandled child {}", child);
294 private void processTableChildrenDelete(final DataTreeCandidateNode child, final PathArgument childIdentifier,
295 final DOMDataWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
296 final YangInstanceIdentifier routesPath) {
297 if (ROUTES.equals(childIdentifier)) {
298 for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
299 final Optional<NormalizedNode<?, ?>> routeBefore = route.getDataBefore();
300 if (routeBefore.isPresent()) {
301 final YangInstanceIdentifier routePath = ribSupport.routePath(routesPath, route.getIdentifier());
302 handleRouteTarget(ModificationType.DELETE, ribSupport, routePath, routeBefore.get());
303 final TablesKey tablesKey = ribSupport.getTablesKey();
304 CountersUtil.decrement(this.prefixesInstalled.get(tablesKey), tablesKey);
308 tx.delete(LogicalDatastoreType.OPERATIONAL, routesPath);
311 private void processModifiedRouteTables(final DataTreeCandidateNode child, final PathArgument childIdentifier,
312 final DOMDataWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
313 final YangInstanceIdentifier routesPath, final Optional<NormalizedNode<?, ?>> childDataAfter) {
314 if (ROUTES.equals(childIdentifier)) {
315 for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
316 processRoute(tx, ribSupport, routesPath.getParent(), route);
319 tx.put(LogicalDatastoreType.OPERATIONAL, routesPath, childDataAfter.get());
323 private void writeRouteTables(final DataTreeCandidateNode child, final PathArgument childIdentifier,
324 final DOMDataWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
325 final YangInstanceIdentifier routesPath, final Optional<NormalizedNode<?, ?>> childDataAfter) {
326 if (ROUTES.equals(childIdentifier)) {
327 final Collection<DataTreeCandidateNode> changedRoutes = ribSupport.changedRoutes(child);
328 if (!changedRoutes.isEmpty()) {
329 tx.put(LogicalDatastoreType.OPERATIONAL, routesPath, childDataAfter.get());
330 // Routes are special, as they may end up being filtered. The previous put conveniently
331 // ensured that we have them in at target, so a subsequent delete will not fail :)
332 for (final DataTreeCandidateNode route : changedRoutes) {
333 processRoute(tx, ribSupport, routesPath.getParent(), route);
339 private void processRoute(final DOMDataWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
340 final YangInstanceIdentifier routesPath, final DataTreeCandidateNode route) {
341 LOG.debug("Process route {}", route.getIdentifier());
342 final YangInstanceIdentifier routePath = ribSupport.routePath(routesPath, route.getIdentifier());
343 final TablesKey tablesKey = ribSupport.getTablesKey();
344 switch (route.getModificationType()) {
347 deleteRoute(tx, ribSupport, routePath, route.getDataBefore().orElse(null), tablesKey);
353 case SUBTREE_MODIFIED:
355 CountersUtil.increment(this.prefixesReceived.get(tablesKey), tablesKey);
356 // Lookup per-table attributes from RIBSupport
357 final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(route.getDataAfter(),
358 ribSupport.routeAttributesIdentifier()).orElse(null);
359 final Attributes routeAttrs = ribSupport.attributeFromContainerNode(advertisedAttrs);
360 final Optional<Attributes> optEffAtt;
361 // In case we want to add LLGR_STALE we do not process route through policies since it may be
362 // considered as received with LLGR_STALE from peer which is not true.
363 final boolean longLivedStale = false;
364 if (longLivedStale) {
365 // LLGR procedures are in effect. If the route is tagged with NO_LLGR, it needs to be removed.
366 final List<Communities> effCommunities = routeAttrs.getCommunities();
367 if (effCommunities != null && effCommunities.contains(CommunityUtil.NO_LLGR)) {
368 deleteRoute(tx, ribSupport, routePath, route.getDataBefore().orElse(null), tablesKey);
371 optEffAtt = Optional.of(wrapLongLivedStale(routeAttrs));
373 final Class<? extends AfiSafiType> afiSafiType
374 = tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey()).get();
375 optEffAtt = this.ribPolicies
376 .applyImportPolicies(this.peerImportParameters, routeAttrs, afiSafiType);
378 if (!optEffAtt.isPresent()) {
379 deleteRoute(tx, ribSupport, routePath, route.getDataBefore().orElse(null), tablesKey);
382 final NormalizedNode<?, ?> routeChanged = route.getDataAfter().get();
383 handleRouteTarget(ModificationType.WRITE, ribSupport, routePath, routeChanged);
384 tx.put(LogicalDatastoreType.OPERATIONAL, routePath, routeChanged);
385 CountersUtil.increment(this.prefixesInstalled.get(tablesKey), tablesKey);
387 final YangInstanceIdentifier attPath = routePath.node(ribSupport.routeAttributesIdentifier());
388 final Attributes attToStore = optEffAtt.get();
389 if(!attToStore.equals(routeAttrs)) {
390 final ContainerNode finalAttribute = ribSupport.attributeToContainerNode(attPath, attToStore);
391 tx.put(LogicalDatastoreType.OPERATIONAL, attPath, finalAttribute);
395 LOG.warn("Ignoring unhandled route {}", route);
400 private void deleteRoute(final DOMDataWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
401 final YangInstanceIdentifier routeIdPath, final NormalizedNode<?, ?> route, final TablesKey tablesKey) {
402 handleRouteTarget(ModificationType.DELETE, ribSupport, routeIdPath, route);
403 tx.delete(LogicalDatastoreType.OPERATIONAL, routeIdPath);
404 LOG.debug("Route deleted. routeId={}", routeIdPath);
405 CountersUtil.decrement(this.prefixesInstalled.get(tablesKey), tablesKey);
408 private void handleRouteTarget(final ModificationType modificationType, final RIBSupport<?, ?, ?, ?> ribSupport,
409 final YangInstanceIdentifier routeIdPath, final NormalizedNode<?, ?> route) {
410 if (ribSupport.getSafi() == RouteTargetConstrainSubsequentAddressFamily.class) {
411 final RouteTargetConstrainRoute rtc =
412 (RouteTargetConstrainRoute) ribSupport.fromNormalizedNode(routeIdPath, route);
413 final RouteTarget rtMembership = RouteTargetMembeshipUtil.getRT(rtc);
414 if (ModificationType.DELETE == modificationType) {
415 if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
416 this.rtCache.uncacheRoute(rtc);
418 this.rtMemberships.remove(rtMembership);
420 if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
421 this.rtCache.cacheRoute(rtc);
423 this.rtMemberships.add(rtMembership);
425 this.rtMembershipsUpdated = true;
429 @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
430 private static Attributes wrapLongLivedStale(final Attributes attrs) {
432 return STALE_LLGR_ATTRIBUTES;
435 final List<Communities> oldCommunities = attrs.getCommunities();
436 final List<Communities> newCommunities;
437 if (oldCommunities != null) {
438 if (oldCommunities.contains(StaleCommunities.STALE_LLGR)) {
441 newCommunities = StaleCommunities.create(oldCommunities);
443 newCommunities = STALE_LLGR_COMMUNUTIES;
446 return new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329
447 .path.attributes.AttributesBuilder(attrs).setCommunities(newCommunities).build();
450 private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates tableKey) {
451 return this.effRibTables.node(TABLES).node(tableKey);
455 public synchronized void close() {
456 if (this.reg != null) {
460 if (this.submitted != null) {
462 this.submitted.get();
463 } catch (final InterruptedException | ExecutionException throwable) {
464 LOG.error("Write routes failed", throwable);
467 if (this.chain != null) {
471 this.prefixesReceived.values().forEach(LongAdder::reset);
472 this.prefixesInstalled.values().forEach(LongAdder::reset);
476 public long getPrefixedReceivedCount(final TablesKey tablesKey) {
477 final LongAdder counter = this.prefixesReceived.get(tablesKey);
478 if (counter == null) {
481 return counter.longValue();
485 public Set<TablesKey> getTableKeys() {
486 return ImmutableSet.copyOf(this.prefixesReceived.keySet());
490 public boolean isSupported(final TablesKey tablesKey) {
491 return this.prefixesReceived.containsKey(tablesKey);
495 public long getPrefixedInstalledCount(final TablesKey tablesKey) {
496 final LongAdder counter = this.prefixesInstalled.get(tablesKey);
497 if (counter == null) {
500 return counter.longValue();
504 public long getTotalPrefixesInstalled() {
505 return this.prefixesInstalled.values().stream().mapToLong(LongAdder::longValue).sum();