HONEYCOMB-139: ietf-acl translation layer. IP6 L3 ACL support
authorMarek Gradzki <mgradzki@cisco.com>
Fri, 26 Aug 2016 10:37:45 +0000 (12:37 +0200)
committerMarek Gradzki <mgradzki@cisco.com>
Fri, 26 Aug 2016 12:28:28 +0000 (14:28 +0200)
Other changes:
- documentation update
- eth + ip4 writer rafactoring + tests

Change-Id: I1ac6a4e99dd4f12c870cbd749af6b98018294dd4
Signed-off-by: Marek Gradzki <mgradzki@cisco.com>
12 files changed:
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/AclWriterFactory.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AbstractAceWriter.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriter.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4Writer.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6Writer.java [new file with mode: 0644]
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceWriter.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AclWriter.java [moved from v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/acl/AclWriter.java with 98% similarity]
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/IetfAClWriter.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/Readme.adoc [new file with mode: 0644]
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriterTest.java [new file with mode: 0644]
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4WriterTest.java
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6WriterTest.java [new file with mode: 0644]

index d0050ab..21bc2e6 100644 (file)
@@ -21,7 +21,7 @@ import static io.fd.honeycomb.translate.v3po.SubinterfaceAugmentationWriterFacto
 
 import com.google.common.collect.Sets;
 import io.fd.honeycomb.translate.impl.write.GenericListWriter;
-import io.fd.honeycomb.translate.v3po.acl.AclWriter;
+import io.fd.honeycomb.translate.v3po.interfaces.acl.AclWriter;
 import io.fd.honeycomb.translate.write.WriterFactory;
 import io.fd.honeycomb.translate.write.registry.ModifiableWriterRegistryBuilder;
 import javax.annotation.Nonnull;
index 9f27271..21a7107 100644 (file)
@@ -27,6 +27,7 @@ import java.util.stream.Collector;
 import javax.annotation.Nonnull;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.Ace;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.Permit;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.AceType;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.openvpp.jvpp.VppBaseCallException;
@@ -37,7 +38,21 @@ import org.openvpp.jvpp.core.dto.ClassifyAddDelTableReply;
 import org.openvpp.jvpp.core.dto.InputAclSetInterface;
 import org.openvpp.jvpp.core.future.FutureJVppCore;
 
+/**
+ * Base writer for translation of ietf-acl model ACEs to VPP's classify tables and sessions.
+ *
+ * Creates one classify table with single session per ACE.
+ *
+ * @param <T> type of access control list entry
+ */
 abstract class AbstractAceWriter<T extends AceType> implements AceWriter {
+
+    // TODO: minimise memory used by classify tables (we create a lot of them to make ietf-acl model
+    // mapping more convenient):
+    // according to https://wiki.fd.io/view/VPP/Introduction_To_N-tuple_Classifiers#Creating_a_classifier_table,
+    // classify table needs 16*(1 + match_n_vectors) bytes, but this does not quite work, so setting 8K for now
+    protected static final int TABLE_MEM_SIZE = 8 * 1024;
+
     private static final Collector<PacketHandling, ?, PacketHandling> SINGLE_ITEM_COLLECTOR =
         RWUtils.singleItemCollector();
 
@@ -47,19 +62,36 @@ abstract class AbstractAceWriter<T extends AceType> implements AceWriter {
         this.futureJVppCore = checkNotNull(futureJVppCore, "futureJVppCore should not be null");
     }
 
-    @Nonnull
-    public FutureJVppCore getFutureJVppCore() {
-        return futureJVppCore;
-    }
-
-    protected abstract ClassifyAddDelTable getClassifyAddDelTableRequest(@Nonnull final PacketHandling action,
-                                                                         @Nonnull final T ace,
-                                                                         final int nextTableIndex);
-
-    protected abstract ClassifyAddDelSession getClassifyAddDelSessionRequest(@Nonnull final PacketHandling action,
-                                                                             @Nonnull final T ace,
-                                                                             final int nextTableIndex);
-
+    /**
+     * Creates classify table for given ACE.
+     *
+     * @param action         packet handling action (permit/deny)
+     * @param ace            ACE to be translated
+     * @param nextTableIndex classify table index
+     * @return classify table that represents given ACE
+     */
+    protected abstract ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action,
+                                                               @Nonnull final T ace,
+                                                               final int nextTableIndex);
+
+    /**
+     * Creates classify session for given ACE.
+     *
+     * @param action     packet handling action (permit/deny)
+     * @param ace        ACE to be translated
+     * @param tableIndex classify table index for the given session
+     * @return classify session that represents given ACE
+     */
+    protected abstract ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action,
+                                                                   @Nonnull final T ace,
+                                                                   final int tableIndex);
+
+    /**
+     * Sets classify table index for input_acl_set_interface request.
+     *
+     * @param request    request DTO
+     * @param tableIndex pointer to a chain of classify tables
+     */
     protected abstract void setClassifyTable(@Nonnull final InputAclSetInterface request, final int tableIndex);
 
     public final void write(@Nonnull final InstanceIdentifier<?> id, @Nonnull final List<Ace> aces,
@@ -68,24 +100,17 @@ abstract class AbstractAceWriter<T extends AceType> implements AceWriter {
         final PacketHandling action = aces.stream().map(ace -> ace.getActions().getPacketHandling()).distinct()
             .collect(SINGLE_ITEM_COLLECTOR);
 
-        int firstTableIndex = -1;
         int nextTableIndex = -1;
         for (final Ace ace : aces) {
-            // Create table + session per entry. We actually need one table for each nonempty subset of params,
-            // so we could decrease number of tables to 109 = 15 (eth) + 31 (ip4) + 63 (ip6) for general case.
-            // TODO: For special cases like many ACEs of similar kind, it could be significant optimization.
+            // Create table + session per entry
 
             final ClassifyAddDelTable ctRequest =
-                getClassifyAddDelTableRequest(action, (T) ace.getMatches().getAceType(), nextTableIndex);
+                createClassifyTable(action, (T) ace.getMatches().getAceType(), nextTableIndex);
             nextTableIndex = createClassifyTable(id, ctRequest);
             createClassifySession(id,
-                getClassifyAddDelSessionRequest(action, (T) ace.getMatches().getAceType(), nextTableIndex));
-            if (firstTableIndex == -1) {
-                firstTableIndex = nextTableIndex;
-            }
+                createClassifySession(action, (T) ace.getMatches().getAceType(), nextTableIndex));
         }
-
-        setClassifyTable(request, firstTableIndex);
+        setClassifyTable(request, nextTableIndex);
     }
 
     private int createClassifyTable(@Nonnull final InstanceIdentifier<?> id,
@@ -104,4 +129,35 @@ abstract class AbstractAceWriter<T extends AceType> implements AceWriter {
 
         TranslateUtils.getReplyForWrite(cs.toCompletableFuture(), id);
     }
+
+    protected ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action, final int nextTableIndex) {
+        final ClassifyAddDelTable request = new ClassifyAddDelTable();
+        request.isAdd = 1;
+        request.tableIndex = -1; // value not present
+
+        request.nbuckets = 1; // we expect exactly one session per table
+
+        if (action instanceof Permit) {
+            request.missNextIndex = 0; // for list of permit rules, deny (0) should be default action
+        } else { // deny is default value
+            request.missNextIndex = -1; // for list of deny rules, permit (-1) should be default action
+        }
+
+        request.nextTableIndex = nextTableIndex;
+        request.memorySize = TABLE_MEM_SIZE;
+
+        return request;
+    }
+
+    protected ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action, final int tableIndex) {
+        final ClassifyAddDelSession request = new ClassifyAddDelSession();
+        request.isAdd = 1;
+        request.tableIndex = tableIndex;
+
+        if (action instanceof Permit) {
+            request.hitNextIndex = -1;
+        } // deny (0) is default value
+
+        return request;
+    }
 }
