BUG 5402 - PolicyEnforcer creates wrong directed flows
[groupbasedpolicy.git] / renderers / ofoverlay / src / main / java / org / opendaylight / groupbasedpolicy / renderer / ofoverlay / flow / PolicyEnforcer.java
old mode 100644 (file)
new mode 100755 (executable)
index 72a8a9f..195fecc
@@ -10,6 +10,7 @@ package org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow;
 
 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.addNxRegMatch;
 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.applyActionIns;
+import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.gotoTableIns;
 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.instructions;
 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxOutputRegAction;
 
@@ -24,23 +25,28 @@ import java.util.Set;
 
 import javax.annotation.concurrent.Immutable;
 
+import org.opendaylight.groupbasedpolicy.api.sf.AllowActionDefinition;
+import org.opendaylight.groupbasedpolicy.api.sf.EtherTypeClassifierDefinition;
+import org.opendaylight.groupbasedpolicy.api.sf.IpProtoClassifierDefinition;
+import org.opendaylight.groupbasedpolicy.api.sf.L4ClassifierDefinition;
+import org.opendaylight.groupbasedpolicy.dto.EgKey;
+import org.opendaylight.groupbasedpolicy.dto.EndpointConstraint;
+import org.opendaylight.groupbasedpolicy.dto.IndexedTenant;
+import org.opendaylight.groupbasedpolicy.dto.Policy;
+import org.opendaylight.groupbasedpolicy.dto.RuleGroup;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfContext;
-import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.PolicyManager.FlowMap;
+import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfWriter;
+import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.endpoint.EndpointManager;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.RegMatch;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.OrdinalFactory.EndpointFwdCtxOrdinals;
-import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchManager;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sf.Action;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sf.AllowAction;
+import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sf.ChainAction;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sf.ClassificationResult;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sf.Classifier;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sf.ParamDerivator;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sf.SubjectFeatures;
-import org.opendaylight.groupbasedpolicy.resolver.EgKey;
-import org.opendaylight.groupbasedpolicy.resolver.EndpointConstraint;
-import org.opendaylight.groupbasedpolicy.resolver.IndexedTenant;
-import org.opendaylight.groupbasedpolicy.resolver.Policy;
-import org.opendaylight.groupbasedpolicy.resolver.PolicyInfo;
-import org.opendaylight.groupbasedpolicy.resolver.RuleGroup;
+import org.opendaylight.groupbasedpolicy.util.TenantUtils;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
@@ -48,19 +54,24 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.ta
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.go.to.table._case.GoToTable;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ClassifierDefinitionId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ConditionName;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.TenantId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint.rev140421.endpoints.Endpoint;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint.rev140421.endpoints.EndpointKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint.rev140421.endpoints.EndpointL3;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayContext;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.has.action.refs.ActionRef;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.has.classifier.refs.ClassifierRef;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.instance.ParameterValue;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.EndpointGroup;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.EndpointGroup.IntraGroupPolicy;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.contract.subject.Rule;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.subject.feature.instances.ActionInstance;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.subject.feature.instances.ClassifierInstance;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.EndpointGroup;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.EndpointGroup.IntraGroupPolicy;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.ExternalImplicitGroup;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.contract.subject.Rule;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.subject.feature.instances.ActionInstance;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.subject.feature.instances.ClassifierInstance;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.Layer3Match;
@@ -76,25 +87,90 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Table.Cell;
 
 /**
- * Manage the table that enforces policy on the traffic. Traffic is denied
- * unless specifically allowed by policy
+ * <h1>Manage the table that enforces policy on the traffic. Traffic is denied
+ * unless specifically allowed by policy (table=4)</h1>
+ *
+ * In policy enforcer, according to current {@link Policy} specific traffic is sent to SFC (nsp and nsi is set), or from SFC
+ * to some {@link Endpoint} or to another classifier.
+ * <p>
+ * <i>Tunnel/overlay flows</i><br>
+ * Priority = 65000 (if more flows, decrements)<br>
+ * Matches:<br>
+ *      - ethertype (tcp, tcp6, ipv6, icmp or missing)<br>
+ *      - Reg0 {@link NxmNxReg0}<br>
+ *      - Reg1 {@link NxmNxReg1}<br>
+ *      - Reg2 {@link NxmNxReg2}<br>
+ *      - Reg3 {@link NxmNxReg3}<br>
+ *      - L3 for src_ip_prefix (if exists)<br>
+ *      - L3 for dst_ip_prefix (if exists)<br>
+ * Actions:<br>
+ *      - set nsi (only chain action)<br>
+ *      - set nsp (only chain action)<br>
+ *      - {@link GoToTable} EXTERNAL MAPPER table<br>
+ *<p>
+ * <i>Allow from tunnel flow</i><br>
+ * Priority = 65000<br>
+ * Matches:<br>
+ *      - Reg1 (set to 0xffffff) {@link NxmNxReg1}<br>
+ *      - in_port (should be tunnel port) {@link NodeConnectorId}<br>
+ * Actions:<br>
+ *      - output:port (Reg7) {@link NxmNxReg7}<br>
+ * <p>
+ * Traffic is sent from one {@link EndpointGroup} to the same EPG
+ * <p>
+ * <i>Allow from same EPG flow</i><br>
+ * Priority = 65000<br>
+ * Matches:<br>
+ *      - Reg0 {@link NxmNxReg0}<br>
+ *      - Reg2 {@link NxmNxReg2}<br>
+ * Actions:<br>
+ *      - output:port (Reg7) {@link NxmNxReg7}
+ * <p>
+ * <i>Arp flow</i><br>
+ * Priority = 20000<br>
+ * Matches:<br>
+ *      - ethernet match (arp)<br>
+ *      - Reg5 {@link NxmNxReg5}<br>
+ * Actions:<br>
+ *      - output:port (Reg7) {@link NxmNxReg7}
+ *
  */
 public class PolicyEnforcer extends FlowTable {
 
-    protected static final Logger LOG = LoggerFactory.getLogger(PolicyEnforcer.class);
-
-    public static short TABLE_ID = 3;
+    private static final Logger LOG = LoggerFactory.getLogger(PolicyEnforcer.class);
+    public static short TABLE_ID;
+    private static boolean isReversedPolicy;
+    private static org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.Instruction gotoEgressNatInstruction;
+    private static org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.Instruction gotoExternalInstruction;
+    private HashSet<PolicyPair> visitedPairs = new HashSet<>();
+    private HashSet<PolicyPair> visitedReversePairs = new HashSet<>();
+    private List<Rule> reversedActiveRules = new ArrayList<>();
+    private ListMultimap<EgKey, EgKey> resolvedEpgPairs = ArrayListMultimap.create();
+    private boolean directPathFlowsCreated = false;
+    private boolean reversePathFlowsCreated = false;
 
     public PolicyEnforcer(OfContext ctx, short tableId) {
         super(ctx);
-        this.TABLE_ID=tableId;
+        TABLE_ID = tableId;
+        isReversedPolicy = false;
+        gotoEgressNatInstruction = gotoTableIns(ctx.getPolicyManager().getTABLEID_EGRESS_NAT());
+        gotoExternalInstruction = gotoTableIns(ctx.getPolicyManager().getTABLEID_EXTERNAL_MAPPER());
+    }
+
+    private static org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.Instruction getGotoEgressNatInstruction() {
+        return gotoEgressNatInstruction;
     }
 
+    private static org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.Instruction getGotoExternalInstruction() {
+        return gotoExternalInstruction;
+    }
 
     @Override
     public short getTableId() {
@@ -102,62 +178,66 @@ public class PolicyEnforcer extends FlowTable {
     }
 
     @Override
-    public void sync(NodeId nodeId, PolicyInfo policyInfo, FlowMap flowMap) throws Exception {
+    public void sync(NodeId nodeId, OfWriter ofWriter) throws Exception {
 
-        flowMap.writeFlow(nodeId, TABLE_ID, dropFlow(Integer.valueOf(1), null));
+        ofWriter.writeFlow(nodeId, TABLE_ID, dropFlow(1, null, TABLE_ID));
 
-        NodeConnectorId tunPort = SwitchManager.getTunnelPort(nodeId, TunnelTypeVxlan.class);
+        NodeConnectorId tunPort = ctx.getSwitchManager().getTunnelPort(nodeId, TunnelTypeVxlan.class);
         if (tunPort != null) {
-            flowMap.writeFlow(nodeId, TABLE_ID, allowFromTunnel(tunPort));
+            ofWriter.writeFlow(nodeId, TABLE_ID, allowFromTunnel(tunPort));
         }
 
-        HashSet<CgPair> visitedPairs = new HashSet<>();
+        visitedPairs = new HashSet<>();
+        reversedActiveRules = new ArrayList<>();
+        visitedReversePairs = new HashSet<>();
+        resolvedEpgPairs = ArrayListMultimap.create();
 
         // Used for ARP flows
         Set<Integer> fdIds = new HashSet<>();
 
-        for (Endpoint srcEp : ctx.getEndpointManager().getEndpointsForNode(nodeId)) {
-            for (EgKey srcEpgKey : ctx.getEndpointManager().getEgKeysForEndpoint(srcEp)) {
-                Set<EgKey> peers = policyInfo.getPeers(srcEpgKey);
-                for (EgKey dstEpgKey : peers) {
-                    for (Endpoint dstEp : ctx.getEndpointManager().getEndpointsForGroup(dstEpgKey)) {
-                        // mEPG ordinals
-                        EndpointFwdCtxOrdinals srcEpFwdCxtOrds = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx,
-                                policyInfo, srcEp);
-                        EndpointFwdCtxOrdinals dstEpFwdCxtOrds = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx,
-                                policyInfo, dstEp);
-                        int dcgId = dstEpFwdCxtOrds.getCgId();
-                        int depgId = dstEpFwdCxtOrds.getEpgId();
-                        int scgId = srcEpFwdCxtOrds.getCgId();
-                        int sepgId = srcEpFwdCxtOrds.getEpgId();
-                        NetworkElements netElements = new NetworkElements(srcEp, dstEp, nodeId, ctx, policyInfo);
-                        fdIds.add(srcEpFwdCxtOrds.getFdId());
-
-                        Policy policy = policyInfo.getPolicy(dstEpgKey, srcEpgKey);
-                        for (Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>> activeRulesByConstraints: getActiveRulesBetweenEps(policy, dstEp, srcEp)) {
-                            Set<IpPrefix> sIpPrefixes = Policy.getIpPrefixesFrom(activeRulesByConstraints.getRowKey()
-                                .getL3EpPrefixes());
-                            Set<IpPrefix> dIpPrefixes = Policy.getIpPrefixesFrom(activeRulesByConstraints.getColumnKey()
-                                .getL3EpPrefixes());
-                            CgPair p = new CgPair(depgId, sepgId, dcgId, scgId, dIpPrefixes, sIpPrefixes);
-                            if (visitedPairs.contains(p))
-                                continue;
-                            visitedPairs.add(p);
-                            syncPolicy(flowMap, netElements, activeRulesByConstraints.getValue(), p);
+        for (Endpoint sourceEp : ctx.getEndpointManager().getEndpointsForNode(nodeId)) {
+            for (EgKey sourceEpgKey : ctx.getEndpointManager().getEgKeysForEndpoint(sourceEp)) {
+                Set<EgKey> peers = ctx.getCurrentPolicy().getPeers(sourceEpgKey);
+                for (EgKey destinationEpgKey : peers) {
+
+                    Set<Endpoint> destinationEndpoints = new HashSet<>();
+                    destinationEndpoints.addAll(ctx.getEndpointManager().getEndpointsForGroup(destinationEpgKey));
+                    destinationEndpoints.addAll(ctx.getEndpointManager().getExtEpsNoLocForGroup(destinationEpgKey));
+                    for (Endpoint destinationEp : destinationEndpoints) {
+
+                        EndpointFwdCtxOrdinals srcEpFwdCxtOrdinals = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, sourceEp);
+                        if (srcEpFwdCxtOrdinals == null) {
+                            LOG.debug("Method getEndpointFwdCtxOrdinals returned null for EP {}", sourceEp);
+                            continue;
                         }
 
-                        // Reverse
-                        policy = policyInfo.getPolicy(srcEpgKey, dstEpgKey);
-                        for (Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>> activeRulesByConstraints : getActiveRulesBetweenEps(policy, srcEp, dstEp)) {
-                            Set<IpPrefix> sIpPrefixes = Policy.getIpPrefixesFrom(activeRulesByConstraints.getRowKey()
-                                .getL3EpPrefixes());
-                            Set<IpPrefix> dIpPrefixes = Policy.getIpPrefixesFrom(activeRulesByConstraints.getColumnKey()
-                                .getL3EpPrefixes());
-                            CgPair p = new CgPair(sepgId, depgId, scgId, dcgId, sIpPrefixes, dIpPrefixes);
-                            if (visitedPairs.contains(p))
-                                continue;
-                            visitedPairs.add(p);
-                            syncPolicy(flowMap, netElements, activeRulesByConstraints.getValue(), p);
+                        EndpointFwdCtxOrdinals dstEpFwdCxtOrdinals = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, destinationEp);
+                        if (dstEpFwdCxtOrdinals == null) {
+                            LOG.debug("Method getEndpointFwdCtxOrdinals returned null for EP {}", destinationEp);
+                            continue;
+                        }
+
+                        fdIds.add(srcEpFwdCxtOrdinals.getFdId());
+                        NetworkElements netElements = new NetworkElements(sourceEp, destinationEp, sourceEpgKey,
+                                destinationEpgKey, nodeId, ctx);
+
+                        // Get policy in both directions
+                        Policy sourceEpgPolicy = ctx.getCurrentPolicy().getPolicy(destinationEpgKey, sourceEpgKey);
+                        Policy destinationEpgPolicy = ctx.getCurrentPolicy().getPolicy(sourceEpgKey, destinationEpgKey);
+                        reversedActiveRules = getRules(getActiveRulesBetweenEps(destinationEpgPolicy, sourceEp, destinationEp));
+
+                        // Resolve flows in both directions if possible according to policy. Get back status of resolution
+                        PathStatus status = resolveSourceEpgPolicy(ofWriter, netElements, sourceEpgPolicy);
+
+                        // When source Epg policy creates no flows, destination Epg policy has to be resolved
+                        if (status.equals(PathStatus.none)) {
+                            resolveDestinationEpgPolicy(ofWriter, netElements, destinationEpgPolicy, false);
+                        }
+                        // When source Epg policy creates flows only in one direction, the other direction has to be
+                        // created here. Is essential to revert directions to prevent flow overriding and incorrect nsp
+                        // evaluation
+                        else if (status.equals(PathStatus.partial)) {
+                            resolveDestinationEpgPolicy(ofWriter, netElements, destinationEpgPolicy, true);
                         }
                     }
                 }
@@ -165,184 +245,494 @@ public class PolicyEnforcer extends FlowTable {
         }
 
         // Allow same EPG
-        // Set<Endpoint> visitedEps = new HashSet<>();
-        for (Endpoint srcEp : ctx.getEndpointManager().getEndpointsForNode(nodeId)) {
-            // visitedEps.add(srcEp);
-            for (EgKey srcEpgKey : ctx.getEndpointManager().getEgKeysForEndpoint(srcEp)) {
-
-                IndexedTenant tenant = ctx.getPolicyResolver().getTenant(srcEpgKey.getTenantId());
-                EndpointGroup group = tenant.getEndpointGroup(srcEpgKey.getEgId());
-                IntraGroupPolicy igp = group.getIntraGroupPolicy();
-
-                if (igp == null || igp.equals(IntraGroupPolicy.Allow)) {
-                    for (Endpoint dstEp : ctx.getEndpointManager().getEndpointsForGroup(srcEpgKey)) {
-                        // mEPG ordinals
-                        // if(visitedEps.contains(dstEp)) {
-                        // continue;
-                        // }
-                        // visitedEps.add(dstEp);
-                        EndpointFwdCtxOrdinals srcEpFwdCxtOrds = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx,
-                                policyInfo, srcEp);
-                        EndpointFwdCtxOrdinals dstEpFwdCxtOrds = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx,
-                                policyInfo, dstEp);
-                        int depgId = dstEpFwdCxtOrds.getEpgId();
-                        int sepgId = srcEpFwdCxtOrds.getEpgId();
-                        flowMap.writeFlow(nodeId, TABLE_ID, allowSameEpg(sepgId, depgId));
-                        flowMap.writeFlow(nodeId, TABLE_ID, allowSameEpg(depgId, sepgId));
+        allowSameEpg(nodeId, ofWriter);
+
+        // Write ARP flows per flood domain
+        for (Integer fdId : fdIds) {
+            ofWriter.writeFlow(nodeId, TABLE_ID, createArpFlow(fdId));
+        }
+    }
+
+    private PathStatus resolveSourceEpgPolicy(OfWriter ofWriter, NetworkElements netElements, Policy directPolicy) {
+        isReversedPolicy = false;
+        directPathFlowsCreated = false;
+        reversePathFlowsCreated = false;
+
+        for (Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>> activeRulesByConstraints : getActiveRulesBetweenEps(
+                directPolicy, netElements.getDstEp(), netElements.getSrcEp())) {
+            Set<IpPrefix> sIpPrefixes = Policy.getIpPrefixesFrom(activeRulesByConstraints.getRowKey()
+                    .getL3EpPrefixes());
+            Set<IpPrefix> dIpPrefixes = Policy.getIpPrefixesFrom(activeRulesByConstraints.getColumnKey()
+                    .getL3EpPrefixes());
+
+            int priority = 65000;
+            for (RuleGroup rg : activeRulesByConstraints.getValue()) {
+                TenantId tenantId = rg.getContractTenant().getId();
+                IndexedTenant tenant = ctx.getTenant(tenantId);
+                for (Rule rule : rg.getRules()) {
+
+                    // Find all rules in the same traffic direction
+                    List<Rule> sameDirectionRules = findRulesInSameDirection(rule, reversedActiveRules);
+                    if (sameDirectionRules.isEmpty()) {
+                        sameDirectionRules.add(rule);
+                    }
+                    sameDirectionRules = Ordering.from(TenantUtils.RULE_COMPARATOR)
+                            .immutableSortedCopy(sameDirectionRules);
+
+                    // Create flows for every pair of rules
+                    for (Rule oppositeRule : sameDirectionRules) {
+
+                        // Evaluate which rule has more specific matches
+                        Rule ruleWithMatches = findRuleWithSpecificMatches(rule, oppositeRule, tenant);
+                        Rule ruleWithActions = mergeRuleActions(rule, oppositeRule, tenant);
+                        if (ruleWithMatches == null) {
+                            LOG.trace("No matches found for pair of rules {}, {}", rule, oppositeRule);
+                            continue;
+                        }
+                        if (ruleWithActions == null) {
+                            LOG.trace("No actions found for pair of rules {}, {}", rule, oppositeRule);
+                            continue;
+                        }
+                        PolicyPair policyPair = null;
+                        if (rule.equals(ruleWithMatches)) {
+                            policyPair = new PolicyPair(netElements.getDstEpOrdinals().getEpgId(),
+                                    netElements.getSrcEpOrdinals().getEpgId(), netElements.getDstEpOrdinals().getCgId(),
+                                    netElements.getSrcEpOrdinals().getCgId(), dIpPrefixes, sIpPrefixes,
+                                    netElements.getDstNodeId(), netElements.getSrcNodeId());
+                        } else {
+                            policyPair = new PolicyPair(netElements.getSrcEpOrdinals().getEpgId(),
+                                    netElements.getDstEpOrdinals().getEpgId(), netElements.getSrcEpOrdinals().getCgId(),
+                                    netElements.getDstEpOrdinals().getCgId(), sIpPrefixes, dIpPrefixes,
+                                    netElements.getSrcNodeId(), netElements.getDstNodeId());
+                        }
+                        LOG.trace("PolicyEnforcer: Visiting PolicyPair {} endpoints {} {}", policyPair,
+                                netElements.getSrcEp().getKey(), netElements.getDstEp().getKey());
+
+                        // Preserve original rule direction
+                        Set<Direction> directions = getRuleDirections(rule);
+
+                        for(Direction direction : directions) {
+
+                            // Create list of matches/actions. Also creates chain flows when specific action requires it
+                            List<MatchBuilder> inMatches = createMatches(Direction.In, policyPair, tenant,
+                                    ruleWithMatches);
+                            List<MatchBuilder> outMatches = createMatches(Direction.Out, policyPair, tenant,
+                                    ruleWithMatches);
+
+                            List<ActionBuilder> actions = createActions(ofWriter, netElements, direction,
+                                    policyPair, tenant, ruleWithActions, false);
+
+                            // Compose flows
+                            createFlows(inMatches, actions, netElements, ofWriter, priority);
+                            createFlows(outMatches, actions, netElements, ofWriter, priority);
+
+                            priority -= 1;
+
+                            // Keep info about what direction has flows already created
+                            if (direction.equals(Direction.In)) {
+                                directPathFlowsCreated = true;
+                            }
+                            if (direction.equals(Direction.Out)) {
+                                reversePathFlowsCreated = true;
+                            }
+
+                            // Fully resolved Ep groups are saved to prevent duplicates
+                            if (directPathFlowsCreated && reversePathFlowsCreated) {
+                                LOG.trace("Epg pair added: {}, {} ", netElements.getSrcEpg(), netElements.getDstEpg());
+                                resolvedEpgPairs.put(netElements.getSrcEpg(), netElements.getDstEpg());
+                            }
+                        }
                     }
                 }
             }
-        }
 
-        // Write ARP flows per flood domain.
-        for (Integer fdId : fdIds) {
-            flowMap.writeFlow(nodeId, TABLE_ID, createArpFlow(fdId));
+        }
+        // Returns appropriate result of resolving
+        if (directPathFlowsCreated && reversePathFlowsCreated) {
+            return PathStatus.both;
+        } else if ((!directPathFlowsCreated && reversePathFlowsCreated) || (directPathFlowsCreated && !reversePathFlowsCreated)) {
+            return PathStatus.partial;
+        } else {
+            return PathStatus.none;
         }
     }
 
-    private Flow createArpFlow(Integer fdId) {
+    private Set<Direction> getRuleDirections(Rule ruleWithMatches) {
+        Set<Direction> directions = new HashSet<>();
+        for (ClassifierRef classifierRef : ruleWithMatches.getClassifierRef()) {
+            if (!directions.contains(classifierRef.getDirection()) && classifierRef.getDirection() == Direction.In) {
+                directions.add(classifierRef.getDirection());
+            }
+            if (!directions.contains(classifierRef.getDirection()) && classifierRef.getDirection() == Direction.Out) {
+                directions.add(classifierRef.getDirection());
+            }
+        }
+        if (directions.isEmpty()) {
+            directions.add(Direction.Bidirectional);
+        }
+        return directions;
+    }
 
-        Long etherType = FlowUtils.ARP;
-        // L2 Classifier so 20,000 for now
-        Integer priority = 20000;
-        FlowId flowid = new FlowId(new StringBuilder().append("arp")
-            .append("|")
-            .append(etherType)
-            .append("|")
-            .append(fdId)
-            .toString());
+    private Rule mergeRuleActions(Rule rule, Rule oppositeRule, IndexedTenant tenant) {
+        if (oppositeRule.equals(rule)) {
+            return rule;
+        }
 
-        MatchBuilder mb = new MatchBuilder().setEthernetMatch(FlowUtils.ethernetMatch(null, null, etherType));
+        Action ruleAction = null;
+        Action oppositeRuleAction = null;
 
-        addNxRegMatch(mb, RegMatch.of(NxmNxReg5.class, Long.valueOf(fdId)));
+        // For now, only allow action and chain action is supported
+        for (ActionRef actionRef : rule.getActionRef()) {
+            ActionInstance actionInstance = tenant.getAction(actionRef.getName());
+            if (actionRef.getOrder() == 0 && (actionInstance.getActionDefinitionId().equals(new AllowAction().getId()))) {
+                ruleAction = new AllowAction();
+            }
+            if (actionRef.getOrder() == 0 && (actionInstance.getActionDefinitionId().equals(new ChainAction().getId()))) {
+                ruleAction = new ChainAction();
+            }
+        }
+        for (ActionRef actionRef : oppositeRule.getActionRef()) {
+            ActionInstance actionInstance = tenant.getAction(actionRef.getName());
+            if (actionRef.getOrder() == 0 && (actionInstance.getActionDefinitionId().equals(new AllowAction().getId()))) {
+                oppositeRuleAction = new AllowAction();
+            }
+            if (actionRef.getOrder() == 0 && (actionInstance.getActionDefinitionId().equals(new ChainAction().getId()))) {
+                oppositeRuleAction = new ChainAction();
+            }
+        }
 
-        Flow flow = base().setPriority(priority)
-            .setId(flowid)
-            .setMatch(mb.build())
-            .setInstructions(instructions(applyActionIns(nxOutputRegAction(NxmNxReg7.class))))
-            .build();
-        return flow;
+        if (ruleAction == null && oppositeRuleAction == null) {
+            return null;
+        } else if (ruleAction != null && ruleAction.getClass().equals(AllowAction.class)) {
+            return oppositeRule;
+        } else if (oppositeRuleAction != null && oppositeRuleAction.getClass().equals(AllowAction.class)) {
+            return rule;
+        } else {
+            // TODO both rules have chain action - add support for more different chain actions. This works for now
+            return rule;
+        }
     }
 
-    private Flow allowSameEpg(int sepgId, int depgId) {
+    private Rule findRuleWithSpecificMatches(Rule rule, Rule oppositeRule, IndexedTenant tenant) {
+        if (oppositeRule.equals(rule)) {
+            return rule;
+        }
 
-        FlowId flowId = new FlowId(new StringBuilder().append("intraallow|").append(sepgId).toString());
-        MatchBuilder mb = new MatchBuilder();
-        addNxRegMatch(mb, RegMatch.of(NxmNxReg0.class, Long.valueOf(sepgId)),
-                RegMatch.of(NxmNxReg2.class, Long.valueOf(depgId)));
-        FlowBuilder flow = base().setId(flowId)
-            .setMatch(mb.build())
-            .setPriority(65000)
-            .setInstructions(instructions(applyActionIns(nxOutputRegAction(NxmNxReg7.class))));
-        return flow.build();
-    }
+        // TODO check all classifierRefs
+        ClassifierRef ruleClassifierRef = rule.getClassifierRef().get(0);
+        ClassifierRef oppositeRuleClassifierRef = oppositeRule.getClassifierRef().get(0);
 
-    private Flow allowFromTunnel(NodeConnectorId tunPort) {
+        ClassifierInstance ruleClassifierInstance = tenant.getClassifier(ruleClassifierRef.getInstanceName());
+        ClassifierInstance oppositeRuleClassifierInstance = tenant.getClassifier(oppositeRuleClassifierRef.getInstanceName());
 
-        FlowId flowId = new FlowId("tunnelallow");
-        MatchBuilder mb = new MatchBuilder().setInPort(tunPort);
-        addNxRegMatch(mb, RegMatch.of(NxmNxReg1.class, Long.valueOf(0xffffff)));
-        FlowBuilder flow = base().setId(flowId)
-            .setMatch(mb.build())
-            .setPriority(65000)
-            .setInstructions(instructions(applyActionIns(nxOutputRegAction(NxmNxReg7.class))));
-        return flow.build();
+        if (ruleClassifierInstance == null) {
+            LOG.trace("Classifier instance not found, ClassifierRef name: {} ", ruleClassifierRef.getInstanceName());
+            return null;
+        }
+        if (oppositeRuleClassifierInstance == null) {
+            LOG.trace("Opposite classifier instance not found, ClassifierRef name: {} ", oppositeRuleClassifierRef.getInstanceName());
+            return null;
+        }
 
-    }
+        // Check ethertype. Values must be equal
+        for (ParameterValue ruleParameter : ruleClassifierInstance.getParameterValue()) {
+            for (ParameterValue oppositeRuleParameter : oppositeRuleClassifierInstance.getParameterValue()) {
+                if ((ruleParameter.getName().getValue().equals(EtherTypeClassifierDefinition.ETHERTYPE_PARAM))
+                        && oppositeRuleParameter.getName().getValue().equals(EtherTypeClassifierDefinition.ETHERTYPE_PARAM)) {
+                    if (!ruleParameter.getIntValue().equals(oppositeRuleParameter.getIntValue())) {
+                        LOG.trace("Ethertype values are not equal, rule: {}, opposite rule: {} ", rule, oppositeRule);
+                        return null;
+                    }
+                }
+            }
+        }
+        // Check proto if exists. Values must be equal or missing
+        ParameterValue ruleProtoParameter = null;
+        ParameterValue oppositeRuleProtoParameter = null;
+        for (ParameterValue ruleParameter : ruleClassifierInstance.getParameterValue()) {
+            if (ruleParameter.getName().getValue().equals(IpProtoClassifierDefinition.PROTO_PARAM)) {
+                ruleProtoParameter = ruleParameter;
+            }
+        }
+        for (ParameterValue oppositeRuleParameter : oppositeRuleClassifierInstance.getParameterValue()) {
+            if (oppositeRuleParameter.getName().getValue().equals(IpProtoClassifierDefinition.PROTO_PARAM)) {
+                oppositeRuleProtoParameter = oppositeRuleParameter;
+            }
+        }
 
-    private void syncPolicy(FlowMap flowMap, NetworkElements netElements, List<RuleGroup> rgs, CgPair p) {
-        int priority = 65000;
-        for (RuleGroup rg : rgs) {
-            TenantId tenantId = rg.getContractTenant().getId();
-            IndexedTenant tenant = ctx.getPolicyResolver().getTenant(tenantId);
-            for (Rule r : rg.getRules()) {
-                syncDirection(flowMap, netElements, tenant, p, r, Direction.In, priority);
-                syncDirection(flowMap, netElements, tenant, p, r, Direction.Out, priority);
+        if (ruleProtoParameter == null || ruleProtoParameter.getIntValue() == null) {
+            return oppositeRule;
+        } else if (oppositeRuleProtoParameter == null || oppositeRuleProtoParameter.getIntValue() == null) {
+            return rule;
+        } else if (!ruleProtoParameter.getIntValue().equals(oppositeRuleProtoParameter.getIntValue())) {
+            LOG.trace("Proto parameters are not equal, rule parameters: {}, opposite rule parameters {} ",
+                    ruleProtoParameter, oppositeRuleProtoParameter);
+            return null;
+        }
+
+        // Check ports
+        // TODO add support for port ranges
+        ParameterValue ruleL4Src = null;
+        ParameterValue oppositeRuleL4Src = null;
+        ParameterValue ruleL4Dst = null;
+        ParameterValue oppositeRuleL4Dst = null;
 
-                priority -= 1;
+        for (ParameterValue ruleParameter : ruleClassifierInstance.getParameterValue()) {
+            if (ruleParameter.getName().getValue().equals(L4ClassifierDefinition.SRC_PORT_PARAM)) {
+                ruleL4Src = ruleParameter;
+            }
+            if (ruleParameter.getName().getValue().equals(L4ClassifierDefinition.DST_PORT_PARAM)) {
+                ruleL4Dst = ruleParameter;
+            }
+        }
+        for (ParameterValue oppositeRuleParameter : oppositeRuleClassifierInstance.getParameterValue()) {
+            if (oppositeRuleParameter.getName().getValue().equals(L4ClassifierDefinition.SRC_PORT_PARAM)) {
+                oppositeRuleL4Src = oppositeRuleParameter;
+            }
+            if (oppositeRuleParameter.getName().getValue().equals(L4ClassifierDefinition.DST_PORT_PARAM)) {
+                oppositeRuleL4Dst = oppositeRuleParameter;
             }
         }
-    }
 
-    /**
-     * Private internal class for ordering Actions in Rules. The order is
-     * determined first by the value of the order parameter, with the lower
-     * order actions being applied first; for Actions with either the same order
-     * or no order, ordering is lexicographical by name.
-     */
-    private static class ActionRefComparator implements Comparator<ActionRef> {
+        if (ruleL4Src == null && ruleL4Dst == null && oppositeRuleL4Src == null && oppositeRuleL4Dst == null) {
+            return rule;
+        }
 
-        public static final ActionRefComparator INSTANCE = new ActionRefComparator();
+        // Source rules
+        if (ruleL4Src == null && oppositeRuleL4Src != null) {
+            return oppositeRule;
+        }
+        if (ruleL4Src != null && oppositeRuleL4Src == null) {
+            return rule;
+        }
+        if (ruleL4Src != null && ruleL4Src.getIntValue() != null && oppositeRuleL4Src.getIntValue() != null
+                && ruleL4Src.equals(oppositeRuleL4Src)) {
+            return rule;
+        }
+        if (ruleL4Src != null && ruleL4Src.getIntValue() != null && oppositeRuleL4Src.getIntValue() != null
+                && !ruleL4Src.equals(oppositeRuleL4Src)) {
+            return null;
+        }
 
-        @Override
-        public int compare(ActionRef arg0, ActionRef arg1) {
-            return ComparisonChain.start()
-                .compare(arg0.getOrder(), arg1.getOrder(), Ordering.natural().nullsLast())
-                .compare(arg0.getName().getValue(), arg1.getName().getValue(), Ordering.natural().nullsLast())
-                .result();
+        // Destination rules
+        if (ruleL4Dst == null && oppositeRuleL4Dst != null) {
+            return oppositeRule;
+        }
+        if (ruleL4Dst != null && oppositeRuleL4Dst == null) {
+            return rule;
+        }
+        if (ruleL4Dst != null && ruleL4Dst.getIntValue() != null && oppositeRuleL4Dst.getIntValue() != null
+                && ruleL4Dst.equals(oppositeRuleL4Dst)) {
+            return rule;
+        }
+        if (ruleL4Dst != null && ruleL4Dst.getIntValue() != null && oppositeRuleL4Dst.getIntValue() != null
+                && !ruleL4Dst.equals(oppositeRuleL4Dst)) {
+            return null;
         }
 
+        return null;
     }
 
-    private void syncDirection(FlowMap flowMap, NetworkElements netElements, IndexedTenant contractTenant, CgPair cgPair, Rule rule,
-            Direction direction, int priority) {
-        /*
-         * Create the ordered action list. The implicit action is "allow", and
-         * is therefore always in the list
-         *
-         * TODO: revisit implicit vs. default for "allow" TODO: look into
-         * incorporating operational policy for actions
-         */
-
-        // TODO: can pass Comparator ActionRefComparator to List constructor, rather than
-        // referencing in sort
-        List<ActionBuilder> actionBuilderList = new ArrayList<ActionBuilder>();
-        if (rule.getActionRef() != null) {
-            /*
-             * Pre-sort by references using order, then name
-             */
-            List<ActionRef> arl = new ArrayList<ActionRef>(rule.getActionRef());
-            Collections.sort(arl, ActionRefComparator.INSTANCE);
+    private void resolveDestinationEpgPolicy(OfWriter ofWriter, NetworkElements netElements, Policy reversedPolicy,
+                                             boolean isReverted) {
+        isReversedPolicy = true;
+        for (Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>> activeRulesByConstraints : getActiveRulesBetweenEps(
+                reversedPolicy, netElements.getSrcEp(), netElements.getDstEp())) {
+            Set<IpPrefix> sIpPrefixes = Policy.getIpPrefixesFrom(activeRulesByConstraints.getRowKey()
+                    .getL3EpPrefixes());
+            Set<IpPrefix> dIpPrefixes = Policy.getIpPrefixesFrom(activeRulesByConstraints.getColumnKey()
+                    .getL3EpPrefixes());
+            PolicyPair policyPair = new PolicyPair(netElements.getSrcEpOrdinals().getEpgId(), netElements.getDstEpOrdinals().getEpgId(),
+                    netElements.getSrcEpOrdinals().getCgId(), netElements.getDstEpOrdinals().getCgId(), sIpPrefixes, dIpPrefixes,
+                    netElements.getSrcNodeId(), netElements.getDstNodeId());
+            if (visitedReversePairs.contains(policyPair)) {
+                LOG.trace(
+                        "PolicyEnforcer: Reverse: Already visited PolicyPair {}, endpoints {} {} skipped",
+                        policyPair, netElements.getSrcEp().getKey(), netElements.getDstEp().getKey());
+                continue;
+            } else {
+                LOG.trace("PolicyEnforcer: Reverse: Visiting: PolicyPair {} via endpoints {} {}",
+                        policyPair, netElements.getSrcEp().getKey(), netElements.getDstEp().getKey());
+                visitedReversePairs.add(policyPair);
 
-            for (ActionRef actionRule : arl) {
-                ActionInstance actionInstance = contractTenant.getAction(actionRule.getName());
-                if (actionInstance == null) {
-                    // XXX TODO fail the match and raise an exception
-                    LOG.warn("Action instance {} not found", actionRule.getName().getValue());
-                    return;
-                }
-                Action action = SubjectFeatures.getAction(actionInstance.getActionDefinitionId());
-                if (action == null) {
-                    // XXX TODO fail the match and raise an exception
-                    LOG.warn("Action definition {} not found", actionInstance.getActionDefinitionId().getValue());
-                    return;
-                }
+            }
+            int priority = 65000;
+            for (RuleGroup rg : activeRulesByConstraints.getValue()) {
+                TenantId tenantId = rg.getContractTenant().getId();
+                IndexedTenant tenant = ctx.getTenant(tenantId);
+                for (Rule rule : rg.getRules()) {
+
+                    Set<Direction> directions = getRuleDirections(rule);
+                    if (directions.isEmpty()) {
+                        continue;
+                    }
 
-                Map<String, Object> params = new HashMap<>();
-                if (actionInstance.getParameterValue() != null) {
-                    for (ParameterValue v : actionInstance.getParameterValue()) {
-                        if (v.getName() == null)
+                    for(Direction direction : directions) {
+
+                        // When specific direction flows exists, do not create them again
+                        if (direction.equals(Direction.In) && reversePathFlowsCreated) {
                             continue;
-                        if (v.getIntValue() != null) {
-                            params.put(v.getName().getValue(), v.getIntValue());
-                        } else if (v.getStringValue() != null) {
-                            params.put(v.getName().getValue(), v.getStringValue());
+                        }
+                        if (direction.equals(Direction.Out) && directPathFlowsCreated) {
+                            continue;
+                        }
+
+                        List<MatchBuilder> inMatches = createMatches(Direction.In, policyPair, tenant, rule);
+                        List<MatchBuilder> outMatches = createMatches(Direction.Out, policyPair, tenant, rule);
+
+                        // In case chain action is called here, it has to know that this is reversed policy to set tunnel
+                        // ordinal correctly
+                        List<ActionBuilder> inActions = createActions(ofWriter, netElements, Direction.In, policyPair, tenant,
+                                rule, isReverted);
+                        List<ActionBuilder> outActions = createActions(ofWriter, netElements, Direction.Out, policyPair, tenant,
+                                rule, isReverted);
+
+                        createFlows(inMatches, inActions, netElements, ofWriter, priority);
+                        createFlows(outMatches, outActions, netElements, ofWriter, priority);
+
+                        if (direction.equals(Direction.In)) {
+                            reversePathFlowsCreated = true;
+                        }
+                        if (direction.equals(Direction.Out)) {
+                            directPathFlowsCreated = true;
+                        }
+
+                        priority -= 1;
+
+                        if (directPathFlowsCreated && reversePathFlowsCreated) {
+                            resolvedEpgPairs.put(netElements.getSrcEpg(), netElements.getDstEpg());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void allowSameEpg(NodeId nodeId, OfWriter ofWriter) throws Exception {
+        for (Endpoint sourceEp : ctx.getEndpointManager().getEndpointsForNode(nodeId)) {
+            for (EgKey sourceEpgKey : ctx.getEndpointManager().getEgKeysForEndpoint(sourceEp)) {
+
+                IndexedTenant tenant = ctx.getTenant(sourceEpgKey.getTenantId());
+                if (tenant != null) {
+                    EndpointGroup group = tenant.getEndpointGroup(sourceEpgKey.getEgId());
+                    if (group == null) {
+                        LOG.debug("EPG {} does not exit and is used in EP {}", sourceEpgKey, sourceEp.getKey());
+                        continue;
+                    }
+                    IntraGroupPolicy igp = group.getIntraGroupPolicy();
+
+                    if (igp == null || igp.equals(IntraGroupPolicy.Allow)) {
+                        for (Endpoint dstEp : ctx.getEndpointManager().getEndpointsForGroup(sourceEpgKey)) {
+                            EndpointFwdCtxOrdinals srcEpFwdCxtOrdinals =
+                                    OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, sourceEp);
+                            if (srcEpFwdCxtOrdinals == null) {
+                                LOG.debug("getEndpointFwdCtxOrdinals is null for EP {}", sourceEp);
+                                continue;
+                            }
+
+                            EndpointFwdCtxOrdinals dstEpFwdCxtOrdinals =
+                                    OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, dstEp);
+                            if (dstEpFwdCxtOrdinals == null) {
+                                LOG.debug("getEndpointFwdCtxOrdinals is null for EP {}", dstEp);
+                                continue;
+                            }
+
+                            int destinationEpgId = dstEpFwdCxtOrdinals.getEpgId();
+                            int sourceEpgId = srcEpFwdCxtOrdinals.getEpgId();
+                            ofWriter.writeFlow(nodeId, TABLE_ID, allowSameEpg(sourceEpgId, destinationEpgId));
+                            ofWriter.writeFlow(nodeId, TABLE_ID, allowSameEpg(destinationEpgId, sourceEpgId));
                         }
                     }
                 }
-                /*
-                 * Convert the GBP Action to one or more OpenFlow Actions
-                 */
-                actionBuilderList = action.updateAction(actionBuilderList, params, actionRule.getOrder(),netElements);
             }
-        } else {
-            Action act = SubjectFeatures.getAction(AllowAction.DEFINITION.getId());
-            actionBuilderList = act.updateAction(actionBuilderList, new HashMap<String, Object>(), 0, netElements);
         }
+    }
+
+    // Return list of all rules with opposite direction
+    private List<Rule> findRulesInSameDirection(Rule ruleToResolve, List<Rule> reversedRules) {
+        List<Rule> sameDirectionRules = new ArrayList<>();
+        for (Rule ruleToCompare : reversedRules) {
+            for (ClassifierRef classifierRefToCompare : ruleToCompare.getClassifierRef()) {
+                for (ClassifierRef classifierRefToResolve : ruleToResolve.getClassifierRef()) {
+                    if (isDirectionOpposite(classifierRefToCompare.getDirection(), classifierRefToResolve.getDirection())) {
+                        sameDirectionRules.add(ruleToCompare);
+                    }
+                }
+            }
+        }
+        return sameDirectionRules;
+    }
+
+    private boolean isDirectionOpposite(Direction one, Direction two) {
+        return ((one.equals(Direction.In) && two.equals(Direction.Out))
+                || (one.equals(Direction.Out) && two.equals(Direction.In)));
+    }
+
+    private List<Rule> getRules(List<Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>>> activeRules) {
+        List<Rule> rules = new ArrayList<>();
+        for (Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>> activeRule : activeRules) {
+            for (RuleGroup ruleGroup : activeRule.getValue()) {
+                for (Rule rule : ruleGroup.getRules()) {
+                    rules.add(rule);
+                }
+            }
+        }
+        return rules;
+    }
+
+    private Flow createArpFlow(Integer fdId) {
+
+        Long etherType = FlowUtils.ARP;
+        // L2 Classifier so 20,000 for now
+        Integer priority = 20000;
+
+        MatchBuilder mb = new MatchBuilder().setEthernetMatch(FlowUtils.ethernetMatch(null, null, etherType));
+
+        addNxRegMatch(mb, RegMatch.of(NxmNxReg5.class, Long.valueOf(fdId)));
+
+        Match match = mb.build();
+        FlowId flowid = FlowIdUtils.newFlowId(TABLE_ID, "arp", match);
+        return base().setPriority(priority)
+                .setId(flowid)
+                .setMatch(match)
+                .setInstructions(instructions(applyActionIns(nxOutputRegAction(NxmNxReg7.class))))
+                .build();
+    }
+
+    private Flow allowSameEpg(int sourceEpgId, int destinationEpgId) {
+
+        MatchBuilder mb = new MatchBuilder();
+        addNxRegMatch(mb, RegMatch.of(NxmNxReg0.class, (long) sourceEpgId),
+                RegMatch.of(NxmNxReg2.class, (long) destinationEpgId));
+        Match match = mb.build();
+        FlowId flowId = FlowIdUtils.newFlowId(TABLE_ID, "intraallow", match);
+        FlowBuilder flow = base().setId(flowId)
+                .setMatch(match)
+                .setPriority(65000)
+                .setInstructions(instructions(applyActionIns(nxOutputRegAction(NxmNxReg7.class))));
+        return flow.build();
+    }
+
+    private Flow allowFromTunnel(NodeConnectorId tunPort) {
+
+        MatchBuilder mb = new MatchBuilder().setInPort(tunPort);
+        addNxRegMatch(mb, RegMatch.of(NxmNxReg1.class, 0xffffffL));
+        Match match = mb.build();
+        FlowId flowId = FlowIdUtils.newFlowId(TABLE_ID, "tunnelallow", match);
+        FlowBuilder flow = base().setId(flowId)
+                .setMatch(match)
+                .setPriority(65000)
+                .setInstructions(instructions(applyActionIns(nxOutputRegAction(NxmNxReg7.class))));
+        return flow.build();
 
+    }
+
+    private List<MatchBuilder> createMatches(Direction direction, PolicyPair policyPair, IndexedTenant contractTenant,
+                                             Rule rule) {
         Map<String, ParameterValue> paramsFromClassifier = new HashMap<>();
         Set<ClassifierDefinitionId> classifiers = new HashSet<>();
         for (ClassifierRef cr : rule.getClassifierRef()) {
+
             if (cr.getDirection() != null && !cr.getDirection().equals(Direction.Bidirectional)
                     && !cr.getDirection().equals(direction)) {
                 continue;
@@ -352,24 +742,22 @@ public class PolicyEnforcer extends FlowTable {
             // extension and data plane support - in 2.4. Will need to handle
             // case where we are working with mix of nodes.
 
-            ClassifierInstance ci = contractTenant.getClassifier(cr.getName());
+            ClassifierInstance ci = contractTenant.getClassifier(cr.getInstanceName());
             if (ci == null) {
                 // XXX TODO fail the match and raise an exception
-                LOG.warn("Classifier instance {} not found", cr.getName().getValue());
-                return;
+                LOG.warn("Classifier instance {} not found", cr.getInstanceName().getValue());
+                return null;
             }
-            Classifier cfier = SubjectFeatures.getClassifier(ci.getClassifierDefinitionId());
-            if (cfier == null) {
+            Classifier classifier = SubjectFeatures.getClassifier(ci.getClassifierDefinitionId());
+            if (classifier == null) {
                 // XXX TODO fail the match and raise an exception
                 LOG.warn("Classifier definition {} not found", ci.getClassifierDefinitionId().getValue());
-                return;
+                return null;
             }
             classifiers.add(new ClassifierDefinitionId(ci.getClassifierDefinitionId()));
             for (ParameterValue v : ci.getParameterValue()) {
                 if (paramsFromClassifier.get(v.getName().getValue()) == null) {
-                    if (v.getIntValue() != null
-                            || v.getStringValue() != null
-                            || v.getRangeValue() != null) {
+                    if (v.getIntValue() != null || v.getStringValue() != null || v.getRangeValue() != null) {
                         paramsFromClassifier.put(v.getName().getValue(), v);
                     }
                 } else {
@@ -380,28 +768,27 @@ public class PolicyEnforcer extends FlowTable {
                 }
             }
         }
-        if(classifiers.isEmpty()) {
-            return;
+        if (classifiers.isEmpty()) {
+            return null;
         }
         List<Map<String, ParameterValue>> derivedParamsByName = ParamDerivator.ETHER_TYPE_DERIVATOR.deriveParameter(paramsFromClassifier);
-        String baseId = createBaseFlowId(direction, cgPair, priority);
         List<MatchBuilder> flowMatchBuilders = new ArrayList<>();
         for (Map<String, ParameterValue> params : derivedParamsByName) {
             List<MatchBuilder> matchBuildersToResolve = new ArrayList<>();
-            if (cgPair.sIpPrefixes.isEmpty() && cgPair.dIpPrefixes.isEmpty()) {
-                matchBuildersToResolve.add(createBaseMatch(direction, cgPair, null, null));
-            } else if (!cgPair.sIpPrefixes.isEmpty() && cgPair.dIpPrefixes.isEmpty()) {
-                for (IpPrefix sIpPrefix : cgPair.sIpPrefixes) {
-                    matchBuildersToResolve.add(createBaseMatch(direction, cgPair, sIpPrefix, null));
+            if (policyPair.consumerEicIpPrefixes.isEmpty() && policyPair.providerEicIpPrefixes.isEmpty()) {
+                matchBuildersToResolve.add(createBaseMatch(direction, policyPair, null, null));
+            } else if (!policyPair.consumerEicIpPrefixes.isEmpty() && policyPair.providerEicIpPrefixes.isEmpty()) {
+                for (IpPrefix sIpPrefix : policyPair.consumerEicIpPrefixes) {
+                    matchBuildersToResolve.add(createBaseMatch(direction, policyPair, sIpPrefix, null));
                 }
-            } else if (cgPair.sIpPrefixes.isEmpty() && !cgPair.dIpPrefixes.isEmpty()) {
-                for (IpPrefix dIpPrefix : cgPair.sIpPrefixes) {
-                    matchBuildersToResolve.add(createBaseMatch(direction, cgPair, null, dIpPrefix));
+            } else if (policyPair.consumerEicIpPrefixes.isEmpty() && !policyPair.providerEicIpPrefixes.isEmpty()) {
+                for (IpPrefix dIpPrefix : policyPair.consumerEicIpPrefixes) {
+                    matchBuildersToResolve.add(createBaseMatch(direction, policyPair, null, dIpPrefix));
                 }
             } else {
-                for (IpPrefix sIpPrefix : cgPair.sIpPrefixes) {
-                    for (IpPrefix dIpPrefix : cgPair.sIpPrefixes) {
-                        matchBuildersToResolve.add(createBaseMatch(direction, cgPair, sIpPrefix, dIpPrefix));
+                for (IpPrefix sIpPrefix : policyPair.consumerEicIpPrefixes) {
+                    for (IpPrefix dIpPrefix : policyPair.consumerEicIpPrefixes) {
+                        matchBuildersToResolve.add(createBaseMatch(direction, policyPair, sIpPrefix, dIpPrefix));
                     }
                 }
             }
@@ -410,59 +797,133 @@ public class PolicyEnforcer extends FlowTable {
                 ClassificationResult result = classifier.updateMatch(matchBuildersToResolve, params);
                 if (!result.isSuccessfull()) {
                     // TODO consider different handling.
-                    throw new IllegalArgumentException("Classification conflict detected in rule: " + rule.getName() + ".\nCause: "
-                            + result.getErrorMessage());
+                    throw new IllegalArgumentException("Classification conflict detected in rule: " + rule.getName()
+                            + ".\nCause: " + result.getErrorMessage());
                 }
                 matchBuildersToResolve = new ArrayList<>(result.getMatchBuilders());
             }
             flowMatchBuilders.addAll(matchBuildersToResolve);
         }
-                FlowBuilder flow = base().setPriority(Integer.valueOf(priority));
-                for (MatchBuilder match : flowMatchBuilders) {
-                    Match m = match.build();
-                    FlowId flowId = new FlowId(baseId + "|" + m.toString());
-                    flow.setMatch(m)
-                        .setId(flowId)
-                        .setPriority(Integer.valueOf(priority))
-                        .setInstructions(instructions(applyActionIns(actionBuilderList)));
-                    flowMap.writeFlow(netElements.getNodeId(), TABLE_ID, flow.build());
+        return flowMatchBuilders;
+    }
+
+    private List<ActionBuilder> createActions(OfWriter ofWriter, NetworkElements netElements, Direction direction, PolicyPair policyPair,
+                                              IndexedTenant contractTenant, Rule rule, boolean isReversedDirection) {
+        List<ActionBuilder> actionBuilderList = new ArrayList<>();
+        if (rule.getActionRef() != null) {
+
+            // Pre-sort by references using order, then name
+            List<ActionRef> actionRefList = new ArrayList<>(rule.getActionRef());
+            Collections.sort(actionRefList, ActionRefComparator.INSTANCE);
+
+            for (ActionRef actionRule : actionRefList) {
+                ActionInstance actionInstance = contractTenant.getAction(actionRule.getName());
+                if (actionInstance == null) {
+                    // XXX TODO fail the match and raise an exception
+                    LOG.warn("Action instance {} not found", actionRule.getName().getValue());
+                    return null;
+                }
+                Action action = SubjectFeatures.getAction(actionInstance.getActionDefinitionId());
+                if (action == null) {
+                    // XXX TODO fail the match and raise an exception
+                    LOG.warn("Action definition {} not found", actionInstance.getActionDefinitionId().getValue());
+                    return null;
                 }
+
+                Map<String, Object> params = new HashMap<>();
+                if (actionInstance.getParameterValue() != null) {
+                    for (ParameterValue v : actionInstance.getParameterValue()) {
+                        if (v.getName() == null)
+                            continue;
+                        if (v.getIntValue() != null) {
+                            params.put(v.getName().getValue(), v.getIntValue());
+                        } else if (v.getStringValue() != null) {
+                            params.put(v.getName().getValue(), v.getStringValue());
+                        }
+                    }
+                }
+                if (isReversedDirection) {
+                    direction = reverse(direction);
+                }
+
+                // Convert the GBP Action to one or more OpenFlow Actions
+                if (!(actionRefList.indexOf(actionRule) == (actionRefList.size() - 1)
+                        && action.equals(SubjectFeatures.getAction(AllowActionDefinition.DEFINITION.getId())))) {
+                    actionBuilderList = action.updateAction(actionBuilderList, params, actionRule.getOrder(), netElements,
+                            policyPair, ofWriter, ctx, direction);
+                }
+            }
+        }
+
+        return actionBuilderList;
     }
 
-    private String createBaseFlowId(Direction direction, CgPair cgPair, int priority) {
-        StringBuilder idb = new StringBuilder();
+    private Direction reverse(Direction direction) {
         if (direction.equals(Direction.In)) {
-            idb.append(cgPair.sepg)
-                    .append("|")
-                    .append(cgPair.scgId)
-                    .append("|")
-                    .append(cgPair.depg)
-                    .append("|")
-                    .append(cgPair.dcgId)
-                    .append("|")
-                    .append(priority);
-        } else {
-            idb.append(cgPair.depg)
-                    .append("|")
-                    .append(cgPair.dcgId)
-                    .append("|")
-                    .append(cgPair.sepg)
-                    .append("|")
-                    .append(cgPair.scgId)
-                    .append("|")
-                    .append(priority);
-        }
-        return idb.toString();
+            return Direction.Out;
+        }
+        else if(direction.equals(Direction.Out)) {
+            return Direction.In;
+        }
+        else {
+            return Direction.Bidirectional;
+        }
     }
 
-    private MatchBuilder createBaseMatch(Direction direction, CgPair cgPair, IpPrefix sIpPrefix, IpPrefix dIpPrefix) {
+    private void createFlows(List<MatchBuilder> flowMatchBuilders, List<ActionBuilder> actionBuilderList, NetworkElements netElements,
+                             OfWriter ofWriter, int priority) {
+        FlowBuilder flow = base().setPriority(priority);
+        if(flowMatchBuilders == null) {
+            return;
+        }
+        for (MatchBuilder mb : flowMatchBuilders) {
+            Match match = mb.build();
+            FlowId flowId = FlowIdUtils.newFlowId(TABLE_ID, "cg", match);
+            flow.setMatch(match).setId(flowId).setPriority(priority);
+
+            // If destination is External, the last Action ALLOW must be changed to goto
+            // NAT/External table.
+            // If actionBuilderList is empty (we removed the last Allow) then go straight to
+            // ExternalMapper table.
+
+            List<ExternalImplicitGroup> eigs = ctx.getTenant(netElements.getDstEp().getTenant())
+                .getTenant()
+                .getPolicy()
+                .getExternalImplicitGroup();
+            boolean performNat = false;
+            for (EndpointL3 natEp : ctx.getEndpointManager().getL3EndpointsWithNat()) {
+                if (natEp.getMacAddress() != null &&
+                    natEp.getL2Context() != null &&
+                    netElements.getSrcEp().getKey().equals(new EndpointKey(natEp.getL2Context(),
+                        natEp.getMacAddress())) &&
+                    EndpointManager.isExternal(netElements.getDstEp(), eigs)) {
+                    performNat = true;
+                    break;
+                }
+            }
+            if (actionBuilderList == null) {
+                //TODO - analyse, what happen for unknown action, SFC, etc.
+                LOG.warn("Action builder list not found, partially flow which is not created: {}", flow.build());
+                continue;
+            }
+            if (actionBuilderList.isEmpty()) {
+                flow.setInstructions((performNat == true) ? instructions(gotoEgressNatInstruction) : instructions(gotoExternalInstruction));
+            } else {
+                flow.setInstructions(instructions(applyActionIns(actionBuilderList),
+                        (performNat == true) ? gotoEgressNatInstruction : gotoExternalInstruction));
+            }
+            ofWriter.writeFlow(netElements.getLocalNodeId(), TABLE_ID, flow.build());
+        }
+    }
+
+    private MatchBuilder createBaseMatch(Direction direction, PolicyPair policyPair, IpPrefix sIpPrefix,
+                                         IpPrefix dIpPrefix) {
         MatchBuilder baseMatch = new MatchBuilder();
         if (direction.equals(Direction.In)) {
-            addNxRegMatch(baseMatch,
-                    RegMatch.of(NxmNxReg0.class, Long.valueOf(cgPair.sepg)),
-                    RegMatch.of(NxmNxReg1.class, Long.valueOf(cgPair.scgId)),
-                    RegMatch.of(NxmNxReg2.class, Long.valueOf(cgPair.depg)),
-                    RegMatch.of(NxmNxReg3.class, Long.valueOf(cgPair.dcgId)));
+            addNxRegMatch(baseMatch, RegMatch.of(NxmNxReg0.class, (long) policyPair.consumerEpgId),
+                    RegMatch.of(NxmNxReg1.class, (long) policyPair.consumerCondGrpId),
+                    RegMatch.of(NxmNxReg2.class, (long) policyPair.providerEpgId),
+                    RegMatch.of(NxmNxReg3.class, (long) policyPair.providerCondGrpId));
             if (sIpPrefix != null) {
                 baseMatch.setLayer3Match(createLayer3Match(sIpPrefix, true));
             }
@@ -470,11 +931,10 @@ public class PolicyEnforcer extends FlowTable {
                 baseMatch.setLayer3Match(createLayer3Match(dIpPrefix, true));
             }
         } else {
-            addNxRegMatch(baseMatch,
-                    RegMatch.of(NxmNxReg0.class, Long.valueOf(cgPair.depg)),
-                    RegMatch.of(NxmNxReg1.class, Long.valueOf(cgPair.dcgId)),
-                    RegMatch.of(NxmNxReg2.class, Long.valueOf(cgPair.sepg)),
-                    RegMatch.of(NxmNxReg3.class, Long.valueOf(cgPair.scgId)));
+            addNxRegMatch(baseMatch, RegMatch.of(NxmNxReg0.class, (long) policyPair.providerEpgId),
+                    RegMatch.of(NxmNxReg1.class, (long) policyPair.providerCondGrpId),
+                    RegMatch.of(NxmNxReg2.class, (long) policyPair.consumerEpgId),
+                    RegMatch.of(NxmNxReg3.class, (long) policyPair.consumerCondGrpId));
             if (sIpPrefix != null) {
                 baseMatch.setLayer3Match(createLayer3Match(sIpPrefix, false));
             }
@@ -502,16 +962,14 @@ public class PolicyEnforcer extends FlowTable {
     }
 
     // TODO: move to a common utils for all renderers
-    public List<Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>>>
-                getActiveRulesBetweenEps(Policy policy, Endpoint consEp, Endpoint provEp) {
+    private List<Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>>> getActiveRulesBetweenEps(Policy policy,
+                                                                                                         Endpoint consEp, Endpoint provEp) {
         List<Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>>> rulesWithEpConstraints = new ArrayList<>();
-        if (policy.getRuleMap() != null) {
-            for (Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>> cell : policy.getRuleMap().cellSet()) {
-                EndpointConstraint consEpConstraint = cell.getRowKey();
-                EndpointConstraint provEpConstraint = cell.getColumnKey();
-                if (epMatchesConstraint(consEp, consEpConstraint) && epMatchesConstraint(provEp, provEpConstraint)) {
-                    rulesWithEpConstraints.add(cell);
-                }
+        for (Cell<EndpointConstraint, EndpointConstraint, List<RuleGroup>> cell : policy.getRuleMap().cellSet()) {
+            EndpointConstraint consEpConstraint = cell.getRowKey();
+            EndpointConstraint provEpConstraint = cell.getColumnKey();
+            if (epMatchesConstraint(consEp, consEpConstraint) && epMatchesConstraint(provEp, provEpConstraint)) {
+                rulesWithEpConstraints.add(cell);
             }
         }
         return rulesWithEpConstraints;
@@ -525,36 +983,86 @@ public class PolicyEnforcer extends FlowTable {
         return constraint.getConditionSet().matches(epConditions);
     }
 
-    @Immutable
-    private static class CgPair {
+    private enum PathStatus { both, partial, none }
+
+    public static boolean checkPolicyOrientation() {
+        return isReversedPolicy;
+    }
+
+    /**
+     * Private internal class for ordering Actions in Rules. The order is
+     * determined first by the value of the order parameter, with the lower
+     * order actions being applied first; for Actions with either the same order
+     * or no order, ordering is lexicographical by name.
+     */
+    private static class ActionRefComparator implements Comparator<ActionRef> {
 
-        private final int sepg;
-        private final int depg;
-        private final int scgId;
-        private final int dcgId;
-        private final Set<IpPrefix> sIpPrefixes;
-        private final Set<IpPrefix> dIpPrefixes;
+        public static final ActionRefComparator INSTANCE = new ActionRefComparator();
 
-        public CgPair(int sepg, int depg, int scgId, int dcgId, Set<IpPrefix> sIpPrefixes, Set<IpPrefix> dIpPrefixes) {
+        @Override
+        public int compare(ActionRef arg0, ActionRef arg1) {
+            return ComparisonChain.start()
+                    .compare(arg0.getOrder(), arg1.getOrder(), Ordering.natural().nullsLast())
+                    .compare(arg0.getName().getValue(), arg1.getName().getValue(), Ordering.natural().nullsLast())
+                    .result();
+        }
+
+    }
+
+    @Immutable
+    public static class PolicyPair {
+
+        private final int consumerEpgId;
+        private final int providerEpgId;
+        private final int consumerCondGrpId;
+        private final int providerCondGrpId;
+        private final Set<IpPrefix> consumerEicIpPrefixes;
+        private final Set<IpPrefix> providerEicIpPrefixes;
+        private final NodeId consumerEpNodeId;
+        private final NodeId providerEpNodeId;
+
+        public PolicyPair(int consumerEpgId, int providerEpgId, int consumerCondGrpId, int providerCondGrpId,
+                          Set<IpPrefix> consumerEicIpPrefixes, Set<IpPrefix> providerEicIpPrefixes, NodeId consumerEpNodeId, NodeId providerEpNodeId) {
             super();
-            this.sepg = sepg;
-            this.depg = depg;
-            this.scgId = scgId;
-            this.dcgId = dcgId;
-            this.sIpPrefixes = sIpPrefixes;
-            this.dIpPrefixes = dIpPrefixes;
+            this.consumerEpgId = consumerEpgId;
+            this.providerEpgId = providerEpgId;
+            this.consumerCondGrpId = consumerCondGrpId;
+            this.providerCondGrpId = providerCondGrpId;
+            this.consumerEicIpPrefixes = consumerEicIpPrefixes;
+            this.providerEicIpPrefixes = providerEicIpPrefixes;
+            this.consumerEpNodeId = consumerEpNodeId;
+            this.providerEpNodeId = providerEpNodeId;
+        }
+
+        public int getConsumerEpgId() {
+            return consumerEpgId;
+        }
+
+        public int getProviderEpgId() {
+            return providerEpgId;
+        }
+
+        public NodeId getConsumerEpNodeId() {
+            return consumerEpNodeId;
+        }
+
+        public NodeId getProviderEpNodeId() {
+            return providerEpNodeId;
         }
 
         @Override
         public int hashCode() {
             final int prime = 31;
             int result = 1;
-            result = prime * result + ((dIpPrefixes == null) ? 0 : dIpPrefixes.hashCode());
-            result = prime * result + dcgId;
-            result = prime * result + depg;
-            result = prime * result + ((sIpPrefixes == null) ? 0 : sIpPrefixes.hashCode());
-            result = prime * result + scgId;
-            result = prime * result + sepg;
+            result = prime * result + ((providerEicIpPrefixes == null) ? 0 : providerEicIpPrefixes.hashCode());
+            result = prime * result + providerCondGrpId;
+            result = prime * result + providerEpgId;
+            result = prime * result + ((consumerEicIpPrefixes == null) ? 0 : consumerEicIpPrefixes.hashCode());
+            result = prime * result + consumerCondGrpId;
+            result = prime * result + consumerEpgId;
+            result = prime * result + ((consumerEpNodeId == null) ? 0 : consumerEpNodeId.hashCode());
+            result = prime * result + ((providerEpNodeId == null) ? 0 : providerEpNodeId.hashCode());
+
             return result;
         }
 
@@ -566,69 +1074,131 @@ public class PolicyEnforcer extends FlowTable {
                 return false;
             if (getClass() != obj.getClass())
                 return false;
-            CgPair other = (CgPair) obj;
-            if (dIpPrefixes == null) {
-                if (other.dIpPrefixes != null)
+            PolicyPair other = (PolicyPair) obj;
+            if (providerEicIpPrefixes == null) {
+                if (other.providerEicIpPrefixes != null) {
                     return false;
-            } else if (!dIpPrefixes.equals(other.dIpPrefixes))
-                return false;
-            if (dcgId != other.dcgId)
+                }
+            } else if (!providerEicIpPrefixes.equals(other.providerEicIpPrefixes)) {
                 return false;
-            if (sIpPrefixes == null) {
-                if (other.sIpPrefixes != null)
+            }
+            if (consumerEicIpPrefixes == null) {
+                if (other.consumerEicIpPrefixes != null) {
                     return false;
-            } else if (!sIpPrefixes.equals(other.sIpPrefixes))
-                return false;
-            if (depg != other.depg)
+                }
+            } else if (!consumerEicIpPrefixes.equals(other.consumerEicIpPrefixes)) {
                 return false;
-            if (scgId != other.scgId)
+            }
+            if (consumerEpNodeId == null) {
+                if (other.consumerEpNodeId != null) {
+                    return false;
+                }
+            } else if (!consumerEpNodeId.getValue().equals(other.consumerEpNodeId.getValue())) {
                 return false;
-            if (sepg != other.sepg)
+            }
+            if (providerEpNodeId == null) {
+                if (other.providerEpNodeId != null) {
+                    return false;
+                }
+            } else if (!providerEpNodeId.getValue().equals(other.providerEpNodeId.getValue())) {
                 return false;
-            return true;
+            }
+            return (providerCondGrpId == other.providerCondGrpId)
+                    && (providerEpgId == other.providerEpgId)
+                    && (consumerCondGrpId == other.consumerCondGrpId)
+                    && (consumerEpgId == other.consumerEpgId);
+
+        }
+
+        @Override
+        public String toString() {
+            return "consumerEPG: " + consumerEpgId +
+                    "consumerCG: " + consumerCondGrpId +
+                    "providerEPG: " + providerEpgId +
+                    "providerCG: " + providerCondGrpId +
+                    "consumerEpNodeId: " + consumerEpNodeId +
+                    "providerEpNodeId: " + providerEpNodeId +
+                    "consumerEicIpPrefixes: " + consumerEicIpPrefixes +
+                    "providerEicIpPrefixes: " + providerEicIpPrefixes;
         }
     }
 
     public class NetworkElements {
-        Endpoint src;
-        Endpoint dst;
-        NodeId nodeId;
-        EndpointFwdCtxOrdinals srcOrds;
-        EndpointFwdCtxOrdinals dstOrds;
 
-        public NetworkElements(Endpoint src, Endpoint dst, NodeId nodeId, OfContext ctx, PolicyInfo policyInfo) throws Exception {
-            this.src=src;
-            this.dst=dst;
-            this.nodeId = nodeId;
-            this.srcOrds=OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, policyInfo, src);
-            this.dstOrds=OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, policyInfo, dst);
+        private final Endpoint srcEp;
+        private final Endpoint dstEp;
+        private final EgKey srcEpg;
+        private final EgKey dstEpg;
+        private NodeId srcNodeId;
+        private NodeId dstNodeId;
+        private final NodeId localNodeId;
+        private EndpointFwdCtxOrdinals srcEpOrdinals;
+        private EndpointFwdCtxOrdinals dstEpOrdinals;
+
+        public NetworkElements(Endpoint srcEp, Endpoint dstEp, EgKey srcEpg, EgKey dstEpg, NodeId nodeId, OfContext ctx) throws Exception {
+            this.srcEp = srcEp;
+            this.dstEp = dstEp;
+            this.srcEpg = srcEpg;
+            this.dstEpg = dstEpg;
+            this.localNodeId = nodeId;
+            this.srcEpOrdinals = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, srcEp);
+            if (this.srcEpOrdinals == null) {
+                LOG.debug("getEndpointFwdCtxOrdinals is null for EP {}", srcEp);
+                return;
+            }
+            this.dstEpOrdinals = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, dstEp);
+            if (this.dstEpOrdinals == null) {
+                LOG.debug("getEndpointFwdCtxOrdinals is null for EP {}", dstEp);
+                return;
+            }
+            if (dstEp.getAugmentation(OfOverlayContext.class) != null) {
+                this.dstNodeId = dstEp.getAugmentation(OfOverlayContext.class).getNodeId();
+            }
+            if (srcEp.getAugmentation(OfOverlayContext.class) != null) {
+                this.srcNodeId = srcEp.getAugmentation(OfOverlayContext.class).getNodeId();
+            }
+        }
+
+
+        public Endpoint getSrcEp() {
+            return srcEp;
         }
 
 
+        public Endpoint getDstEp() {
+            return dstEp;
+        }
+
+        public EgKey getSrcEpg() {
+            return srcEpg;
+        }
 
-        public EndpointFwdCtxOrdinals getSrcOrds() {
-            return srcOrds;
+        public EgKey getDstEpg() {
+            return dstEpg;
         }
 
+        public NodeId getSrcNodeId() {
+            return srcNodeId;
+        }
 
 
-        public EndpointFwdCtxOrdinals getDstOrds() {
-            return dstOrds;
+        public NodeId getDstNodeId() {
+            return dstNodeId;
         }
 
 
-        public Endpoint getSrc() {
-            return src;
+        public NodeId getLocalNodeId() {
+            return localNodeId;
         }
 
 
-        public Endpoint getDst() {
-            return dst;
+        public EndpointFwdCtxOrdinals getSrcEpOrdinals() {
+            return srcEpOrdinals;
         }
 
 
-        public NodeId getNodeId() {
-            return nodeId;
+        public EndpointFwdCtxOrdinals getDstEpOrdinals() {
+            return dstEpOrdinals;
         }