use of org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables in project netvirt by opendaylight.
the class NaptEventHandler method handleEvent.
// TODO Clean up the exception handling
@SuppressWarnings("checkstyle:IllegalCatch")
public void handleEvent(final NAPTEntryEvent naptEntryEvent) {
/*
Flow programming logic of the OUTBOUND NAPT TABLE :
1) Get the internal IP address, port number, router ID from the event.
2) Use the NAPT service getExternalAddressMapping() to get the External IP and the port.
3) Build the flow for replacing the Internal IP and port with the External IP and port.
a) Write the matching criteria.
b) Match the router ID in the metadata.
d) Write the VPN ID to the metadata.
e) Write the other data.
f) Set the apply actions instruction with the action setfield.
4) Write the flow to the OUTBOUND NAPT Table and forward to FIB table for routing the traffic.
Flow programming logic of the INBOUND NAPT TABLE :
Same as Outbound table logic except that :
1) Build the flow for replacing the External IP and port with the Internal IP and port.
2) Match the VPN ID in the metadata.
3) Write the router ID to the metadata.
5) Write the flow to the INBOUND NAPT Table and forward to FIB table for routing the traffic.
*/
try {
Uint32 routerId = naptEntryEvent.getRouterId();
String internalIpAddress = naptEntryEvent.getIpAddress();
int internalPort = naptEntryEvent.getPortNumber();
NAPTEntryEvent.Protocol protocol = naptEntryEvent.getProtocol();
String sourceIPPortKey = routerId + NatConstants.COLON_SEPARATOR + internalIpAddress + NatConstants.COLON_SEPARATOR + internalPort;
LOG.trace("handleEvent : Time Elapsed before procesing snat ({}:{}) packet is {} ms,routerId: {}," + "isPktProcessed:{}", internalIpAddress, internalPort, System.currentTimeMillis() - naptEntryEvent.getObjectCreationTime(), routerId, naptEntryEvent.isPktProcessed());
// Get the DPN ID
Uint64 dpnId = NatUtil.getPrimaryNaptfromRouterId(dataBroker, routerId);
Uint32 bgpVpnId = NatConstants.INVALID_ID;
if (dpnId == null) {
LOG.warn("handleEvent : dpnId is null. Assuming the router ID {} as the BGP VPN ID and " + "proceeding....", routerId);
bgpVpnId = routerId;
LOG.debug("handleEvent : BGP VPN ID {}", bgpVpnId);
String vpnName = NatUtil.getRouterName(dataBroker, bgpVpnId);
String routerName = NatUtil.getRouterIdfromVpnInstance(dataBroker, vpnName, internalIpAddress);
if (routerName == null) {
NaptPacketInHandler.removeIncomingPacketMap(sourceIPPortKey);
LOG.error("handleEvent: Unable to find router for VpnName {}. Droping packet for SNAT ({})" + "session", vpnName, sourceIPPortKey);
return;
}
routerId = NatUtil.getVpnId(dataBroker, routerName);
LOG.debug("handleEvent : Router ID {}", routerId);
dpnId = NatUtil.getPrimaryNaptfromRouterId(dataBroker, routerId);
if (dpnId == null) {
NaptPacketInHandler.removeIncomingPacketMap(sourceIPPortKey);
LOG.error("handleEvent: Unable to find router for VpnName {}. Droping packet for SNAT ({})" + "session", vpnName, sourceIPPortKey);
return;
}
}
if (naptEntryEvent.getOperation() == NAPTEntryEvent.Operation.ADD) {
LOG.debug("handleEvent : Inside Add operation of NaptEventHandler");
// Build and install the NAPT translation flows in the Outbound and Inbound NAPT tables
if (!naptEntryEvent.isPktProcessed()) {
// Get the External Gateway MAC Address
String extGwMacAddress = NatUtil.getExtGwMacAddFromRouterId(dataBroker, routerId);
if (extGwMacAddress != null) {
LOG.debug("handleEvent : External Gateway MAC address {} found for External Router ID {}", extGwMacAddress, routerId);
} else {
NaptPacketInHandler.removeIncomingPacketMap(sourceIPPortKey);
LOG.error("handleEvent: No External Gateway MAC address found for External Router ID {}." + "Droping packet for SNAT ({}) session", routerId, sourceIPPortKey);
return;
}
// Get the external network ID from the ExternalRouter model
Uuid networkId = NatUtil.getNetworkIdFromRouterId(dataBroker, routerId);
if (networkId == null) {
NaptPacketInHandler.removeIncomingPacketMap(sourceIPPortKey);
LOG.error("handleEvent: networkId is null. Droping packet for SNAT ({}) session", sourceIPPortKey);
return;
}
// Get the VPN ID from the ExternalNetworks model
Uuid vpnUuid = NatUtil.getVpnIdfromNetworkId(dataBroker, networkId);
if (vpnUuid == null) {
NaptPacketInHandler.removeIncomingPacketMap(sourceIPPortKey);
LOG.error("handleEvent: vpnUuid is null. Droping packet for SNAT ({}) session", sourceIPPortKey);
return;
}
Uint32 vpnId = NatUtil.getVpnId(dataBroker, vpnUuid.getValue());
SessionAddress internalAddress = new SessionAddress(internalIpAddress, internalPort);
// Get the external IP address for the corresponding internal IP address
SessionAddress externalAddress = naptManager.getExternalAddressMapping(routerId, internalAddress, naptEntryEvent.getProtocol());
if (externalAddress == null) {
NaptPacketInHandler.removeIncomingPacketMap(sourceIPPortKey);
LOG.error("handleEvent: externalAddress is null. Droping packet for SNAT ({}) session", sourceIPPortKey);
return;
}
Uint32 vpnIdFromExternalSubnet = getVpnIdFromExternalSubnet(routerId, externalAddress.getIpAddress());
if (vpnIdFromExternalSubnet != NatConstants.INVALID_ID) {
vpnId = vpnIdFromExternalSubnet;
}
// Added External Gateway MAC Address
Future<RpcResult<AddFlowOutput>> addFlowResult = buildAndInstallNatFlowsOptionalRpc(dpnId, NwConstants.INBOUND_NAPT_TABLE, vpnId, routerId, bgpVpnId, externalAddress, internalAddress, protocol, extGwMacAddress, true);
final Uint64 finalDpnId = dpnId;
final Uint32 finalVpnId = vpnId;
final Uint32 finalRouterId = routerId;
final Uint32 finalBgpVpnId = bgpVpnId;
Futures.addCallback(JdkFutureAdapters.listenInPoolThread(addFlowResult), new FutureCallback<RpcResult<AddFlowOutput>>() {
@Override
public void onSuccess(@Nullable RpcResult<AddFlowOutput> result) {
LOG.debug("handleEvent : Configured inbound rule for {} to {}", internalAddress, externalAddress);
Future<RpcResult<AddFlowOutput>> addFlowResult = buildAndInstallNatFlowsOptionalRpc(finalDpnId, NwConstants.OUTBOUND_NAPT_TABLE, finalVpnId, finalRouterId, finalBgpVpnId, internalAddress, externalAddress, protocol, extGwMacAddress, true);
Futures.addCallback(JdkFutureAdapters.listenInPoolThread(addFlowResult), new FutureCallback<RpcResult<AddFlowOutput>>() {
@Override
public void onSuccess(@Nullable RpcResult<AddFlowOutput> result) {
LOG.debug("handleEvent : Configured outbound rule, sending packet out" + "from {} to {}", internalAddress, externalAddress);
prepareAndSendPacketOut(naptEntryEvent, finalRouterId, sourceIPPortKey);
}
@Override
public void onFailure(@NonNull Throwable throwable) {
LOG.error("handleEvent : Error configuring outbound " + "SNAT flows using RPC for SNAT connection from {} to {}", internalAddress, externalAddress);
}
}, MoreExecutors.directExecutor());
}
@Override
public void onFailure(@NonNull Throwable throwable) {
LOG.error("handleEvent : Error configuring inbound SNAT flows " + "using RPC for SNAT connection from {} to {}", internalAddress, externalAddress);
}
}, MoreExecutors.directExecutor());
} else {
prepareAndSendPacketOut(naptEntryEvent, routerId, sourceIPPortKey);
}
LOG.trace("handleEvent : Time elapsed after Processsing snat ({}:{}) packet: {}ms,isPktProcessed:{} ", naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), System.currentTimeMillis() - naptEntryEvent.getObjectCreationTime(), naptEntryEvent.isPktProcessed());
} else {
LOG.debug("handleEvent : Inside delete Operation of NaptEventHandler");
handleFlowRemoved(naptEntryEvent, routerId, sourceIPPortKey, dpnId);
LOG.info("handleEvent : exited for removeEvent for IP {}, port {}, routerID : {}", naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), routerId);
}
} catch (Exception e) {
LOG.error("handleEvent :Exception in NaptEventHandler.handleEvent() payload {}", naptEntryEvent, e);
}
}
use of org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables in project netvirt by opendaylight.
the class ExternalRoutersListener method handleEnableSnat.
public void handleEnableSnat(Routers routers, Uint32 routerId, Uint64 primarySwitchId, Uint32 bgpVpnId, TypedWriteTransaction<Configuration> confTx) {
String routerName = routers.getRouterName();
LOG.info("handleEnableSnat : Handling SNAT for router {}", routerName);
naptManager.initialiseExternalCounter(routers, routerId);
subnetRegisterMapping(routers, routerId);
LOG.debug("handleEnableSnat:About to create and install outbound miss entry in Primary Switch {} for router {}", primarySwitchId, routerName);
ProviderTypes extNwProvType = NatEvpnUtil.getExtNwProvTypeFromRouterName(dataBroker, routerName, routers.getNetworkId());
if (extNwProvType == null) {
LOG.error("handleEnableSnat : External Network Provider Type missing");
return;
}
if (bgpVpnId != NatConstants.INVALID_ID) {
installFlowsWithUpdatedVpnId(primarySwitchId, routerName, bgpVpnId, routerId, routers.getNetworkId(), false, confTx, extNwProvType);
} else {
// write metadata and punt
installOutboundMissEntry(routerName, routerId, primarySwitchId, confTx);
handlePrimaryNaptSwitch(primarySwitchId, routerName, routerId, routers.getNetworkId(), confTx);
// Now install entries in SNAT tables to point to Primary for each router
List<Uint64> switches = naptSwitchSelector.getDpnsForVpn(routerName);
for (Uint64 dpnId : switches) {
// Handle switches and NAPT switches separately
if (!dpnId.equals(primarySwitchId)) {
LOG.debug("handleEnableSnat : Handle Ordinary switch");
handleSwitches(dpnId, routerName, routerId, primarySwitchId);
}
}
}
Collection<String> externalIps = NatUtil.getExternalIpsForRouter(dataBroker, routerId);
if (externalIps.isEmpty()) {
LOG.error("handleEnableSnat : Internal External mapping not found for router {}", routerName);
return;
} else {
for (String externalIpAddrPrefix : externalIps) {
LOG.debug("handleEnableSnat : Calling handleSnatReverseTraffic for primarySwitchId {}, " + "routerName {} and externalIpAddPrefix {}", primarySwitchId, routerName, externalIpAddrPrefix);
externalIpAddrPrefix = NatUtil.validateAndAddNetworkMask(externalIpAddrPrefix);
handleSnatReverseTraffic(confTx, primarySwitchId, routers, routerId, routerName, externalIpAddrPrefix);
}
}
LOG.debug("handleEnableSnat : Exit");
}
use of org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables in project bgpcep by opendaylight.
the class BmpMonitorImplTest method testMonitoringStation.
private Channel testMonitoringStation(final String remoteRouterIpAddr) throws InterruptedException, ExecutionException {
final Channel channel = connectTestClient(remoteRouterIpAddr, msgRegistry);
final RouterId routerId = getRouterId(remoteRouterIpAddr);
readDataOperational(getDataBroker(), MONITOR_IID, monitor -> {
// now find the current router instance
Router router = null;
for (final Router r : monitor.nonnullRouter().values()) {
if (routerId.equals(r.getRouterId())) {
router = r;
break;
}
}
assertNotNull(router);
assertEquals(Status.Down, router.getStatus());
assertNull(router.getPeer());
return router;
});
waitWriteAndFlushSuccess(channel.writeAndFlush(TestUtil.createInitMsg("description", "name", "some info")));
readDataOperational(getDataBroker(), MONITOR_IID, monitor -> {
Router retRouter = null;
for (final Router r : monitor.nonnullRouter().values()) {
if (routerId.equals(r.getRouterId())) {
retRouter = r;
break;
}
}
assertNotNull(retRouter);
assertEquals("some info;", retRouter.getInfo());
assertEquals("name", retRouter.getName());
assertEquals("description", retRouter.getDescription());
assertEquals(routerId, retRouter.getRouterId());
assertNull(retRouter.getPeer());
assertEquals(Status.Up, retRouter.getStatus());
return retRouter;
});
waitWriteAndFlushSuccess(channel.writeAndFlush(TestUtil.createPeerUpNotification(PEER1, true)));
final KeyedInstanceIdentifier<Router, RouterKey> routerIId = MONITOR_IID.child(Router.class, new RouterKey(routerId));
readDataOperational(getDataBroker(), routerIId, router -> {
final Map<PeerKey, Peer> peers = router.getPeer();
assertNotNull(peers);
assertEquals(1, peers.size());
final Peer peer = peers.values().iterator().next();
assertEquals(PeerType.Global, peer.getType());
assertEquals(PEER_ID, peer.getPeerId());
assertEquals(PEER1, peer.getBgpId());
assertEquals(TestUtil.IPV4_ADDRESS_10, peer.getAddress().getIpv4AddressNoZone());
assertEquals(TestUtil.PEER_AS, peer.getAs());
assertNull(peer.getPeerDistinguisher());
assertNull(peer.getStats());
assertNotNull(peer.getPrePolicyRib());
assertEquals(1, peer.getPrePolicyRib().nonnullTables().size());
final Tables prePolicyTable = peer.getPrePolicyRib().nonnullTables().values().iterator().next();
assertEquals(Ipv4AddressFamily.class, prePolicyTable.getAfi());
assertEquals(UnicastSubsequentAddressFamily.class, prePolicyTable.getSafi());
assertFalse(prePolicyTable.getAttributes().getUptodate());
assertNotNull(peer.getPostPolicyRib());
assertEquals(1, peer.getPostPolicyRib().nonnullTables().size());
final Tables postPolicyTable = peer.getPrePolicyRib().nonnullTables().values().iterator().next();
assertEquals(Ipv4AddressFamily.class, postPolicyTable.getAfi());
assertEquals(UnicastSubsequentAddressFamily.class, postPolicyTable.getSafi());
assertFalse(postPolicyTable.getAttributes().getUptodate());
assertNotNull(peer.getPeerSession());
final PeerSession peerSession = peer.getPeerSession();
assertEquals(TestUtil.IPV4_ADDRESS_10, peerSession.getLocalAddress().getIpv4AddressNoZone());
assertEquals(TestUtil.PEER_LOCAL_PORT, peerSession.getLocalPort());
assertEquals(TestUtil.PEER_REMOTE_PORT, peerSession.getRemotePort());
assertEquals(Status.Up, peerSession.getStatus());
assertNotNull(peerSession.getReceivedOpen());
assertNotNull(peerSession.getSentOpen());
return router;
});
final StatsReportsMessage statsMsg = TestUtil.createStatsReportMsg(PEER1);
waitWriteAndFlushSuccess(channel.writeAndFlush(statsMsg));
final KeyedInstanceIdentifier<Peer, PeerKey> peerIId = routerIId.child(Peer.class, new PeerKey(PEER_ID));
readDataOperational(getDataBroker(), peerIId.child(Stats.class), peerStats -> {
assertNotNull(peerStats.getTimestampSec());
final Tlvs tlvs = statsMsg.getTlvs();
assertEquals(tlvs.getAdjRibsInRoutesTlv().getCount(), peerStats.getAdjRibsInRoutes());
assertEquals(tlvs.getDuplicatePrefixAdvertisementsTlv().getCount(), peerStats.getDuplicatePrefixAdvertisements());
assertEquals(tlvs.getDuplicateWithdrawsTlv().getCount(), peerStats.getDuplicateWithdraws());
assertEquals(tlvs.getInvalidatedAsConfedLoopTlv().getCount(), peerStats.getInvalidatedAsConfedLoop());
assertEquals(tlvs.getInvalidatedAsPathLoopTlv().getCount(), peerStats.getInvalidatedAsPathLoop());
assertEquals(tlvs.getInvalidatedClusterListLoopTlv().getCount(), peerStats.getInvalidatedClusterListLoop());
assertEquals(tlvs.getInvalidatedOriginatorIdTlv().getCount(), peerStats.getInvalidatedOriginatorId());
assertEquals(tlvs.getLocRibRoutesTlv().getCount(), peerStats.getLocRibRoutes());
assertEquals(tlvs.getRejectedPrefixesTlv().getCount(), peerStats.getRejectedPrefixes());
assertEquals(tlvs.getPerAfiSafiAdjRibInTlv().getCount().toString(), peerStats.getPerAfiSafiAdjRibInRoutes().nonnullAfiSafi().values().iterator().next().getCount().toString());
assertEquals(tlvs.getPerAfiSafiLocRibTlv().getCount().toString(), peerStats.getPerAfiSafiLocRibRoutes().nonnullAfiSafi().values().iterator().next().getCount().toString());
return peerStats;
});
// route mirror message test
final RouteMirroringMessage routeMirrorMsg = TestUtil.createRouteMirrorMsg(PEER1);
waitWriteAndFlushSuccess(channel.writeAndFlush(routeMirrorMsg));
readDataOperational(getDataBroker(), peerIId.child(Mirrors.class), routeMirrors -> {
assertNotNull(routeMirrors.getTimestampSec());
return routeMirrors;
});
waitWriteAndFlushSuccess(channel.writeAndFlush(createRouteMonitMsg(false, PEER1, AdjRibInType.PrePolicy)));
waitWriteAndFlushSuccess(channel.writeAndFlush(createRouteMonMsgWithEndOfRibMarker(PEER1, AdjRibInType.PrePolicy)));
readDataOperational(getDataBroker(), peerIId.child(PrePolicyRib.class), prePolicyRib -> {
assertFalse(prePolicyRib.nonnullTables().isEmpty());
final Tables tables = prePolicyRib.nonnullTables().values().iterator().next();
assertTrue(tables.getAttributes().getUptodate());
assertEquals(3, ((Ipv4RoutesCase) tables.getRoutes()).getIpv4Routes().nonnullIpv4Route().size());
return tables;
});
waitWriteAndFlushSuccess(channel.writeAndFlush(createRouteMonitMsg(false, PEER1, AdjRibInType.PostPolicy)));
waitWriteAndFlushSuccess(channel.writeAndFlush(createRouteMonMsgWithEndOfRibMarker(PEER1, AdjRibInType.PostPolicy)));
readDataOperational(getDataBroker(), peerIId.child(PostPolicyRib.class), postPolicyRib -> {
assertFalse(postPolicyRib.nonnullTables().isEmpty());
final Tables tables = postPolicyRib.nonnullTables().values().iterator().next();
assertTrue(tables.getAttributes().getUptodate());
assertEquals(3, ((org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.bmp.monitor.monitor.router.peer.post.policy.rib.tables.routes.Ipv4RoutesCase) tables.getRoutes()).getIpv4Routes().nonnullIpv4Route().size());
return tables;
});
waitWriteAndFlushSuccess(channel.writeAndFlush(TestUtil.createPeerDownNotification(PEER1)));
readDataOperational(getDataBroker(), routerIId, router -> {
assertNull(router.getPeer());
return router;
});
return channel;
}
use of org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables in project bgpcep by opendaylight.
the class AbstractPeer method initializeRibOut.
@Override
public final synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>> void initializeRibOut(final RouteEntryDependenciesContainer entryDep, final List<ActualBestPathRoutes<C, S>> routesToStore) {
if (ribOutChain == null) {
LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
return;
}
final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
final YangInstanceIdentifier tableRibout = getRibOutIId(ribSupport.tablesKey());
final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
for (final ActualBestPathRoutes<C, S> initRoute : routesToStore) {
if (!supportsLLGR() && initRoute.isDepreferenced()) {
// Stale Long-lived Graceful Restart routes should not be propagated
continue;
}
final PeerId fromPeerId = initRoute.getFromPeerId();
if (!filterRoutes(fromPeerId, ribSupport.getTablesKey())) {
continue;
}
final MapEntryNode route = initRoute.getRoute();
final Peer fromPeer = entryDep.getPeerTracker().getPeer(fromPeerId);
if (fromPeer == null) {
LOG.debug("Failed to acquire peer structure for {}, ignoring route {}", fromPeerId, initRoute);
continue;
}
final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, initRoute, addPathSupported);
applyExportPolicy(entryDep, fromPeerId, route, routePath, initRoute.getAttributes()).ifPresent(attributes -> storeRoute(ribSupport, initRoute, route, routePath, attributes, tx));
}
final FluentFuture<? extends CommitInfo> future = tx.commit();
submitted = future;
future.addCallback(new FutureCallback<CommitInfo>() {
@Override
public void onSuccess(final CommitInfo result) {
LOG.trace("Successful update commit");
}
@Override
public void onFailure(final Throwable trw) {
LOG.error("Failed update commit", trw);
}
}, MoreExecutors.directExecutor());
}
use of org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables in project bgpcep by opendaylight.
the class AbstractPeer method reEvaluateAdvertizement.
@Override
public final synchronized <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>> void reEvaluateAdvertizement(final RouteEntryDependenciesContainer entryDep, final List<ActualBestPathRoutes<C, S>> routesToStore) {
if (ribOutChain == null) {
LOG.debug("Session closed, skip changes to peer AdjRibsOut {}", getPeerId());
return;
}
final RIBSupport<C, S> ribSupport = entryDep.getRIBSupport();
final NodeIdentifierWithPredicates tk = ribSupport.tablesKey();
final boolean addPathSupported = supportsAddPathSupported(ribSupport.getTablesKey());
final DOMDataTreeWriteTransaction tx = ribOutChain.newWriteOnlyTransaction();
for (final ActualBestPathRoutes<C, S> actualBestRoute : routesToStore) {
final PeerId fromPeerId = actualBestRoute.getFromPeerId();
if (!filterRoutes(fromPeerId, ribSupport.getTablesKey())) {
continue;
}
final YangInstanceIdentifier tableRibout = getRibOutIId(tk);
// Stale Long-lived Graceful Restart routes should not be propagated
if (supportsLLGR() || !actualBestRoute.isDepreferenced()) {
final YangInstanceIdentifier routePath = createRoutePath(ribSupport, tableRibout, actualBestRoute, addPathSupported);
final MapEntryNode route = actualBestRoute.getRoute();
final Optional<ContainerNode> effAttr = applyExportPolicy(entryDep, fromPeerId, route, routePath, actualBestRoute.getAttributes());
if (effAttr.isPresent()) {
storeRoute(ribSupport, actualBestRoute, route, routePath, effAttr.get(), tx);
continue;
}
}
deleteRoute(ribSupport, addPathSupported, tableRibout, actualBestRoute, tx);
}
final FluentFuture<? extends CommitInfo> future = tx.commit();
submitted = future;
future.addCallback(new FutureCallback<CommitInfo>() {
@Override
public void onSuccess(final CommitInfo result) {
LOG.trace("Successful update commit");
}
@Override
public void onFailure(final Throwable trw) {
LOG.error("Failed update commit", trw);
}
}, MoreExecutors.directExecutor());
}
Aggregations