index 4744de1..1240a29 100644 (file)
 
 package io.fd.honeycomb.translate.v3po.interfaces.acl;
 
+import com.google.common.annotations.VisibleForTesting;
 import io.fd.honeycomb.translate.v3po.util.TranslateUtils;
 import java.util.List;
 import javax.annotation.Nonnull;
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.Permit;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceEth;
 import org.openvpp.jvpp.core.dto.ClassifyAddDelSession;
 import org.openvpp.jvpp.core.dto.ClassifyAddDelTable;
@@ -30,8 +30,10 @@ import org.openvpp.jvpp.core.future.FutureJVppCore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-class AceEthWriter extends AbstractAceWriter<AceEth> {
+final class AceEthWriter extends AbstractAceWriter<AceEth> {
 
+    @VisibleForTesting
+    static final int MATCH_N_VECTORS = 1;
     private static final Logger LOG = LoggerFactory.getLogger(AceEthWriter.class);
 
     public AceEthWriter(@Nonnull final FutureJVppCore futureJVppCore) {
@@ -39,22 +41,10 @@ class AceEthWriter extends AbstractAceWriter<AceEth> {
     }
 
     @Override
-    public ClassifyAddDelTable getClassifyAddDelTableRequest(@Nonnull final PacketHandling action,
-                                                             @Nonnull final AceEth aceEth,
-                                                             @Nonnull final int nextTableIndex) {
-        final ClassifyAddDelTable request = new ClassifyAddDelTable();
-        request.isAdd = 1;
-        request.tableIndex = -1; // value not present
-
-        request.nbuckets = 1; // we expect exactly one session per table
-
-        if (action instanceof Permit) {
-            request.missNextIndex = 0; // for list of permit rules, deny (0) should be default action
-        } else { // deny is default value
-            request.missNextIndex = -1; // for list of deny rules, permit (-1) should be default action
-        }
-
-        request.nextTableIndex = nextTableIndex;
+    public ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action,
+                                                   @Nonnull final AceEth aceEth,
+                                                   @Nonnull final int nextTableIndex) {
+        final ClassifyAddDelTable request = createClassifyTable(action, nextTableIndex);
 
         request.mask = new byte[16];
         boolean aceIsEmpty = true;
@@ -95,17 +85,11 @@ class AceEthWriter extends AbstractAceWriter<AceEth> {
 
         if (aceIsEmpty) {
             throw new IllegalArgumentException(
-                String.format("Ace %s does not define packet field matches", aceEth.toString()));
+                String.format("Ace %s does not define packet field match values", aceEth.toString()));
         }
 
         request.skipNVectors = 0;
-        request.matchNVectors = request.mask.length / 16;
-
-        // TODO: minimise memory used by classify tables (we create a lot of them to make ietf-acl model
-        // mapping more convenient):
-        // according to https://wiki.fd.io/view/VPP/Introduction_To_N-tuple_Classifiers#Creating_a_classifier_table,
-        // classify table needs 16*(1 + match_n_vectors) bytes, but this does not quite work, so setting 8K for now
-        request.memorySize = 8 * 1024;
+        request.matchNVectors = MATCH_N_VECTORS;
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("ACE action={}, rule={} translated to table={}.", action, aceEth,
@@ -115,16 +99,10 @@ class AceEthWriter extends AbstractAceWriter<AceEth> {
     }
 
     @Override
-    public ClassifyAddDelSession getClassifyAddDelSessionRequest(@Nonnull final PacketHandling action,
-                                                                 @Nonnull final AceEth aceEth,
-                                                                 @Nonnull final int tableIndex) {
-        final ClassifyAddDelSession request = new ClassifyAddDelSession();
-        request.isAdd = 1;
-        request.tableIndex = tableIndex;
-
-        if (action instanceof Permit) {
-            request.hitNextIndex = -1;
-        } // deny (0) is default value
+    public ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action,
+                                                       @Nonnull final AceEth aceEth,
+                                                       @Nonnull final int tableIndex) {
+        final ClassifyAddDelSession request = createClassifySession(action, tableIndex);
 
         request.match = new byte[16];
         boolean noMatch = true;
index ac110f9..b2a9613 100644 (file)
@@ -19,11 +19,11 @@ package io.fd.honeycomb.translate.v3po.interfaces.acl;
 import static com.google.common.base.Preconditions.checkArgument;
 import static io.fd.honeycomb.translate.v3po.util.TranslateUtils.ipv4AddressNoZoneToArray;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.primitives.Ints;
 import javax.annotation.Nonnull;
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.Permit;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIp;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv4;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;
@@ -34,52 +34,61 @@ import org.openvpp.jvpp.core.future.FutureJVppCore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-class AceIp4Writer extends AbstractAceWriter<AceIp> {
+final class AceIp4Writer extends AbstractAceWriter<AceIp> {
 
-    private static final Logger LOG = LoggerFactory.getLogger(AceIp4Writer.class);
-    private static final int TABLE_MASK_LENGTH = 48; // number of bytes
+    @VisibleForTesting
     static final int MATCH_N_VECTORS = 3; // number of 16B vectors
-    static final int TABLE_MEM_SIZE = 8 * 1024;
-    private static final int IP_MASK_LENGTH = 32; // number of bits
-    private static final int IP_VERSION_OFFSET = 14;
-    private static final int DSCP_OFFSET = 15;
-    private static final int SRC_IP_OFFSET = 26;
-    private static final int DST_IP_OFFSET = 30;
+    private static final Logger LOG = LoggerFactory.getLogger(AceIp4Writer.class);
+    private static final int TABLE_MASK_LENGTH = 48;
+    private static final int IP4_MASK_BIT_LENGTH = 32;
+
+    private static final int IP_VERSION_OFFSET = 14; // first 14 bytes represent L2 header (2x6 + etherType(2))
     private static final int IP_VERSION_MASK = 0xf0;
+    private static final int DSCP_OFFSET = 15;
     private static final int DSCP_MASK = 0xfc;
+    private static final int IP4_LEN = 4;
+    private static final int SRC_IP_OFFSET = IP_VERSION_OFFSET + 12;
+    private static final int DST_IP_OFFSET = SRC_IP_OFFSET + IP4_LEN;
 
     public AceIp4Writer(@Nonnull final FutureJVppCore futureJVppCore) {
         super(futureJVppCore);
     }
 
-    @Override
-    public ClassifyAddDelTable getClassifyAddDelTableRequest(@Nonnull final PacketHandling action,
-                                                             @Nonnull final AceIp aceIp,
-                                                             @Nonnull final int nextTableIndex) {
-        checkArgument(aceIp.getAceIpVersion() instanceof AceIpv4, "Expected AceIpv4 version, but was %", aceIp);
-        final AceIpv4 ipVersion = (AceIpv4) aceIp.getAceIpVersion();
-
-        final ClassifyAddDelTable request = new ClassifyAddDelTable();
-        request.isAdd = 1;
-        request.tableIndex = -1; // value not present
+    private static byte[] toByteMask(final int prefixLength) {
+        final long mask = ((1L << prefixLength) - 1) << (IP4_MASK_BIT_LENGTH - prefixLength);
+        return Ints.toByteArray((int) mask);
+    }
 
-        request.nbuckets = 1; // we expect exactly one session per table
+    private static byte[] toByteMask(final Ipv4Prefix ipv4Prefix) {
+        final int prefixLength = Byte.valueOf(ipv4Prefix.getValue().split("/")[1]);
+        return toByteMask(prefixLength);
+    }
 
-        if (action instanceof Permit) {
-            request.missNextIndex = 0; // for list of permit rules, deny (0) should be default action
-        } else { // deny is default value
-            request.missNextIndex = -1; // for list of deny rules, permit (-1) should be default action
+    private static byte[] toMatchValue(final Ipv4Prefix ipv4Prefix) {
+        final String[] split = ipv4Prefix.getValue().split("/");
+        final byte[] addressBytes = ipv4AddressNoZoneToArray(split[0]);
+        final byte[] mask = toByteMask(Byte.valueOf(split[1]));
+        for (int i = 0; i < addressBytes.length; ++i) {
+            addressBytes[i] &= mask[i];
         }
+        return addressBytes;
+    }
 
-        request.nextTableIndex = nextTableIndex;
+    @Override
+    public ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action,
+                                                   @Nonnull final AceIp aceIp,
+                                                   final int nextTableIndex) {
+        checkArgument(aceIp.getAceIpVersion() instanceof AceIpv4, "Expected AceIpv4 version, but was %", aceIp);
+        final AceIpv4 ipVersion = (AceIpv4) aceIp.getAceIpVersion();
+
+        final ClassifyAddDelTable request = createClassifyTable(action, nextTableIndex);
         request.skipNVectors = 0; // match entire L2 and L3 header
         request.matchNVectors = MATCH_N_VECTORS;
-        request.memorySize = TABLE_MEM_SIZE;
 
         boolean aceIsEmpty = true;
         request.mask = new byte[TABLE_MASK_LENGTH];
 
-        // First 14 bytes represent l2 header (2x6 + etherType(2)_
+        // First 14 bytes represent l2 header (2x6 + etherType(2))
         if (aceIp.getProtocol() != null) { // Internet Protocol number
             request.mask[IP_VERSION_OFFSET] = (byte) IP_VERSION_MASK; // first 4 bits
         }
@@ -99,17 +108,18 @@ class AceIp4Writer extends AbstractAceWriter<AceIp> {
 
         if (ipVersion.getSourceIpv4Network() != null) {
             aceIsEmpty = false;
-            System.arraycopy(toByteMask(ipVersion.getSourceIpv4Network()), 0, request.mask, SRC_IP_OFFSET, 4);
+            System.arraycopy(toByteMask(ipVersion.getSourceIpv4Network()), 0, request.mask, SRC_IP_OFFSET, IP4_LEN);
         }
 
         if (ipVersion.getDestinationIpv4Network() != null) {
             aceIsEmpty = false;
-            System.arraycopy(toByteMask(ipVersion.getDestinationIpv4Network()), 0, request.mask, DST_IP_OFFSET, 4);
+            System
+                .arraycopy(toByteMask(ipVersion.getDestinationIpv4Network()), 0, request.mask, DST_IP_OFFSET, IP4_LEN);
         }
 
         if (aceIsEmpty) {
             throw new IllegalArgumentException(
-                String.format("Ace %s does not define packet field matches", aceIp.toString()));
+                String.format("Ace %s does not define packet field match values", aceIp.toString()));
         }
 
         if (LOG.isDebugEnabled()) {
@@ -120,19 +130,13 @@ class AceIp4Writer extends AbstractAceWriter<AceIp> {
     }
 
     @Override
-    public ClassifyAddDelSession getClassifyAddDelSessionRequest(@Nonnull final PacketHandling action,
-                                                                 @Nonnull final AceIp aceIp,
-                                                                 @Nonnull final int tableIndex) {
+    public ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action,
+                                                       @Nonnull final AceIp aceIp,
+                                                       final int tableIndex) {
         checkArgument(aceIp.getAceIpVersion() instanceof AceIpv4, "Expected AceIpv4 version, but was %", aceIp);
         final AceIpv4 ipVersion = (AceIpv4) aceIp.getAceIpVersion();
 
-        final ClassifyAddDelSession request = new ClassifyAddDelSession();
-        request.isAdd = 1;
-        request.tableIndex = tableIndex;
-
-        if (action instanceof Permit) {
-            request.hitNextIndex = -1;
-        } // deny (0) is default value
+        final ClassifyAddDelSession request = createClassifySession(action, tableIndex);
 
         request.match = new byte[TABLE_MASK_LENGTH];
         boolean noMatch = true;
@@ -156,17 +160,18 @@ class AceIp4Writer extends AbstractAceWriter<AceIp> {
 
         if (ipVersion.getSourceIpv4Network() != null) {
             noMatch = false;
-            System.arraycopy(toMatchValue(ipVersion.getSourceIpv4Network()), 0, request.match, SRC_IP_OFFSET, 4);
+            System.arraycopy(toMatchValue(ipVersion.getSourceIpv4Network()), 0, request.match, SRC_IP_OFFSET, IP4_LEN);
         }
 
         if (ipVersion.getDestinationIpv4Network() != null) {
             noMatch = false;
-            System.arraycopy(toMatchValue(ipVersion.getDestinationIpv4Network()), 0, request.match, DST_IP_OFFSET, 4);
+            System.arraycopy(toMatchValue(ipVersion.getDestinationIpv4Network()), 0, request.match, DST_IP_OFFSET,
+                IP4_LEN);
         }
 
         if (noMatch) {
             throw new IllegalArgumentException(
-                String.format("Ace %s does not define neither source nor destination MAC address", aceIp.toString()));
+                String.format("Ace %s does not define packet field match values", aceIp.toString()));
         }
 
         if (LOG.isDebugEnabled()) {
@@ -180,24 +185,4 @@ class AceIp4Writer extends AbstractAceWriter<AceIp> {
     protected void setClassifyTable(@Nonnull final InputAclSetInterface request, final int tableIndex) {
         request.ip4TableIndex = tableIndex;
     }
-
-    private static byte[] toByteMask(final int prefixLength) {
-        final long mask = ((1L << prefixLength) - 1) << (IP_MASK_LENGTH - prefixLength);
-        return Ints.toByteArray((int) mask);
-    }
-
-    private static byte[] toByteMask(final Ipv4Prefix ipv4Prefix) {
-        final int prefixLength = Byte.valueOf(ipv4Prefix.getValue().split("/")[1]);
-        return toByteMask(prefixLength);
-    }
-
-    private static byte[] toMatchValue(final Ipv4Prefix ipv4Prefix) {
-        final String[] split = ipv4Prefix.getValue().split("/");
-        final byte[] addressBytes = ipv4AddressNoZoneToArray(split[0]);
-        final byte[] mask = toByteMask(Byte.valueOf(split[1]));
-        for (int i = 0; i < addressBytes.length; ++i) {
-            addressBytes[i] &= mask[i];
-        }
-        return addressBytes;
-    }
 }
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6Writer.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6Writer.java
new file mode 100644 (file)
index 0000000..c46f2e1
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.translate.v3po.interfaces.acl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.BitSet;
+import javax.annotation.Nonnull;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIp;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv6;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Prefix;
+import org.openvpp.jvpp.core.dto.ClassifyAddDelSession;
+import org.openvpp.jvpp.core.dto.ClassifyAddDelTable;
+import org.openvpp.jvpp.core.dto.InputAclSetInterface;
+import org.openvpp.jvpp.core.future.FutureJVppCore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class AceIp6Writer extends AbstractAceWriter<AceIp> {
+
+    @VisibleForTesting
+    static final int MATCH_N_VECTORS = 4; // number of 16B vectors
+    private static final Logger LOG = LoggerFactory.getLogger(AceIp6Writer.class);
+    private static final int TABLE_MASK_LENGTH = 64;
+    private static final int IP6_MASK_BIT_LENGTH = 128;
+
+    private static final int IP_VERSION_OFFSET = 14; // first 14 bytes represent L2 header (2x6 + etherType(2))
+    private static final int IP_VERSION_MASK = 0xf0;
+    private static final int DSCP_MASK1 = 0x0f;
+    private static final int DSCP_MASK2 = 0xc0;
+    private static final int IP6_LEN = 16;
+    private static final int SRC_IP_OFFSET = IP_VERSION_OFFSET + 8;
+    private static final int DST_IP_OFFSET = SRC_IP_OFFSET + IP6_LEN;
+
+    public AceIp6Writer(@Nonnull final FutureJVppCore futureJVppCore) {
+        super(futureJVppCore);
+    }
+
+    private static byte[] toByteMask(final int prefixLength) {
+        final BitSet mask = new BitSet(IP6_MASK_BIT_LENGTH);
+        mask.set(0, prefixLength, true);
+        if (prefixLength < IP6_MASK_BIT_LENGTH) {
+            mask.set(prefixLength, IP6_MASK_BIT_LENGTH, false);
+        }
+        return mask.toByteArray();
+    }
+
+    private static byte[] toByteMask(final Ipv6Prefix ipv6Prefix) {
+        final int prefixLength = Short.valueOf(ipv6Prefix.getValue().split("/")[1]);
+        return toByteMask(prefixLength);
+    }
+
+    private static byte[] toMatchValue(final Ipv6Prefix ipv6Prefix) {
+        final String[] split = ipv6Prefix.getValue().split("/");
+        final byte[] addressBytes;
+        try {
+            addressBytes = InetAddress.getByName(split[0]).getAddress();
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("Invalid IP6 address", e);
+        }
+        final byte[] mask = toByteMask(Short.valueOf(split[1]));
+        int pos = 0;
+        for (; pos < mask.length; ++pos) {
+            addressBytes[pos] &= mask[pos];
+        }
+        // mask can be shorter that address, so we need to clear rest of the address:
+        for (; pos < addressBytes.length; ++pos) {
+            addressBytes[pos] = 0;
+        }
+        return addressBytes;
+    }
+
+    @Override
+    public ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action,
+                                                   @Nonnull final AceIp aceIp,
+                                                   final int nextTableIndex) {
+        checkArgument(aceIp.getAceIpVersion() instanceof AceIpv6, "Expected AceIpv6 version, but was %", aceIp);
+        final AceIpv6 ipVersion = (AceIpv6) aceIp.getAceIpVersion();
+
+        final ClassifyAddDelTable request = createClassifyTable(action, nextTableIndex);
+        request.skipNVectors = 0; // match entire L2 and L3 header
+        request.matchNVectors = MATCH_N_VECTORS;
+
+        boolean aceIsEmpty = true;
+        request.mask = new byte[TABLE_MASK_LENGTH];
+        if (aceIp.getProtocol() != null) {
+            request.mask[IP_VERSION_OFFSET] |= IP_VERSION_MASK;
+        }
+
+        if (aceIp.getDscp() != null) {
+            aceIsEmpty = false;
+            // DCSP (bits 4-9 of IP6 header)
+            request.mask[IP_VERSION_OFFSET] |= DSCP_MASK1;
+            request.mask[IP_VERSION_OFFSET + 1] |= DSCP_MASK2;
+        }
+
+        if (aceIp.getSourcePortRange() != null) {
+            LOG.warn("L4 Header fields are not supported. Ignoring {}", aceIp.getSourcePortRange());
+        }
+
+        if (aceIp.getDestinationPortRange() != null) {
+            LOG.warn("L4 Header fields are not supported. Ignoring {}", aceIp.getDestinationPortRange());
+        }
+
+        if (ipVersion.getFlowLabel() != null) {
+            aceIsEmpty = false;
+            // bits 12-31
+            request.mask[IP_VERSION_OFFSET + 1] |= (byte) 0x0f;
+            request.mask[IP_VERSION_OFFSET + 2] = (byte) 0xff;
+            request.mask[IP_VERSION_OFFSET + 3] = (byte) 0xff;
+        }
+
+        if (ipVersion.getSourceIpv6Network() != null) {
+            aceIsEmpty = false;
+            final byte[] mask = toByteMask(ipVersion.getSourceIpv6Network());
+            System.arraycopy(mask, 0, request.mask, SRC_IP_OFFSET, mask.length);
+        }
+
+        if (ipVersion.getDestinationIpv6Network() != null) {
+            aceIsEmpty = false;
+            final byte[] mask = toByteMask(ipVersion.getDestinationIpv6Network());
+            System.arraycopy(mask, 0, request.mask, DST_IP_OFFSET, mask.length);
+        }
+
+        if (aceIsEmpty) {
+            throw new IllegalArgumentException(
+                String.format("Ace %s does not define packet field match values", aceIp.toString()));
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("ACE action={}, rule={} translated to table={}.", action, aceIp,
+                ReflectionToStringBuilder.toString(request));
+        }
+
+        return request;
+    }
+
+    @Override
+    public ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action,
+                                                       @Nonnull final AceIp aceIp,
+                                                       final int tableIndex) {
+        checkArgument(aceIp.getAceIpVersion() instanceof AceIpv6, "Expected AceIpv6 version, but was %", aceIp);
+        final AceIpv6 ipVersion = (AceIpv6) aceIp.getAceIpVersion();
+
+        final ClassifyAddDelSession request = createClassifySession(action, tableIndex);
+        request.match = new byte[TABLE_MASK_LENGTH];
+        boolean noMatch = true;
+
+        if (aceIp.getProtocol() != null) {
+            request.match[IP_VERSION_OFFSET] |= (byte) (IP_VERSION_MASK & (aceIp.getProtocol().intValue() << 4));
+        }
+
+        if (aceIp.getDscp() != null) {
+            noMatch = false;
+            final int dscp = aceIp.getDscp().getValue();
+            // set bits 4-9 of IP6 header:
+            request.match[IP_VERSION_OFFSET] |= (byte) (DSCP_MASK1 & (dscp >> 2));
+            request.match[IP_VERSION_OFFSET + 1] |= (byte) (DSCP_MASK2 & (dscp << 6));
+        }
+
+        if (aceIp.getSourcePortRange() != null) {
+            LOG.warn("L4 Header fields are not supported. Ignoring {}", aceIp.getSourcePortRange());
+        }
+
+        if (aceIp.getDestinationPortRange() != null) {
+            LOG.warn("L4 Header fields are not supported. Ignoring {}", aceIp.getDestinationPortRange());
+        }
+
+        if (ipVersion.getFlowLabel() != null) {
+            noMatch = false;
+            final int flowLabel = ipVersion.getFlowLabel().getValue().intValue();
+            // bits 12-31
+            request.match[IP_VERSION_OFFSET + 1] |= (byte) (0x0f & (flowLabel >> 16));
+            request.match[IP_VERSION_OFFSET + 2] = (byte) (0xff & (flowLabel >> 8));
+            request.match[IP_VERSION_OFFSET + 3] = (byte) (0xff & flowLabel);
+        }
+
+        if (ipVersion.getSourceIpv6Network() != null) {
+            noMatch = false;
+            final byte[] match = toMatchValue(ipVersion.getSourceIpv6Network());
+            System.arraycopy(match, 0, request.match, SRC_IP_OFFSET, IP6_LEN);
+        }
+
+        if (ipVersion.getDestinationIpv6Network() != null) {
+            noMatch = false;
+            final byte[] match = toMatchValue(ipVersion.getDestinationIpv6Network());
+            System.arraycopy(match, 0, request.match, DST_IP_OFFSET, IP6_LEN);
+        }
+
+        if (noMatch) {
+            throw new IllegalArgumentException(
+                String.format("Ace %s does not define packet field match values", aceIp.toString()));
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("ACE action={}, rule={} translated to session={}.", action, aceIp,
+                ReflectionToStringBuilder.toString(request));
+        }
+        return request;
+    }
+
+    @Override
+    protected void setClassifyTable(@Nonnull final InputAclSetInterface request, final int tableIndex) {
+        request.ip6TableIndex = tableIndex;
+    }
+}
index be3889f..2d66619 100644 (file)
@@ -24,7 +24,20 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.openvpp.jvpp.VppBaseCallException;
 import org.openvpp.jvpp.core.dto.InputAclSetInterface;
 
+/**
+ * Writer responsible for translation of ietf-acl model ACEs to VPP's classify tables and sessions.
+ */
 interface AceWriter {
+
+    /**
+     * Translates list of ACEs to chain of classify tables. Each ACE is translated into one classify table with single
+     * classify session. Also initializes input_acl_set_interface request message DTO with first classify table of the
+     * chain that was created.
+     *
+     * @param id      uniquely identifies ietf-acl container
+     * @param aces    list of access control entries
+     * @param request input_acl_set_interface request DTO
+     */
     void write(@Nonnull final InstanceIdentifier<?> id, @Nonnull final List<Ace> aces,
                @Nonnull final InputAclSetInterface request) throws VppBaseCallException, WriteTimeoutException;
 }
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package io.fd.honeycomb.translate.v3po.acl;
+package io.fd.honeycomb.translate.v3po.interfaces.acl;
 
 import io.fd.honeycomb.translate.spi.write.ListWriterCustomizer;
 import io.fd.honeycomb.translate.write.WriteContext;
index 58741cf..f4ba56d 100644 (file)
@@ -20,7 +20,6 @@ import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
-import io.fd.honeycomb.translate.v3po.acl.AclWriter;
 import io.fd.honeycomb.translate.v3po.util.TranslateUtils;
 import io.fd.honeycomb.translate.v3po.util.WriteTimeoutException;
 import io.fd.honeycomb.translate.write.WriteContext;
@@ -65,13 +64,33 @@ public final class IetfAClWriter {
         this.jvpp = Preconditions.checkNotNull(futureJVppCore, "futureJVppCore should not be null");
         aceWriters.put(AclType.ETH, new AceEthWriter(futureJVppCore));
         aceWriters.put(AclType.IP4, new AceIp4Writer(futureJVppCore));
+        aceWriters.put(AclType.IP6, new AceIp6Writer(futureJVppCore));
+    }
+
+    private static Stream<Ace> aclToAceStream(@Nonnull final Acl assignedAcl,
+                                              @Nonnull final WriteContext writeContext) {
+        final String aclName = assignedAcl.getName();
+        final Class<? extends AclBase> aclType = assignedAcl.getType();
+
+        // ietf-acl updates are handled first, so we use writeContext.readAfter
+        final Optional<org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl>
+            aclOptional = writeContext.readAfter(AclWriter.ACL_ID.child(
+            org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl.class,
+            new AclKey(aclName, aclType)));
+        checkArgument(aclOptional.isPresent(), "Acl lists not configured");
+        final org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl
+            acl = aclOptional.get();
+
+        final AccessListEntries accessListEntries = acl.getAccessListEntries();
+        checkArgument(accessListEntries != null, "access list entries not configured");
+
+        return accessListEntries.getAce().stream();
     }
 
     void deleteAcl(@Nonnull final InstanceIdentifier<?> id, final int swIfIndex)
         throws WriteTimeoutException, WriteFailedException.DeleteFailedException {
         final ClassifyTableByInterface request = new ClassifyTableByInterface();
         request.swIfIndex = swIfIndex;
-        jvpp.classifyTableByInterface(request);
 
         try {
             final CompletionStage<ClassifyTableByInterfaceReply> cs = jvpp.classifyTableByInterface(request);
@@ -138,26 +157,6 @@ public final class IetfAClWriter {
 
     }
 
-    private static Stream<Ace> aclToAceStream(@Nonnull final Acl assignedAcl,
-                                              @Nonnull final WriteContext writeContext) {
-        final String aclName = assignedAcl.getName();
-        final Class<? extends AclBase> aclType = assignedAcl.getType();
-
-        // ietf-acl updates are handled first, so we use writeContext.readAfter
-        final Optional<org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl>
-            aclOptional = writeContext.readAfter(AclWriter.ACL_ID.child(
-            org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl.class,
-            new AclKey(aclName, aclType)));
-        checkArgument(aclOptional.isPresent(), "Acl lists not configured");
-        final org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl
-            acl = aclOptional.get();
-
-        final AccessListEntries accessListEntries = acl.getAccessListEntries();
-        checkArgument(accessListEntries != null, "access list entries not configured");
-
-        return accessListEntries.getAce().stream();
-    }
-
     private enum AclType {
         ETH, IP4, IP6;
 
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/Readme.adoc b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/Readme.adoc
new file mode 100644 (file)
index 0000000..e59f72a
--- /dev/null
@@ -0,0 +1,32 @@
+= VPP to IETF-ACL model translation
+
+Package provides VPP translation code for draft-ietf-netmod-acl-model-08.
+Access control lists are mapped to chains of classify tables, each with single classify session.
+
+== Available operations
+
+=== Configuration data
+Configuration data for the model is stored in Honeycomb. Corresponding classify tables and sessions
+are not created until control access list is assigned to an interface.
+
+Classify tables and sessions are removed from VPP when ACL assignment is deleted.
+
+ACLs can be shared among interfaces, but each time, new instance of classify table chain would be created in VPP.
+
+ACLs that are assigned to an interface have to be unassigned before update/removal.
+
+=== Operational state
+Operational read in terms of ietf-acl model is not supported (would require storing additional metadata in vpp).
+As a consequence, configuration data initialization based on operational state is not possible.
+
+To check how ietf-acl model was translated to classify tables/session, low-level vpp-classfier model can be used.
+
+== Restrictions
+
+VPP classfier works in form of offsets and masks of 16B units.
+The offset always starts at the beginning of L2 Ethernet header
+of input packet. Because IP header can have variable length,
+source/destination port matching (L4 features of ietf-acl model) is not possible.
+
+Current implementation also assumes constant Ethernet header size
+(802.1Q headers are not supported).
\ No newline at end of file
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriterTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriterTest.java
new file mode 100644 (file)
index 0000000..a32659a
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.translate.v3po.interfaces.acl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.DenyBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceEth;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceEthBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
+import org.openvpp.jvpp.core.dto.ClassifyAddDelSession;
+import org.openvpp.jvpp.core.dto.ClassifyAddDelTable;
+import org.openvpp.jvpp.core.dto.InputAclSetInterface;
+import org.openvpp.jvpp.core.future.FutureJVppCore;
+
+public class AceEthWriterTest {
+
+    @Mock
+    private FutureJVppCore jvpp;
+    private AceEthWriter writer;
+    private PacketHandling action;
+    private AceEth aceEth;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        writer = new AceEthWriter(jvpp);
+        action = new DenyBuilder().setDeny(true).build();
+        aceEth = new AceEthBuilder()
+            .setDestinationMacAddress(new MacAddress("11:22:33:44:55:66"))
+            .setDestinationMacAddressMask(new MacAddress("ff:ff:ff:ff:ff:ff"))
+            .setSourceMacAddress(new MacAddress("aa:bb:cc:dd:ee:ff"))
+            .setSourceMacAddressMask(new MacAddress("ff:ff:ff:00:00:00"))
+            .build();
+    }
+
+    @Test
+    public void testGetClassifyAddDelTableRequest() throws Exception {
+        final int nextTableIndex = 42;
+        final ClassifyAddDelTable request = writer.createClassifyTable(action, aceEth, nextTableIndex);
+
+        assertEquals(1, request.isAdd);
+        assertEquals(-1, request.tableIndex);
+        assertEquals(1, request.nbuckets);
+        assertEquals(-1, request.missNextIndex);
+        assertEquals(nextTableIndex, request.nextTableIndex);
+        assertEquals(0, request.skipNVectors);
+        assertEquals(AceEthWriter.MATCH_N_VECTORS, request.matchNVectors);
+        assertEquals(AceEthWriter.TABLE_MEM_SIZE, request.memorySize);
+
+        byte[] expectedMask = new byte[] {
+            // destination MAC:
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            // source MAC:
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, 0, 0, 0,
+            0, 0, 0, 0
+        };
+        assertArrayEquals(expectedMask, request.mask);
+    }
+
+    @Test
+    public void testGetClassifyAddDelSessionRequest() throws Exception {
+        final int tableIndex = 123;
+        final ClassifyAddDelSession request = writer.createClassifySession(action, aceEth, tableIndex);
+
+        assertEquals(1, request.isAdd);
+        assertEquals(tableIndex, request.tableIndex);
+        assertEquals(0, request.hitNextIndex);
+
+        byte[] expectedMatch = new byte[] {
+            // destination MAC:
+            (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+            // source MAC:
+            (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff,
+            0, 0, 0, 0
+        };
+        assertArrayEquals(expectedMatch, request.match);
+    }
+
+    @Test
+    public void testSetClassifyTable() throws Exception {
+        final int tableIndex = 321;
+        final InputAclSetInterface request = new InputAclSetInterface();
+        writer.setClassifyTable(request, tableIndex);
+        assertEquals(tableIndex, request.l2TableIndex);
+    }
+}
\ No newline at end of file
index 5be0ea0..95b8fc5 100644 (file)
@@ -36,6 +36,7 @@ import org.openvpp.jvpp.core.dto.InputAclSetInterface;
 import org.openvpp.jvpp.core.future.FutureJVppCore;
 
 public class AceIp4WriterTest {
+
     @Mock
     private FutureJVppCore jvpp;
     private AceIp4Writer writer;
@@ -60,7 +61,7 @@ public class AceIp4WriterTest {
     @Test
     public void testGetClassifyAddDelTableRequest() throws Exception {
         final int nextTableIndex = 42;
-        final ClassifyAddDelTable request = writer.getClassifyAddDelTableRequest(action, aceIp, nextTableIndex);
+        final ClassifyAddDelTable request = writer.createClassifyTable(action, aceIp, nextTableIndex);
 
         assertEquals(1, request.isAdd);
         assertEquals(-1, request.tableIndex);
@@ -82,7 +83,7 @@ public class AceIp4WriterTest {
     @Test
     public void testGetClassifyAddDelSessionRequest() throws Exception {
         final int tableIndex = 123;
-        final ClassifyAddDelSession request = writer.getClassifyAddDelSessionRequest(action, aceIp, tableIndex);
+        final ClassifyAddDelSession request = writer.createClassifySession(action, aceIp, tableIndex);
 
         assertEquals(1, request.isAdd);
         assertEquals(tableIndex, request.tableIndex);
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6WriterTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6WriterTest.java
new file mode 100644 (file)
index 0000000..1818468
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.translate.v3po.interfaces.acl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.DenyBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIp;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIpBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv6Builder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Dscp;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6FlowLabel;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Prefix;
+import org.openvpp.jvpp.core.dto.ClassifyAddDelSession;
+import org.openvpp.jvpp.core.dto.ClassifyAddDelTable;
+import org.openvpp.jvpp.core.dto.InputAclSetInterface;
+import org.openvpp.jvpp.core.future.FutureJVppCore;
+
+public class AceIp6WriterTest {
+
+    @Mock
+    private FutureJVppCore jvpp;
+    private AceIp6Writer writer;
+    private PacketHandling action;
+    private AceIp aceIp;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        writer = new AceIp6Writer(jvpp);
+        action = new DenyBuilder().setDeny(true).build();
+        aceIp = new AceIpBuilder()
+            .setProtocol((short) 6)
+            .setDscp(new Dscp((short) 11))
+            .setAceIpVersion(new AceIpv6Builder()
+                .setFlowLabel(new Ipv6FlowLabel(123L))
+                .setSourceIpv6Network(new Ipv6Prefix("2001:db8:85a3:8d3:1319:8a2e:370:7348/128"))
+                .setDestinationIpv6Network(new Ipv6Prefix("fe80:1234:5678:abcd:ef01::/64"))
+                .build())
+            .build();
+    }
+
+    @Test
+    public void testGetClassifyAddDelTableRequest() throws Exception {
+        final int nextTableIndex = 42;
+        final ClassifyAddDelTable request = writer.createClassifyTable(action, aceIp, nextTableIndex);
+
+        assertEquals(1, request.isAdd);
+        assertEquals(-1, request.tableIndex);
+        assertEquals(1, request.nbuckets);
+        assertEquals(-1, request.missNextIndex);
+        assertEquals(nextTableIndex, request.nextTableIndex);
+        assertEquals(0, request.skipNVectors);
+        assertEquals(AceIp6Writer.MATCH_N_VECTORS, request.matchNVectors);
+        assertEquals(AceIp6Writer.TABLE_MEM_SIZE, request.memorySize);
+
+        byte[] expectedMask = new byte[] {
+            // L2:
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            // version, dscp, flow:
+            (byte) 0xff, (byte) 0xcf, (byte) 0xff, (byte) 0xff,
+            0, 0, 0, 0,
+            // source address:
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            // destination address:
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            0, 0, 0, 0, 0, 0, 0, 0,
+            // padding to multiple of 16B:
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+        };
+        assertArrayEquals(expectedMask, request.mask);
+    }
+
+    @Test
+    public void testGetClassifyAddDelSessionRequest() throws Exception {
+        final int tableIndex = 123;
+        final ClassifyAddDelSession request = writer.createClassifySession(action, aceIp, tableIndex);
+
+        assertEquals(1, request.isAdd);
+        assertEquals(tableIndex, request.tableIndex);
+        assertEquals(0, request.hitNextIndex);
+
+        byte[] expectedMatch = new byte[] {
+            // L2:
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            // version(6), dscp(11), flow(123):
+            (byte) 0x62, (byte) 0xc0, (byte) 0x00, (byte) 0x7b,
+            0, 0, 0, 0,
+            // source address:
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, (byte) 0x85, (byte) 0xa3, (byte) 0x08, (byte) 0xd3,
+            (byte) 0x13, (byte) 0x19, (byte) 0x8a, (byte) 0x2e, (byte) 0x03, (byte) 0x70, (byte) 0x73, (byte) 0x48,
+            // destination address:
+            (byte) 0xfe, (byte) 0x80, (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78, (byte) 0xab, (byte) 0xcd,
+            0, 0, 0, 0, 0, 0, 0, 0,
+            // padding to multiple of 16B:
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+        };
+        assertArrayEquals(expectedMatch, request.match);
+    }
+
+    @Test
+    public void testSetClassifyTable() throws Exception {
+        final int tableIndex = 321;
+        final InputAclSetInterface request = new InputAclSetInterface();
+        writer.setClassifyTable(request, tableIndex);
+        assertEquals(tableIndex, request.ip6TableIndex);
+    }
+}
\ No newline at end of file