Fix default route configuration for DVR case in VPP renderer
[groupbasedpolicy.git] / renderers / vpp / src / main / java / org / opendaylight / groupbasedpolicy / renderer / vpp / policy / acl / AclManager.java
1 /*
2  * Copyright (c) 2017 Cisco Systems, Inc. and others. 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
9 package org.opendaylight.groupbasedpolicy.renderer.vpp.policy.acl;
10
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.concurrent.Callable;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.Executors;
19 import java.util.function.Predicate;
20 import java.util.stream.Collectors;
21 import java.util.stream.Stream;
22
23 import javax.annotation.Nonnull;
24 import javax.annotation.Nullable;
25
26 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
27 import org.opendaylight.groupbasedpolicy.renderer.util.AddressEndpointUtils;
28 import org.opendaylight.groupbasedpolicy.renderer.vpp.iface.InterfaceManager;
29 import org.opendaylight.groupbasedpolicy.renderer.vpp.iface.VppPathMapper;
30 import org.opendaylight.groupbasedpolicy.renderer.vpp.policy.PolicyContext;
31 import org.opendaylight.groupbasedpolicy.renderer.vpp.policy.RendererResolvedPolicy;
32 import org.opendaylight.groupbasedpolicy.renderer.vpp.policy.ResolvedRuleGroup;
33 import org.opendaylight.groupbasedpolicy.renderer.vpp.policy.acl.AccessListUtil.ACE_DIRECTION;
34 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.GbpNetconfTransaction;
35 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.KeyFactory;
36 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.MountedDataBrokerProvider;
37 import org.opendaylight.groupbasedpolicy.renderer.vpp.util.VppIidFactory;
38 import org.opendaylight.groupbasedpolicy.util.EndpointUtils;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.AclKey;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.AccessListEntries;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.Ace;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.InterfaceKey;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.endpoints.address.endpoints.AddressEndpointKey;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.has.absolute.location.absolute.location.location.type.ExternalLocationCase;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.endpoints.AddressEndpointWithLocation;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.RendererEndpoint;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.RendererEndpointKey;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.renderer.endpoint.PeerEndpointKey;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.rule.groups.RuleGroupKey;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.acl.rev170615.VppAcl;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
55 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 import com.google.common.base.Function;
60 import com.google.common.base.Optional;
61 import com.google.common.base.Preconditions;
62 import com.google.common.collect.HashBasedTable;
63 import com.google.common.collect.HashMultimap;
64 import com.google.common.collect.ImmutableList;
65 import com.google.common.collect.ImmutableMap;
66 import com.google.common.collect.ImmutableSet;
67 import com.google.common.collect.ImmutableSetMultimap;
68 import com.google.common.collect.ImmutableTable;
69 import com.google.common.collect.ImmutableTable.Builder;
70 import com.google.common.collect.Lists;
71 import com.google.common.collect.SetMultimap;
72 import com.google.common.collect.Sets;
73 import com.google.common.collect.Sets.SetView;
74 import com.google.common.collect.Table;
75 import com.google.common.util.concurrent.FutureCallback;
76 import com.google.common.util.concurrent.Futures;
77 import com.google.common.util.concurrent.ListenableFuture;
78 import com.google.common.util.concurrent.ListeningExecutorService;
79 import com.google.common.util.concurrent.MoreExecutors;
80 import com.google.common.util.concurrent.SettableFuture;
81
82 public class AclManager {
83
84     private static final Logger LOG = LoggerFactory.getLogger(AclManager.class);
85     private final MountedDataBrokerProvider mountDataProvider;
86
87     private static ImmutableTable<NodeId, InterfaceKey, ImmutableSet<AddressEndpointKey>> endpointsByInterface;
88     private final InterfaceManager interfaceManager;
89     ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(30));
90
91     public AclManager(@Nonnull MountedDataBrokerProvider mountDataProvider, InterfaceManager interfaceManager) {
92         this.mountDataProvider = Preconditions.checkNotNull(mountDataProvider);
93         this.interfaceManager = Preconditions.checkNotNull(interfaceManager);
94     }
95
96     public ListenableFuture<List<AccessListWrapper>> resolveAclsOnInterface(RendererEndpointKey rEpKey,
97             PolicyContext ctx) {
98         Callable<List<AccessListWrapper>> aclBuildExecutor = new Callable<List<AccessListWrapper>>() {
99
100             @Override
101             public List<AccessListWrapper> call() throws Exception {
102                 LOG.info("Resolving ACL for renderer endpoint {}", rEpKey);
103                 List<AccessListWrapper> aclWrappers = new ArrayList<>();
104                 for (ACE_DIRECTION dir : new ACE_DIRECTION[] {ACE_DIRECTION.INGRESS, ACE_DIRECTION.EGRESS}) {
105                     aclWrappers.add(buildAccessListWrappers(dir, ctx, rEpKey));
106                 }
107                 return aclWrappers;
108             }
109         };
110         ListenableFuture<List<AccessListWrapper>> accessListFuture = executor.submit(aclBuildExecutor);
111         return accessListFuture;
112     }
113
114     /**
115      * @param policyDirection direction for which policy should be resolved. EP -> VPP = OUTBOUND,
116      *        EP <- VPP = INBOUND
117      * @param ctx with cached data
118      * @param rEpKey key of EP for which to create ACLs.
119      * @return synchronization futures, so that INGRESS and EGRESS ACLS can be resolved in parallel.
120      */
121     private static AccessListWrapper buildAccessListWrappers(ACE_DIRECTION policyDirection, PolicyContext ctx,
122             RendererEndpointKey rEpKey) {
123         LOG.trace("Resolving policy for VPP renderer endpoint {} in a separate thread in {} direction.", rEpKey,
124                 policyDirection);
125         AccessListWrapper aclWrapper = AccessListUtil.ACE_DIRECTION.INGRESS
126             .equals(policyDirection) ? new IngressAccessListWrapper() : new EgressAccessListWrapper();
127         AccessListUtil.configureLocalRules(ctx, rEpKey, policyDirection, aclWrapper);
128         // we support multiple IP end-points on a same interface
129         for (AddressEndpointKey aek : otherEndpointsOnTheSameInterface(ctx,
130                 AddressEndpointUtils.fromRendererEpKey(rEpKey))) {
131             AccessListUtil.configureLocalRules(ctx, AddressEndpointUtils.toRendererEpKey(aek), policyDirection,
132                     aclWrapper);
133         }
134         // resolve peers with no location
135         aclWrapper.writeRules(AccessListUtil.denyDomainSubnets(ctx, policyDirection));
136         // TODO currently any traffic heading to/from outside of managed domain is
137         // permitted for demonstration purposes
138         aclWrapper.writeRule(AccessListUtil.allowExternalNetworksForEp(rEpKey, policyDirection));
139         return aclWrapper;
140     }
141
142     private Map<RendererEndpointKey, Set<RuleGroupKey>> resolveChanges(@Nonnull PolicyContext policyCtx,
143             @Nonnull SetView<RuleGroupKey> deltaRuleGroups, @Nonnull SetView<RendererEndpointKey> changedEndpoints) {
144         Map<RendererEndpointKey, Set<RuleGroupKey>> endpointsToUpdate = new HashMap<>();
145         deltaRuleGroups.forEach(deltaRuleGroup -> {
146             policyCtx.getEndpointsByRuleGroups().get(deltaRuleGroup).forEach(addrEpWithLoc -> {
147                 endpointsToUpdate.put(AddressEndpointUtils.toRendererEpKey(addrEpWithLoc), deltaRuleGroups);
148             });
149         });
150         // end-points changed
151         for (RendererEndpointKey rendEp : changedEndpoints) {
152             policyCtx.getPolicyTable()
153                 .row(rendEp)
154                 .keySet()
155                 .stream()
156                 .filter(peer -> policyCtx.getPolicyTable().get(rendEp, peer) != null)
157                 .forEach(peer -> {
158                     endpointsToUpdate.put(rendEp,
159                             policyCtx.getPolicyTable()
160                                 .get(rendEp, peer)
161                                 .stream()
162                                 .map(policy -> policy.getRuleGroup().getRelatedRuleGroupKey())
163                                 .collect(Collectors.toSet()));
164                     RendererEndpointKey viceVersaX = AddressEndpointUtils.toRendererEpKey(peer);
165                     PeerEndpointKey viceVersaY = AddressEndpointUtils.toPeerEpKey(rendEp);
166                     if (policyCtx.getPolicyTable().get(viceVersaX, viceVersaY) != null) {
167                         endpointsToUpdate.put(viceVersaX,
168                                 policyCtx.getPolicyTable()
169                                     .get(viceVersaX, viceVersaY)
170                                     .stream()
171                                     .map(policy -> policy.getRuleGroup().getRelatedRuleGroupKey())
172                                     .collect(Collectors.toSet()));
173                     }
174                 });
175         }
176         return endpointsToUpdate;
177     }
178
179     public void resolveRulesToConfigure(@Nonnull PolicyContext policyCtx,
180             @Nonnull SetView<RendererEndpointKey> changedEndpoints, @Nonnull SetView<RuleGroupKey> deltaRuleGroups,
181             boolean write) {
182         Map<RendererEndpointKey, Set<RuleGroupKey>> resolvedChanges =
183                 resolveChanges(policyCtx, deltaRuleGroups, changedEndpoints);
184
185         List<ProcessingBean> beans = new ArrayList<>();
186         resolvedChanges.forEach((rendEpKey, rulesToUpdate) -> {
187             beans.add(new ProcessingBean(rendEpKey, rulesToUpdate, ACE_DIRECTION.INGRESS, policyCtx, write));
188             beans.add(new ProcessingBean(rendEpKey, rulesToUpdate, ACE_DIRECTION.EGRESS, policyCtx, write));
189         });
190         Table<NodeId, AclKey, List<Ace>> denyTenantTraffic = HashBasedTable.<NodeId, AclKey, List<Ace>>create();
191         Table<NodeId, AclKey, List<Ace>> permitExternal = HashBasedTable.<NodeId, AclKey, List<Ace>>create();
192         Table<NodeId, AclKey, List<Ace>> aceTable = HashBasedTable.<NodeId, AclKey, List<Ace>>create();
193         Function<InterfaceKey, String> getIntfName = (intf) -> {
194             Optional<String> intfName = VppPathMapper.interfacePathToInterfaceName(intf.getName());
195             Preconditions.checkArgument(intfName.isPresent(), "Failed to resolve interface name from " + intf);
196             return intfName.get();
197         };
198         beans.forEach(bean -> {
199             List<GbpAceBuilder> aceBuilders = bean.resolveAces(resolveUpdates(policyCtx, bean.epKey, bean.ruleGroups));
200             getInterfacesForEndpoint(policyCtx, KeyFactory.addressEndpointKey(bean.epKey)).asMap()
201                 .forEach((nodeId, intfcs) -> {
202                     intfcs.stream()
203                         .filter(intf -> !interfaceManager.isExcludedFromPolicy(nodeId, getIntfName.apply(intf)))
204                         .forEach(intf -> {
205                             AclKey aclKey = new AclKey(getIntfName.apply(intf) + bean.aceDirection, VppAcl.class);
206                             denyTenantTraffic.put(nodeId, aclKey,
207                                     getWorkaroundFlows(policyCtx, bean.epKey, bean.aceDirection));
208                             AccessListUtil.allowExternalNetworksForEp(bean.epKey, bean.aceDirection).build();
209                             denyTenantTraffic.put(nodeId, aclKey,
210                                     AccessListUtil.denyDomainSubnets(policyCtx, bean.aceDirection)
211                                         .stream()
212                                         .map(GbpAceBuilder::build)
213                                         .collect(Collectors.toList()));
214                             permitExternal.put(nodeId, aclKey, Lists.newArrayList(
215                                     AccessListUtil.allowExternalNetworksForEp(bean.epKey, bean.aceDirection).build()));
216                             Optional<List<Ace>> entries = Optional.fromNullable(aceTable.get(nodeId, aclKey));
217                             aceTable.put(nodeId, aclKey,
218                                     Stream
219                                         .concat((entries.isPresent()) ? entries.get().stream() : Stream.empty(),
220                                                 aceBuilders.stream().map(aceBuilder -> aceBuilder.build()))
221                                         .collect(Collectors.toList()));
222                         });
223                 });
224         });
225         // to avoid empty ACL (IllegalStateArgument on HC), rules have to be updated gently
226         updateRules(ImmutableTable.copyOf(aceTable), write);
227         updateRules(ImmutableTable.copyOf(denyTenantTraffic), false);
228         updateRules(ImmutableTable.copyOf(denyTenantTraffic), true);
229         updateRules(ImmutableTable.copyOf(permitExternal), false);
230         updateRules(ImmutableTable.copyOf(permitExternal), true);
231
232     }
233
234     private List<Ace> getWorkaroundFlows(PolicyContext policyCtx, RendererEndpointKey key, ACE_DIRECTION dir) {
235         List<Ace> result = new ArrayList<>();
236         result.addAll(AccessListUtil.denyDomainSubnets(policyCtx, dir)
237             .stream()
238             .map(GbpAceBuilder::build)
239             .collect(Collectors.toList()));
240         result.add(AccessListUtil.allowExternalNetworksForEp(key, dir).build());
241         return result;
242     }
243
244     private ImmutableTable<RendererEndpointKey, PeerEndpointKey, List<RendererResolvedPolicy>> resolveUpdates(
245             PolicyContext policyCtx, RendererEndpointKey rendEpKey, Set<RuleGroupKey> rulesToUpdate) {
246         java.util.Optional<RendererEndpoint> rendEp = policyCtx.getRendererEndpoint(rendEpKey);
247         Builder<RendererEndpointKey, PeerEndpointKey, List<RendererResolvedPolicy>> updateTreeBuilder = new Builder<>();
248         rendEp.get()
249             .getPeerEndpoint()
250             .stream()
251             // TODO see UT
252             .filter(peerEp -> policyCtx.getPolicyTable().get(rendEpKey, peerEp.getKey()) != null)
253             .forEach(peerEp -> {
254                 updateTreeBuilder.put(rendEpKey, peerEp.getKey(),
255                         policyCtx.getPolicyTable()
256                             .get(rendEpKey, peerEp.getKey())
257                             .stream()
258                             .filter(rrp -> rulesToUpdate.contains(rrp.getRuleGroup().getRelatedRuleGroupKey()))
259                             .collect(Collectors.toList()));
260             });
261         return updateTreeBuilder.build();
262     }
263
264     private void updateRules(ImmutableTable<NodeId, AclKey, List<Ace>> rulesToUpdate, boolean write) {
265         List<ListenableFuture<Void>> sync = new ArrayList<>();
266         rulesToUpdate.rowKeySet().forEach(nodeId -> {
267             Callable<Void> syncExecutor = new Callable<Void>() {
268
269                 @Override
270                 public Void call() throws Exception {
271                     InstanceIdentifier<Node> vppIid = VppIidFactory.getNetconfNodeIid(nodeId);
272                     Optional<DataBroker> dataBroker =
273                         mountDataProvider.resolveDataBrokerForMountPoint(vppIid);
274                     if (!dataBroker.isPresent()) {
275                         LOG.error("Failed to update ACLs for endpoints on node {}. Mount point does not exist.",
276                                 nodeId);
277                     }
278                     ImmutableMap<AclKey, List<Ace>> row = rulesToUpdate.row(nodeId);
279                     row.keySet().forEach(aclKey -> {
280                         Map<InstanceIdentifier<Ace>, Ace> entries = new HashMap<>();
281                         row.get(aclKey).forEach(ace -> {
282                             entries.put(VppIidFactory.getVppAcl(aclKey)
283                                 .builder()
284                                 .child(AccessListEntries.class)
285                                 .child(Ace.class, ace.getKey())
286                                 .build(), ace);
287                         });
288                         if (entries.isEmpty()) {
289                             return;
290                         }
291                         LOG.debug("{} rules {} ACL {} on node {}.", (write) ? "Writing" : "Removing",
292                                 aclKey.getAclName(), (write) ? "to" : "from", nodeId.getValue());
293                         boolean result = (write) ? GbpNetconfTransaction.netconfSyncedWrite(vppIid, entries,
294                                 GbpNetconfTransaction.RETRY_COUNT) : GbpNetconfTransaction.netconfSyncedDelete(vppIid,
295                                         entries.keySet(), GbpNetconfTransaction.RETRY_COUNT);
296                         if (!result) {
297                             LOG.error("Failed to update rules in ACL {} on mount point {}", aclKey, nodeId.getValue());
298                         }
299                     });
300                     return null;
301                 }
302             };
303             sync.add(executor.submit(syncExecutor));
304         });
305         try {
306             Futures.allAsList(sync).get();
307         } catch (InterruptedException | ExecutionException e) {
308             LOG.error("Failed to sync ACLs on VPP nodes. {}", e);
309         }
310     }
311
312     private List<GbpAceBuilder> generateRulesForEndpointPair(PolicyContext ctx, RendererEndpointKey r,
313             PeerEndpointKey p, RendererResolvedPolicy rrp, ACE_DIRECTION aceDirection) {
314         List<GbpAceBuilder> rules = new ArrayList<>();
315         Direction direction =
316                 AccessListUtil.calculateClassifDirection(rrp.getRendererEndpointParticipation(), aceDirection);
317         ResolvedRuleGroup resolvedRuleGroup = ctx.getRuleGroupByKey().get(rrp.getRuleGroup().getRelatedRuleGroupKey());
318         resolvedRuleGroup.getRules().forEach(rule -> {
319             Optional<GbpAceBuilder> ace = AccessListUtil.resolveAceClassifersAndAction(rule, direction,
320                     AccessListUtil.resolveAceName(rule.getName(), r, p));
321             if (ace.isPresent()) {
322                 rules.add(ace.get());
323             }
324         });
325         AccessListUtil.updateAddressesInRules(rules, r, p, ctx, aceDirection, true);
326         return rules;
327     }
328
329     // TODO remove
330     public void updateAclsForPeers(PolicyContext policyCtx, RendererEndpointKey rEpKey) {
331         ImmutableSet<PeerEndpointKey> peers = policyCtx.getPolicyTable().row(rEpKey).keySet();
332         List<ListenableFuture<Void>> sync = new ArrayList<>();
333         for (RendererEndpointKey peerRendEp : peers.stream()
334             .map(AddressEndpointUtils::fromPeerEpKey)
335             .collect(Collectors.toList())
336             .stream()
337             .map(AddressEndpointUtils::toRendererEpKey)
338             .collect(Collectors.toList())) {
339             sync.add(updateAclsForRendEp(peerRendEp, policyCtx));
340         }
341         try {
342             Futures.allAsList(sync).get();
343         } catch (InterruptedException | ExecutionException e) {
344             LOG.error("Failed to update ACLs for peers of {}. {}", rEpKey, e);
345         }
346     }
347
348     public ListenableFuture<Void> updateAclsForRendEp(RendererEndpointKey rEpKey, PolicyContext policyCtx) {
349         SettableFuture<Void> sf = SettableFuture.create();
350         AddressEndpointWithLocation peerAddrEp = policyCtx.getAddrEpByKey().get(KeyFactory.addressEndpointKey(rEpKey));
351         ExternalLocationCase epLoc;
352         try {
353             epLoc = InterfaceManager.resolveAndValidateLocation(peerAddrEp);
354         } catch (NullPointerException | IllegalArgumentException e) {
355             // TODO investigate, don't just move on.
356             LOG.warn("Peer {} has no location. Moving on...", peerAddrEp.getKey().getAddress(), e.getMessage());
357             return Futures.immediateFuture(null);
358         }
359         InstanceIdentifier<Node> vppNodeIid = (InstanceIdentifier<Node>) epLoc.getExternalNodeMountPoint();
360         Optional<InstanceIdentifier<Interface>> optInterfaceIid =
361                 VppPathMapper.interfaceToInstanceIdentifier(epLoc.getExternalNodeConnector());
362         if (!optInterfaceIid.isPresent()) {
363             LOG.warn("Cannot  find interface for endpoint {}. ACLs for endpoint not updated {}. ", rEpKey);
364             return Futures.immediateFuture(null);
365         }
366         if (interfaceManager.isExcludedFromPolicy(vppNodeIid.firstKeyOf(Node.class).getNodeId(),
367                 optInterfaceIid.get().firstKeyOf(Interface.class).getName())) {
368             return Futures.immediateFuture(null);
369         }
370         LOG.info("Updating policy for endpoint {}", rEpKey);
371         ListenableFuture<List<AccessListWrapper>> future = resolveAclsOnInterface(rEpKey, policyCtx);
372         Futures.addCallback(future, new FutureCallback<List<AccessListWrapper>>() {
373
374             @Override
375             public void onSuccess(List<AccessListWrapper> result) {
376                 result.forEach(acl -> acl.writeAcl(vppNodeIid, optInterfaceIid.get().firstKeyOf(Interface.class)));
377                 sf.set(null);
378             }
379
380             @Override
381             public void onFailure(Throwable t) {
382                 LOG.error("Failed to update ACL for interface {} on node {}",
383                         optInterfaceIid.get().firstKeyOf(Interface.class), vppNodeIid.firstKeyOf(Node.class));
384                 sf.set(null);
385             }
386         }, MoreExecutors.directExecutor());
387         return sf;
388     }
389
390     /**
391      * Cache end-points accessible via a single interface for further processing.
392      *
393      * @param ctx policy context
394      */
395     public void cacheEndpointsByInterfaces(@Nonnull PolicyContext ctx) {
396         Builder<NodeId, InterfaceKey, ImmutableSet<AddressEndpointKey>> resultBuilder = new Builder<>();
397         resolveEndpointsOnMultipleInterface(ImmutableList.copyOf(ctx.getAddrEpByKey().values()), resultBuilder);
398         endpointsByInterface = resultBuilder.build();
399     }
400
401     /**
402      * Recursively grouping interfaces behind the same port
403      */
404     private void resolveEndpointsOnMultipleInterface(@Nullable List<AddressEndpointWithLocation> eps,
405             @Nonnull Builder<NodeId, InterfaceKey, ImmutableSet<AddressEndpointKey>> builder) {
406         if (eps == null || eps.isEmpty()) {
407             return;
408         }
409         // look for any end-point with absolute location as reference end-point in this cycle;
410         java.util.Optional<AddressEndpointWithLocation> refEndpoint =
411                 eps.stream().filter(ep -> EndpointUtils.getExternalLocationFrom(ep).isPresent()).findAny();
412         if (!refEndpoint.isPresent()) {
413             return;
414         }
415         Predicate<AddressEndpointWithLocation> sameLocation = new Predicate<AddressEndpointWithLocation>() {
416
417             @Override
418             public boolean test(AddressEndpointWithLocation addrEp) {
419                 return AddressEndpointUtils.sameExternalLocationCase(refEndpoint.get(), addrEp);
420             }
421         };
422         Optional<ExternalLocationCase> extLoc = EndpointUtils.getExternalLocationFrom(refEndpoint.get());
423         Set<AddressEndpointKey> sameLocations = eps.stream()
424             .filter(sameLocation)
425             .map(addrEp -> AddressEndpointUtils.fromAddressEndpointWithLocationKey(addrEp.getKey()))
426             .collect(Collectors.toSet());
427         builder.put(extLoc.get().getExternalNodeMountPoint().firstKeyOf(Node.class).getNodeId(),
428                 new InterfaceKey(extLoc.get().getExternalNodeConnector()),
429                 ImmutableSet.<AddressEndpointKey>copyOf(sameLocations));
430         List<AddressEndpointWithLocation> differentLocations = eps.stream()
431             // keep end-points with different location and end-points with relative location in loop
432             .filter(sameLocation.negate().or(p -> !EndpointUtils.getExternalLocationFrom(p).isPresent()))
433             .collect(Collectors.toList());
434         if (!differentLocations.isEmpty()) {
435             resolveEndpointsOnMultipleInterface(differentLocations, builder);
436         }
437     }
438
439     public @Nonnull static ImmutableSet<AddressEndpointKey> otherEndpointsOnTheSameInterface(@Nonnull PolicyContext ctx,
440             @Nonnull AddressEndpointKey key) {
441         if (endpointsByInterface != null) {
442             for (InterfaceKey ifaceKey : endpointsByInterface.columnKeySet()) {
443                 for (NodeId nodeId : endpointsByInterface.column(ifaceKey).keySet()) {
444                     ImmutableSet<AddressEndpointKey> addrEps = endpointsByInterface.get(nodeId, ifaceKey);
445                     if (addrEps != null && addrEps.contains(key) && addrEps.size() > 1) {
446                         return endpointsByInterface.get(nodeId, ifaceKey);
447                     }
448                 }
449             }
450         }
451         return ImmutableSet.copyOf(Sets.newHashSet());
452     }
453
454     public @Nonnull static ImmutableSetMultimap<NodeId, InterfaceKey> getInterfacesForEndpoint(
455             @Nonnull PolicyContext ctx, @Nonnull AddressEndpointKey key) {
456         SetMultimap<NodeId, InterfaceKey> interfaces = HashMultimap.create();
457         if (endpointsByInterface != null) {
458             for (InterfaceKey ifaceKey : endpointsByInterface.columnKeySet()) {
459                 for (NodeId nodeId : endpointsByInterface.column(ifaceKey).keySet()) {
460                     ImmutableSet<AddressEndpointKey> addrEps = endpointsByInterface.get(nodeId, ifaceKey);
461                     if (addrEps != null && addrEps.contains(key)) {
462                         interfaces.put(nodeId, ifaceKey);
463                     }
464                 }
465             }
466         }
467         return ImmutableSetMultimap.copyOf(interfaces);
468     }
469
470     private final class ProcessingBean {
471
472         final RendererEndpointKey epKey;
473         final Set<RuleGroupKey> ruleGroups;
474         final ACE_DIRECTION aceDirection;
475         final PolicyContext policyCtx;
476         final boolean write;
477
478         ProcessingBean(RendererEndpointKey key, Set<RuleGroupKey> ruleGroups, ACE_DIRECTION aceDirection,
479                 PolicyContext policyCtx, boolean write) {
480             this.epKey = key;
481             this.ruleGroups = ruleGroups;
482             this.aceDirection = aceDirection;
483             this.policyCtx = policyCtx;
484             this.write = write;
485         }
486
487         List<GbpAceBuilder> resolveAces(
488                 ImmutableTable<RendererEndpointKey, PeerEndpointKey, List<RendererResolvedPolicy>> updateTree) {
489             List<GbpAceBuilder> result = new ArrayList<>();
490             updateTree.columnKeySet().stream().filter(peer -> updateTree.get(epKey, peer) != null).forEach(peer -> {
491                 updateTree.get(epKey, peer).stream().forEach(rrp -> {
492                     if (write) {
493                         result.addAll(generateRulesForEndpointPair(policyCtx, epKey, peer, rrp, aceDirection));
494                     } else {
495                         // we only need to resolve rule names when removing ACE
496                         result.addAll(rrp.getRuleGroup()
497                             .getRules()
498                             .stream()
499                             .map(rule -> new GbpAceBuilder(AccessListUtil.resolveAceName(rule.getName(), epKey, peer)))
500                             .collect(Collectors.toList()));
501                     }
502                 });
503             });
504             return result;
505         }
506     }
507 }