HONEYCOMG-47: Tap interface CRUD support 40/1040/5
authorMaros Marsalek <mmarsale@cisco.com>
Fri, 6 May 2016 07:41:51 +0000 (09:41 +0200)
committerMaros Marsalek <mmarsale@cisco.com>
Wed, 11 May 2016 07:21:01 +0000 (09:21 +0200)
Tap interface specific configuration and state was added to V3po
yang model.
TapCustomizer added.
Fixed customizers for Interfaces state.
Fixed bug in Bridge domain customizers.

Change-Id: I9dd47b8ada5153df8732c02cb59d331ab1adc71e
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
16 files changed:
v3po/api/src/main/yang/v3po.yang
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveAugmentReaderCustomizer.java [new file with mode: 0644]
v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveAugmentReaderCustomizerTest.java [new file with mode: 0644]
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfaces/InterfaceCustomizer.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfaces/TapCustomizer.java [new file with mode: 0644]
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfacesstate/EthernetCustomizer.java [moved from v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfacesstate/VppInterfaceStateCustomizer.java with 60% similarity]
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfacesstate/InterfaceCustomizer.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfacesstate/InterfaceUtils.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfacesstate/TapCustomizer.java [new file with mode: 0644]
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/utils/V3poUtils.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/vppstate/BridgeDomainCustomizer.java
v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/InterfacesHoneycombWriterModule.java
v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/InterfacesStateHoneycombReaderModule.java
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/interfaces/TapCustomizerTest.java [new file with mode: 0644]
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/interfacesstate/InterfaceCustomizerTest.java
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/utils/V3poUtilsTest.java

index 9587cc6..065ff65 100644 (file)
@@ -49,6 +49,10 @@ module v3po {
   identity vhost-user {
     base if:interface-type;
   }
+  
+  identity tap {
+    base if:interface-type;
+  }
 
   typedef vxlan-vni {
     // FIXME: should be in a vxlan-specific model
@@ -94,6 +98,26 @@ module v3po {
     }
   }
 
+  grouping tap-interface-base-attributes {
+    leaf tap-name {
+      type string;
+    }
+  }
+
+  grouping tap-interface-config-attributes {
+    leaf mac {
+      type yang:phys-address;
+      mandatory false;
+      description "Mac address to be set for the tap interface. Random will be used if not configured";
+    }
+
+    leaf device-instance {
+      type uint32;
+      mandatory false;
+      description "Custom device instance. Autogenerated will be used if not configured";
+    }
+  }
+
   augment /if:interfaces/if:interface {
     ext:augment-identifier "vpp-interface-augmentation";
 
@@ -101,6 +125,14 @@ module v3po {
     // 1. The link between interface type and this augmentation is unclear
     // 2. Only this augmentation with combination of ifc type is trigger to do something for vpp, what if user only configures base interface stuff ? + We need to get leaves defined by ietf-interfaces when we are processing this augment
 
+    // TODO grouping
+    container tap {
+      when "../if:type = 'v3po:tap'";
+
+      uses tap-interface-base-attributes;
+      uses tap-interface-config-attributes;
+    }
+
     container ethernet {
       when "../if:type = 'ianaift:ethernetCsmacd'";
       leaf mtu {
@@ -245,6 +277,12 @@ module v3po {
     leaf description {
       type string;
     }
+
+    container tap {
+      when "../if:type = 'v3po:tap'";
+      uses tap-interface-base-attributes;
+    }
+
     container ethernet {
       when "../if:type = 'ianaift:ethernetCsmacd'";
       leaf mtu {
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveAugmentReaderCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveAugmentReaderCustomizer.java
new file mode 100644 (file)
index 0000000..38107ed
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.v3po.translate.util.read;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Lists;
+import io.fd.honeycomb.v3po.translate.spi.read.ChildReaderCustomizer;
+import io.fd.honeycomb.v3po.translate.util.ReflectionUtils;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.opendaylight.yangtools.concepts.Builder;
+import org.opendaylight.yangtools.yang.binding.Augmentation;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+/**
+ * Might be slow !
+ */
+public class ReflexiveAugmentReaderCustomizer<C extends DataObject, B extends Builder<C>>
+    extends ReflexiveRootReaderCustomizer<C, B>
+    implements ChildReaderCustomizer<C,B> {
+
+    private final Class<C> augType;
+
+    public ReflexiveAugmentReaderCustomizer(final Class<B> builderClass, final Class<C> augType) {
+        super(builderClass);
+        this.augType = augType;
+    }
+
+    @Override
+    public void merge(final Builder<? extends DataObject> parentBuilder, final C readValue) {
+        final Optional<Method> method =
+            ReflectionUtils.findMethodReflex(parentBuilder.getClass(), "addAugmentation",
+                Lists.newArrayList(Class.class, Augmentation.class), parentBuilder.getClass());
+
+        checkArgument(method.isPresent(), "Not possible to add augmentations to builder: %s", parentBuilder);
+        try {
+            method.get().invoke(parentBuilder, augType, readValue);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new IllegalArgumentException("Unable to set " + readValue + " to " + parentBuilder, e);
+        }
+    }
+
+}
diff --git a/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveAugmentReaderCustomizerTest.java b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveAugmentReaderCustomizerTest.java
new file mode 100644 (file)
index 0000000..3edc001
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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.v3po.translate.util.read;
+
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.InterfaceBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceStateAugmentation;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceStateAugmentationBuilder;
+
+public class ReflexiveAugmentReaderCustomizerTest {
+
+    private ReflexiveAugmentReaderCustomizer<VppInterfaceStateAugmentation, VppInterfaceStateAugmentationBuilder>
+        vppIfcStateAugmentCustomizer;
+
+    @Before
+    public void setUp() throws Exception {
+        vppIfcStateAugmentCustomizer =
+            new ReflexiveAugmentReaderCustomizer<>(VppInterfaceStateAugmentationBuilder.class,
+                VppInterfaceStateAugmentation.class);
+    }
+
+    @Test
+    public void testAddAugment() throws Exception {
+        final InterfaceBuilder parentBuilder = new InterfaceBuilder();
+        final VppInterfaceStateAugmentation augmentation = vppIfcStateAugmentCustomizer.getBuilder(null).build();
+        vppIfcStateAugmentCustomizer.merge(parentBuilder, augmentation);
+        assertSame(augmentation, parentBuilder.getAugmentation(VppInterfaceStateAugmentation.class));
+    }
+}
\ No newline at end of file
index 5d5a6c0..b6d8748 100644 (file)
@@ -97,15 +97,12 @@ public class InterfaceCustomizer extends FutureJVppCustomizer implements ListWri
 
     private void setInterface(final InstanceIdentifier<Interface> id, final Interface swIf)
         throws VppApiInvocationException, WriteFailedException {
-        LOG.info("Setting interface {}, type: {}", swIf.getName(), swIf.getType().getSimpleName());
-        LOG.debug("Setting interface {}", swIf);
-
+        LOG.debug("Setting interface: {} to: {}", id, swIf);
         setInterfaceAttributes(swIf, swIf.getName());
     }
 
     private void setInterfaceAttributes(final Interface swIf, final String swIfName)
         throws VppApiInvocationException {
-        LOG.debug("Creating {} interface {}", swIf.getType().getSimpleName(), swIf.getName());
 
         setInterfaceFlags(swIfName, interfaceContext.getIndex(swIfName),
             swIf.isEnabled() ? (byte) 1 : (byte) 0);
@@ -114,8 +111,7 @@ public class InterfaceCustomizer extends FutureJVppCustomizer implements ListWri
     private void updateInterface(final InstanceIdentifier<Interface> id,
                                  final Interface dataBefore,
                                  final Interface dataAfter) throws VppApiInvocationException {
-        LOG.info("Updating interface {}, type: {}", dataAfter.getName(), dataAfter.getType().getSimpleName());
-        LOG.debug("Updating interface {}", dataAfter);
+        LOG.debug("Updating interface:{} to: {}", id, dataAfter);
 
         setInterfaceAttributes(dataAfter, dataAfter.getName());
     }
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfaces/TapCustomizer.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfaces/TapCustomizer.java
new file mode 100644 (file)
index 0000000..f05238d
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * 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.v3po.translate.v3po.interfaces;
+
+import com.google.common.base.Optional;
+import io.fd.honeycomb.v3po.translate.Context;
+import io.fd.honeycomb.v3po.translate.spi.write.ChildWriterCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.util.FutureJVppCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
+import io.fd.honeycomb.v3po.translate.v3po.util.VppApiInvocationException;
+import io.fd.honeycomb.v3po.translate.v3po.utils.V3poUtils;
+import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
+import java.util.concurrent.CompletionStage;
+import javax.annotation.Nonnull;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.PhysAddress;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceAugmentation;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Tap;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.openvpp.jvpp.dto.TapConnect;
+import org.openvpp.jvpp.dto.TapConnectReply;
+import org.openvpp.jvpp.dto.TapDelete;
+import org.openvpp.jvpp.dto.TapDeleteReply;
+import org.openvpp.jvpp.dto.TapModify;
+import org.openvpp.jvpp.dto.TapModifyReply;
+import org.openvpp.jvpp.future.FutureJVpp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TapCustomizer extends FutureJVppCustomizer implements ChildWriterCustomizer<Tap> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TapCustomizer.class);
+    private final NamingContext interfaceContext;
+
+    public TapCustomizer(final FutureJVpp vppApi, final NamingContext interfaceContext) {
+        super(vppApi);
+        this.interfaceContext = interfaceContext;
+    }
+
+    @Nonnull
+    @Override
+    public Optional<Tap> extract(@Nonnull final InstanceIdentifier<Tap> currentId,
+                                   @Nonnull final DataObject parentData) {
+        return Optional.fromNullable(((VppInterfaceAugmentation) parentData).getTap());
+    }
+
+    @Override
+    public void writeCurrentAttributes(@Nonnull final InstanceIdentifier<Tap> id, @Nonnull final Tap dataAfter,
+                                       @Nonnull final Context writeContext)
+        throws WriteFailedException.CreateFailedException {
+        try {
+            createTap(id.firstKeyOf(Interface.class).getName(), dataAfter);
+        } catch (VppApiInvocationException e) {
+            LOG.warn("Write of Tap failed", e);
+            throw new WriteFailedException.CreateFailedException(id, dataAfter, e);
+        }
+    }
+
+    @Override
+    public void updateCurrentAttributes(@Nonnull final InstanceIdentifier<Tap> id, @Nonnull final Tap dataBefore,
+                                        @Nonnull final Tap dataAfter, @Nonnull final Context writeContext)
+        throws WriteFailedException.UpdateFailedException {
+        final String ifcName = id.firstKeyOf(Interface.class).getName();
+
+        final int index;
+        try {
+            index = interfaceContext.getIndex(ifcName);
+        } catch (IllegalArgumentException e) {
+            throw new WriteFailedException.UpdateFailedException(id, dataBefore, dataAfter, e);
+        }
+
+        try {
+            modifyTap(ifcName, index, dataAfter);
+        } catch (VppApiInvocationException e) {
+            LOG.warn("Write of Tap failed", e);
+            throw new WriteFailedException.UpdateFailedException(id, dataBefore, dataAfter, e);
+        }
+    }
+
+    @Override
+    public void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<Tap> id, @Nonnull final Tap dataBefore,
+                                        @Nonnull final Context writeContext)
+        throws WriteFailedException.DeleteFailedException {
+        final String ifcName = id.firstKeyOf(Interface.class).getName();
+
+        final int index;
+        try {
+            index = interfaceContext.getIndex(ifcName);
+        } catch (IllegalArgumentException e) {
+            throw new WriteFailedException.DeleteFailedException(id, e);
+        }
+
+        try {
+            deleteTap(ifcName, index, dataBefore);
+        } catch (VppApiInvocationException e) {
+            LOG.warn("Delete of Tap failed", e);
+            throw new WriteFailedException.DeleteFailedException(id, e);
+        }
+    }
+
+    private void createTap(final String swIfName, final Tap tap) throws VppApiInvocationException {
+        LOG.debug("Setting tap interface: {}. Tap: {}", swIfName, tap);
+        final CompletionStage<TapConnectReply> tapConnectFuture =
+            getFutureJVpp().tapConnect(getTapConnectRequest(tap.getTapName(), tap.getMac(), tap.getDeviceInstance()));
+        final TapConnectReply reply =
+            V3poUtils.getReply(tapConnectFuture.toCompletableFuture());
+        if (reply.retval < 0) {
+            LOG.warn("Failed to set tap interface: {}, tap: {}", swIfName, tap);
+            throw new VppApiInvocationException("tap_connect", reply.context, reply.retval);
+        } else {
+            LOG.debug("Tap set successfully for: {}, tap: {}", swIfName, tap);
+            // Add new interface to our interface context
+            interfaceContext.addName(reply.swIfIndex, swIfName);
+        }
+    }
+
+    private void modifyTap(final String swIfName, final int index, final Tap tap) throws VppApiInvocationException {
+        LOG.debug("Modifying tap interface: {}. Tap: {}", swIfName, tap);
+        final CompletionStage<TapModifyReply> vxlanAddDelTunnelReplyCompletionStage =
+            getFutureJVpp().tapModify(getTapModifyRequest(tap.getTapName(), index, tap.getMac(), tap.getDeviceInstance()));
+        final TapModifyReply reply =
+            V3poUtils.getReply(vxlanAddDelTunnelReplyCompletionStage.toCompletableFuture());
+        if (reply.retval < 0) {
+            LOG.warn("Failed to modify tap interface: {}, tap: {}", swIfName, tap);
+            throw new VppApiInvocationException("tap_modify", reply.context, reply.retval);
+        } else {
+            LOG.debug("Tap modified successfully for: {}, tap: {}", swIfName, tap);
+        }
+    }
+
+    private void deleteTap(final String swIfName, final int index, final Tap dataBefore)
+        throws VppApiInvocationException {
+        LOG.debug("Deleting tap interface: {}. Tap: {}", swIfName, dataBefore);
+        final CompletionStage<TapDeleteReply> vxlanAddDelTunnelReplyCompletionStage =
+            getFutureJVpp().tapDelete(getTapDeleteRequest(index));
+        final TapDeleteReply reply =
+            V3poUtils.getReply(vxlanAddDelTunnelReplyCompletionStage.toCompletableFuture());
+        if (reply.retval < 0) {
+            LOG.warn("Failed to delete tap interface: {}, tap: {}", swIfName, dataBefore);
+            throw new VppApiInvocationException("tap_modify", reply.context, reply.retval);
+        } else {
+            LOG.debug("Tap deleted successfully for: {}, tap: {}", swIfName, dataBefore);
+            // Remove deleted interface from interface context
+            interfaceContext.removeName(swIfName);
+        }
+    }
+
+    private TapConnect getTapConnectRequest(final String tapName, final PhysAddress mac, final Long deviceInstance) {
+        final TapConnect tapConnect = new TapConnect();
+        tapConnect.tapName = tapName.getBytes();
+
+        if(mac == null) {
+            tapConnect.useRandomMac = 1;
+            tapConnect.macAddress = new byte[6];
+        } else {
+            tapConnect.useRandomMac = 0;
+            tapConnect.macAddress = V3poUtils.parseMac(mac.getValue());
+        }
+
+        if(deviceInstance == null) {
+            tapConnect.renumber = 0;
+        } else {
+            tapConnect.renumber = 1;
+            tapConnect.customDevInstance = Math.toIntExact(deviceInstance);
+        }
+
+        return tapConnect;
+    }
+
+    private TapModify getTapModifyRequest(final String tapName, final int swIndex, final PhysAddress mac, final Long deviceInstance) {
+        final TapModify tapConnect = new TapModify();
+        tapConnect.tapName = tapName.getBytes();
+        tapConnect.swIfIndex = swIndex;
+
+        if(mac == null) {
+            tapConnect.useRandomMac = 1;
+            tapConnect.macAddress = new byte[6];
+        } else {
+            tapConnect.useRandomMac = 0;
+            tapConnect.macAddress = V3poUtils.parseMac(mac.getValue());
+        }
+
+        if(deviceInstance == null) {
+            tapConnect.renumber = 0;
+        } else {
+            tapConnect.renumber = 1;
+            tapConnect.customDevInstance = Math.toIntExact(deviceInstance);
+        }
+
+        return tapConnect;
+    }
+
+    private TapDelete getTapDeleteRequest(final int swIndex) {
+        final TapDelete tapConnect = new TapDelete();
+        tapConnect.swIfIndex = swIndex;
+        return tapConnect;
+    }
+}
 
 package io.fd.honeycomb.v3po.translate.v3po.interfacesstate;
 
+import static io.fd.honeycomb.v3po.translate.v3po.interfacesstate.InterfaceCustomizer.getCachedInterfaceDump;
+
 import io.fd.honeycomb.v3po.translate.Context;
 import io.fd.honeycomb.v3po.translate.read.ReadFailedException;
 import io.fd.honeycomb.v3po.translate.spi.read.ChildReaderCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.util.FutureJVppCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
 import javax.annotation.Nonnull;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.InterfaceBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.InterfaceKey;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceStateAugmentation;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceStateAugmentationBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces.state._interface.Ethernet;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces.state._interface.EthernetBuilder;
@@ -37,54 +38,50 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 
-public class VppInterfaceStateCustomizer extends FutureJVppCustomizer
-        implements ChildReaderCustomizer<VppInterfaceStateAugmentation, VppInterfaceStateAugmentationBuilder> {
+public class EthernetCustomizer extends FutureJVppCustomizer
+        implements ChildReaderCustomizer<Ethernet, EthernetBuilder> {
 
-    private static final Logger LOG = LoggerFactory.getLogger(VppInterfaceStateCustomizer.class);
+    private static final Logger LOG = LoggerFactory.getLogger(EthernetCustomizer.class);
+    private NamingContext interfaceContext;
 
-    public VppInterfaceStateCustomizer(@Nonnull final FutureJVpp jvpp) {
+    public EthernetCustomizer(@Nonnull final FutureJVpp jvpp,
+                              final NamingContext interfaceContext) {
         super(jvpp);
+        this.interfaceContext = interfaceContext;
     }
 
     @Override
     public void merge(@Nonnull Builder<? extends DataObject> parentBuilder,
-                      @Nonnull VppInterfaceStateAugmentation readValue) {
-        ((InterfaceBuilder) parentBuilder).addAugmentation(VppInterfaceStateAugmentation.class, readValue);
+                      @Nonnull Ethernet readValue) {
+        ((VppInterfaceStateAugmentationBuilder) parentBuilder).setEthernet(readValue);
     }
 
     @Nonnull
     @Override
-    public VppInterfaceStateAugmentationBuilder getBuilder(
-            @Nonnull InstanceIdentifier<VppInterfaceStateAugmentation> id) {
-        return new VppInterfaceStateAugmentationBuilder();
+    public EthernetBuilder getBuilder(
+            @Nonnull InstanceIdentifier<Ethernet> id) {
+        return new EthernetBuilder();
     }
 
     @Override
-    public void readCurrentAttributes(@Nonnull final InstanceIdentifier<VppInterfaceStateAugmentation> id,
-                                      @Nonnull final VppInterfaceStateAugmentationBuilder builder,
+    public void readCurrentAttributes(@Nonnull final InstanceIdentifier<Ethernet> id,
+                                      @Nonnull final EthernetBuilder builder,
                                       @Nonnull final Context ctx) throws ReadFailedException {
 
         final InterfaceKey key = id.firstKeyOf(Interface.class);
-        final SwInterfaceDetails iface;
-        try {
-            iface = InterfaceUtils.getVppInterfaceDetails(getFutureJVpp(), key);
-        } catch (Exception e) {
-            throw new ReadFailedException(id, e);
-        }
+        final SwInterfaceDetails iface = InterfaceUtils.getVppInterfaceDetails(getFutureJVpp(), key,
+                interfaceContext.getIndex(key.getName()), getCachedInterfaceDump(ctx));
 
-        final EthernetBuilder ethernet = new EthernetBuilder();
-        ethernet.setMtu((int) iface.linkMtu);
+        builder.setMtu((int) iface.linkMtu);
         switch (iface.linkDuplex) {
             case 1:
-                ethernet.setDuplex(Ethernet.Duplex.Half);
+                builder.setDuplex(Ethernet.Duplex.Half);
                 break;
             case 2:
-                ethernet.setDuplex(Ethernet.Duplex.Full);
+                builder.setDuplex(Ethernet.Duplex.Full);
                 break;
             default:
                 break;
         }
-
-        builder.setEthernet(ethernet.build());
     }
 }
index f7d473f..82e1146 100644 (file)
@@ -24,10 +24,10 @@ import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
 import io.fd.honeycomb.v3po.translate.v3po.utils.V3poUtils;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev140508.EthernetCsmacd;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.InterfacesStateBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface.AdminStatus;
@@ -44,11 +44,15 @@ import org.openvpp.jvpp.future.FutureJVpp;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-
+/**
+ * Customizer for reading ietf-interfaces:interfaces-state/interface
+ */
 public class InterfaceCustomizer extends FutureJVppCustomizer
         implements ListReaderCustomizer<Interface, InterfaceKey, InterfaceBuilder> {
 
     private static final Logger LOG = LoggerFactory.getLogger(InterfaceCustomizer.class);
+    public static final String DUMPED_IFCS_CONTEXT_KEY = InterfaceCustomizer.class.getName() + "dumpedInterfacesDuringGetAllIds";
+
     private final NamingContext interfaceContext;
 
     public InterfaceCustomizer(@Nonnull final FutureJVpp jvpp, final NamingContext interfaceContext) {
@@ -56,47 +60,53 @@ public class InterfaceCustomizer extends FutureJVppCustomizer
         this.interfaceContext = interfaceContext;
     }
 
+    @Nonnull
     @Override
-    public InterfaceBuilder getBuilder(InstanceIdentifier<Interface> id) {
+    public InterfaceBuilder getBuilder(@Nonnull InstanceIdentifier<Interface> id) {
         return new InterfaceBuilder();
     }
 
     @Override
-    public void readCurrentAttributes(InstanceIdentifier<Interface> id, InterfaceBuilder builder, Context ctx)
-            throws ReadFailedException {
+    public void readCurrentAttributes(@Nonnull InstanceIdentifier<Interface> id, @Nonnull InterfaceBuilder builder,
+                                      @Nonnull Context ctx) throws ReadFailedException {
+        LOG.debug("Reading attributes for interface: {}", id);
         final InterfaceKey key = id.firstKeyOf(id.getTargetType());
 
-        final SwInterfaceDetails iface;
-        try {
-            iface = InterfaceUtils.getVppInterfaceDetails(getFutureJVpp(), key);
-        } catch (Exception e) {
-            throw new ReadFailedException(id, e);
-        }
+        final Map<Integer, SwInterfaceDetails> cachedDump = getCachedInterfaceDump(ctx);
 
+        // Pass cached details from getAllIds to getDetails to avoid additional dumps
+        final SwInterfaceDetails iface = InterfaceUtils.getVppInterfaceDetails(getFutureJVpp(), key,
+            interfaceContext.getIndex(key.getName()), cachedDump);
+        LOG.debug("Interface details for interface: {}, details: {}", key.getName(), iface);
 
         builder.setName(key.getName());
-        // FIXME: report interface type based on name
-        //Tunnel.class l2vlan(802.1q) bridge (transparent bridge?)
-        builder.setType(EthernetCsmacd.class);
+        builder.setType(InterfaceUtils.getInterfaceType(new String(iface.interfaceName).intern()));
         builder.setIfIndex(InterfaceUtils.vppIfIndexToYang(iface.swIfIndex));
-        builder.setAdminStatus(iface.adminUpDown == 1
-                ? AdminStatus.Up
-                : AdminStatus.Down);
-        builder.setOperStatus(1 == iface.linkUpDown
-                ? OperStatus.Up
-                : OperStatus.Down);
+        builder.setAdminStatus(1 == iface.adminUpDown ? AdminStatus.Up : AdminStatus.Down);
+        builder.setOperStatus(1 == iface.linkUpDown ? OperStatus.Up : OperStatus.Down);
         if (0 != iface.linkSpeed) {
             builder.setSpeed(InterfaceUtils.vppInterfaceSpeedToYang(iface.linkSpeed));
         }
         if (iface.l2AddressLength == 6) {
             builder.setPhysAddress(new PhysAddress(InterfaceUtils.vppPhysAddrToYang(iface.l2Address)));
         }
+        LOG.trace("Base attributes read for interface: {} as: {}", key.getName(), builder);
+    }
+
+    @Nonnull
+    @SuppressWarnings("unchecked")
+    public static Map<Integer, SwInterfaceDetails> getCachedInterfaceDump(final @Nonnull Context ctx) {
+        return ctx.get(DUMPED_IFCS_CONTEXT_KEY) == null
+            ? Collections.emptyMap()
+            : (Map<Integer, SwInterfaceDetails>) ctx.get(DUMPED_IFCS_CONTEXT_KEY);
     }
 
     @Nonnull
     @Override
     public List<InterfaceKey> getAllIds(@Nonnull final InstanceIdentifier<Interface> id,
                                         @Nonnull final Context context) throws ReadFailedException {
+        LOG.trace("Dumping all interfaces to get all IDs");
+
         final SwInterfaceDump request = new SwInterfaceDump();
         request.nameFilter = "".getBytes();
         request.nameFilterValid = 0;
@@ -105,21 +115,31 @@ public class InterfaceCustomizer extends FutureJVppCustomizer
                 getFutureJVpp().swInterfaceDump(request).toCompletableFuture();
         final SwInterfaceDetailsReplyDump ifaces = V3poUtils.getReply(swInterfaceDetailsReplyDumpCompletableFuture);
 
-        // TODO can we get null here?
         if (null == ifaces || null == ifaces.swInterfaceDetails) {
+            LOG.debug("No interfaces found in VPP");
             return Collections.emptyList();
         }
 
-        return ifaces.swInterfaceDetails.stream()
-                .filter(elt -> elt != null)
-                .map((elt) -> {
-                    // Store interface name from VPP in context if not yet present
-                    if(!interfaceContext.containsName(elt.swIfIndex)){
-                        interfaceContext.addName(elt.swIfIndex, V3poUtils.toString(elt.interfaceName));
-                    }
-                    return new InterfaceKey(interfaceContext.getName(elt.swIfIndex));
-                })
-                .collect(Collectors.toList());
+        // Cache interfaces dump in per-tx context to later be used in readCurrentAttributes
+        context.put(DUMPED_IFCS_CONTEXT_KEY, ifaces.swInterfaceDetails.stream()
+            .collect(Collectors.toMap(t -> t.swIfIndex, swInterfaceDetails -> swInterfaceDetails)));
+
+        final List<InterfaceKey> interfacesKeys = ifaces.swInterfaceDetails.stream()
+            .filter(elt -> elt != null)
+            .map((elt) -> {
+                // Store interface name from VPP in context if not yet present
+                if (!interfaceContext.containsName(elt.swIfIndex)) {
+                    interfaceContext.addName(elt.swIfIndex, V3poUtils.toString(elt.interfaceName));
+                }
+                LOG.trace("Interface with name: {}, VPP name: {} and index: {} found in VPP",
+                    interfaceContext.getName(elt.swIfIndex), elt.interfaceName, elt.swIfIndex);
+
+                return new InterfaceKey(interfaceContext.getName(elt.swIfIndex));
+            })
+            .collect(Collectors.toList());
+
+        LOG.debug("Interfaces found in VPP: {}", interfacesKeys);
+        return interfacesKeys;
     }
 
     @Override
index 691b95f..62cac35 100644 (file)
@@ -18,13 +18,18 @@ package io.fd.honeycomb.v3po.translate.v3po.interfacesstate;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Iterables;
+import io.fd.honeycomb.v3po.translate.v3po.utils.V3poUtils;
 import java.math.BigInteger;
+import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.CompletionStage;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev140508.EthernetCsmacd;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.InterfaceType;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.InterfaceKey;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Gauge64;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Tap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VxlanTunnel;
 import org.openvpp.jvpp.dto.SwInterfaceDetails;
 import org.openvpp.jvpp.dto.SwInterfaceDetailsReplyDump;
 import org.openvpp.jvpp.dto.SwInterfaceDump;
@@ -82,6 +87,7 @@ public final class InterfaceUtils {
         sb.append(HEX_CHARS[v & 15]);
     }
 
+    // TODO rename and move to V3poUtils
     /**
      * Reads first 6 bytes of supplied byte array and converts to string as Yang dictates
      * <p> Replace later with
@@ -125,7 +131,7 @@ public final class InterfaceUtils {
      * @param yangIfIndex if-index from ietf-interfaces.
      * @return VPP's representation of the if-index
      */
-    public static int YangIfIndexToVpp(int yangIfIndex) {
+    public static int yangIfIndexToVpp(int yangIfIndex) {
         Preconditions.checkArgument(yangIfIndex >= 1, "YANG if-index has invalid value %s", yangIfIndex);
         return yangIfIndex - 1;
     }
@@ -136,27 +142,60 @@ public final class InterfaceUtils {
      *
      * @param futureJvpp VPP Java Future API
      * @param key        interface key
+     * @param index      VPP index of the interface
+     * @param allInterfaces cached interfaces dump with all the interfaces. If interface not present, another dump all
+     *                      will be performed
+     *
      * @return SwInterfaceDetails DTO or null if interface was not found
-     * @throws ExecutionException   if exception has been thrown while executing VPP query
-     * @throws InterruptedException if the current thread was interrupted
+     *
+     * @throws IllegalArgumentException If interface cannot be found
      */
-    @Nullable
+    @Nonnull
     public static SwInterfaceDetails getVppInterfaceDetails(@Nonnull final FutureJVpp futureJvpp,
-                                                            @Nonnull InterfaceKey key)
-            throws ExecutionException, InterruptedException {
+                                                            @Nonnull InterfaceKey key, final int index,
+                                                            @Nonnull final Map<Integer, SwInterfaceDetails> allInterfaces) {
         final SwInterfaceDump request = new SwInterfaceDump();
         request.nameFilter = key.getName().getBytes();
         request.nameFilterValid = 1;
 
-        // TODO should we use timeout?
-        SwInterfaceDetailsReplyDump ifaces = futureJvpp.swInterfaceDump(request).toCompletableFuture().get();
-        if (null == ifaces) { // TODO can we get null here?
-            LOG.warn("VPP returned null instead of interface by key {}", key.getName().getBytes());
-            return null;
-        }
+        CompletionStage<SwInterfaceDetailsReplyDump> requestFuture = futureJvpp.swInterfaceDump(request);
+        SwInterfaceDetailsReplyDump ifaces = V3poUtils.getReply(requestFuture.toCompletableFuture());
+        if (null == ifaces || null == ifaces.swInterfaceDetails || ifaces.swInterfaceDetails.isEmpty()) {
+            LOG.warn("VPP returned null instead of interface by key {}", key.getName());
+            LOG.warn("Iterating through all the interfaces to find interface: {}", key.getName());
+            request.nameFilterValid = 0;
+
+            // Returned cached if available
+            if(allInterfaces.containsKey(index)) {
+                return allInterfaces.get(index);
+            }
 
+            // Or else just perform full dump and do inefficient filtering
+            requestFuture = futureJvpp.swInterfaceDump(request);
+            ifaces = V3poUtils.getReply(requestFuture.toCompletableFuture());
+
+            return ifaces.swInterfaceDetails.stream().filter((swIfc) -> swIfc.swIfIndex == index)
+                .findFirst().orElseThrow(() -> new IllegalArgumentException("Unable to find interface " + key.getName()));
+        }
         return Iterables.getOnlyElement(ifaces.swInterfaceDetails);
     }
 
+    /**
+     * Determine interface type based on its VPP name (relying on VPP's interface naming conventions)
+     *
+     * @param interfaceName VPP generated interface name
+     * @return Interface type
+     */
+    @Nonnull
+    public static Class<? extends InterfaceType> getInterfaceType(@Nonnull final String interfaceName) {
+        if(interfaceName.startsWith("tap")) {
+            return Tap.class;
+        }
 
+        if(interfaceName.startsWith("vxlan")) {
+            return VxlanTunnel.class;
+        }
+
+        return EthernetCsmacd.class;
+    }
 }
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfacesstate/TapCustomizer.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/v3po/translate/v3po/interfacesstate/TapCustomizer.java
new file mode 100644 (file)
index 0000000..a505436
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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.v3po.translate.v3po.interfacesstate;
+
+import io.fd.honeycomb.v3po.translate.Context;
+import io.fd.honeycomb.v3po.translate.read.ReadFailedException;
+import io.fd.honeycomb.v3po.translate.spi.read.ChildReaderCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.util.FutureJVppCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
+import io.fd.honeycomb.v3po.translate.v3po.utils.V3poUtils;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletionStage;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.InterfaceKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceStateAugmentationBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces.state._interface.Tap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces.state._interface.TapBuilder;
+import org.opendaylight.yangtools.concepts.Builder;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.openvpp.jvpp.dto.SwInterfaceTapDetails;
+import org.openvpp.jvpp.dto.SwInterfaceTapDetailsReplyDump;
+import org.openvpp.jvpp.dto.SwInterfaceTapDump;
+import org.openvpp.jvpp.future.FutureJVpp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class TapCustomizer extends FutureJVppCustomizer
+        implements ChildReaderCustomizer<Tap, TapBuilder> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TapCustomizer.class);
+    public static final String DUMPED_TAPS_CONTEXT_KEY = TapCustomizer.class.getName() + "dumpedTapsDuringGetAllIds";
+    private NamingContext interfaceContext;
+
+    public TapCustomizer(@Nonnull final FutureJVpp jvpp,
+                         final NamingContext interfaceContext) {
+        super(jvpp);
+        this.interfaceContext = interfaceContext;
+    }
+
+    @Override
+    public void merge(@Nonnull Builder<? extends DataObject> parentBuilder,
+                      @Nonnull Tap readValue) {
+        ((VppInterfaceStateAugmentationBuilder) parentBuilder).setTap(readValue);
+    }
+
+    @Nonnull
+    @Override
+    public TapBuilder getBuilder(
+            @Nonnull InstanceIdentifier<Tap> id) {
+        return new TapBuilder();
+    }
+
+    @Override
+    public void readCurrentAttributes(@Nonnull final InstanceIdentifier<Tap> id,
+                                      @Nonnull final TapBuilder builder,
+                                      @Nonnull final Context ctx) throws ReadFailedException {
+        final InterfaceKey key = id.firstKeyOf(Interface.class);
+
+        @SuppressWarnings("unchecked")
+        Map<Integer, SwInterfaceTapDetails> mappedTaps =
+            (Map<Integer, SwInterfaceTapDetails>) ctx.get(DUMPED_TAPS_CONTEXT_KEY);
+
+        if(mappedTaps == null) {
+            // Full Tap dump has to be performed here, no filter or anything is here to help so at least we cache it
+            final SwInterfaceTapDump request = new SwInterfaceTapDump();
+            final CompletionStage<SwInterfaceTapDetailsReplyDump> swInterfaceTapDetailsReplyDumpCompletionStage =
+                getFutureJVpp().swInterfaceTapDump(request);
+            final SwInterfaceTapDetailsReplyDump reply =
+                V3poUtils.getReply(swInterfaceTapDetailsReplyDumpCompletionStage.toCompletableFuture());
+
+            if(null == reply || null == reply.swInterfaceTapDetails) {
+                mappedTaps = Collections.emptyMap();
+            } else {
+                final List<SwInterfaceTapDetails> swInterfaceTapDetails = reply.swInterfaceTapDetails;
+                // Cache interfaces dump in per-tx context to later be used in readCurrentAttributes
+                mappedTaps = swInterfaceTapDetails.stream()
+                    .collect(Collectors.toMap(t -> t.swIfIndex, swInterfaceDetails -> swInterfaceDetails));
+            }
+
+            ctx.put(DUMPED_TAPS_CONTEXT_KEY, mappedTaps);
+        }
+
+        // Relying here that parent InterfaceCustomizer was invoked first to fill in the context with initial ifc mapping
+        final int index = interfaceContext.getIndex(key.getName());
+        final SwInterfaceTapDetails swInterfaceTapDetails = mappedTaps.get(index);
+        if(swInterfaceTapDetails == null) {
+            // Not a Tap interface type
+            return;
+        }
+
+        builder.setTapName(V3poUtils.toString(swInterfaceTapDetails.devName));
+    }
+}
index b4217df..392dc46 100644 (file)
 
 package io.fd.honeycomb.v3po.translate.v3po.utils;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import com.google.common.base.Splitter;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev140508.EthernetCsmacd;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev140508.SoftwareLoopback;
+import java.util.function.BiConsumer;
+import javax.annotation.Nonnull;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4AddressNoZone;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.InterfaceType;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VxlanTunnel;
 import org.openvpp.jvpp.dto.JVppReply;
 
 public final class V3poUtils {
 
     // TODO move to vpp-translate-utils
 
-    public static final int RESPONSE_NOT_READY = -77;
-    public static final int RELEASE = 1;
-    public static final Splitter DOT_SPLITTER = Splitter.on('.');
-    public static final BiMap<String, Class<? extends InterfaceType>> IFC_TYPES = HashBiMap.create();
-    static {
-        V3poUtils.IFC_TYPES.put("vxlan", VxlanTunnel.class);
-        V3poUtils.IFC_TYPES.put("lo", SoftwareLoopback.class);
-        V3poUtils.IFC_TYPES.put("Ether", EthernetCsmacd.class);
-        // TODO missing types below
-//        V3poUtils.IFC_TYPES.put("l2tpv3_tunnel", EthernetCsmacd.class);
-//        V3poUtils.IFC_TYPES.put("tap", EthernetCsmacd.class);
-    }
+    public static final Splitter COLON_SPLITTER = Splitter.on(':');
 
     private V3poUtils() {}
 
@@ -77,4 +65,35 @@ public final class V3poUtils {
     public static String toString(final byte[] cString) {
         return new String(cString).replaceAll("\\u0000", "").intern();
     }
+
+    /**
+     * Parse string represented mac address (using ":" as separator) into a byte array
+     */
+    @Nonnull
+    public static byte[] parseMac(@Nonnull final String macAddress) {
+        final List<String> parts = COLON_SPLITTER.splitToList(macAddress);
+        checkArgument(parts.size() == 6, "Mac address is expected to have 6 parts but was: %s", macAddress);
+        return parseMacLikeString(parts);
+    }
+
+    private static byte[] parseMacLikeString(final List<String> strings) {
+        return strings.stream().limit(6).map(V3poUtils::parseHexByte).collect(
+            () -> new byte[strings.size()],
+            new BiConsumer<byte[], Byte>() {
+
+                private int i = -1;
+
+                @Override
+                public void accept(final byte[] bytes, final Byte aByte) {
+                    bytes[++i] = aByte;
+                }
+            },
+            (bytes, bytes2) -> {
+                throw new UnsupportedOperationException("Parallel collect not supported");
+            });
+    }
+
+    private static byte parseHexByte(final String aByte) {
+        return (byte)Integer.parseInt(aByte, 16);
+    }
 }
index addb425..1a3855c 100644 (file)
@@ -109,21 +109,27 @@ public final class BridgeDomainCustomizer extends FutureJVppCustomizer
         try {
             final L2FibTableEntryReplyDump dump =
                     getFutureJVpp().l2FibTableDump(l2FibRequest).toCompletableFuture().get();
-            final List<L2Fib> l2Fibs = Lists.newArrayListWithCapacity(dump.l2FibTableEntry.size());
-            for (L2FibTableEntry entry : dump.l2FibTableEntry) {
-                // entry.mac is a long value in the format 66:55:44:33:22:11:XX:XX
-                // where mac address is 11:22:33:44:55:66
-                final PhysAddress address = new PhysAddress(getMacAddress(Longs.toByteArray(entry.mac)));
-                l2Fibs.add(new L2FibBuilder()
+            final List<L2Fib> l2Fibs;
+
+            if(null == dump || null == dump.l2FibTableEntry) {
+                l2Fibs = Collections.emptyList();
+            } else {
+                l2Fibs = Lists.newArrayListWithCapacity(dump.l2FibTableEntry.size());
+                for (L2FibTableEntry entry : dump.l2FibTableEntry) {
+                    // entry.mac is a long value in the format 66:55:44:33:22:11:XX:XX
+                    // where mac address is 11:22:33:44:55:66
+                    final PhysAddress address = new PhysAddress(getMacAddress(Longs.toByteArray(entry.mac)));
+                    l2Fibs.add(new L2FibBuilder()
                         .setAction((byteToBoolean(entry.filterMac)
-                                ? L2Fib.Action.Filter
-                                : L2Fib.Action.Forward))
+                            ? L2Fib.Action.Filter
+                            : L2Fib.Action.Forward))
                         .setBridgedVirtualInterface(byteToBoolean(entry.bviMac))
                         .setOutgoingInterface(interfaceContext.getName(entry.swIfIndex))
                         .setStaticConfig(byteToBoolean(entry.staticMac))
                         .setPhysAddress(address)
                         .setKey(new L2FibKey(address))
                         .build());
+                }
             }
             builder.setL2Fib(l2Fibs);
 
index 769589b..2095f30 100644 (file)
@@ -13,6 +13,7 @@ import io.fd.honeycomb.v3po.translate.v3po.interfaces.EthernetCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.InterfaceCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.L2Customizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.RoutingCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.TapCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.VxlanCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv4Customizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv6Customizer;
@@ -28,6 +29,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Ethernet;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.L2;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Routing;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Tap;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Vxlan;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
 import org.opendaylight.yangtools.yang.binding.ChildOf;
@@ -96,12 +98,15 @@ public class InterfacesHoneycombWriterModule extends org.opendaylight.yang.gen.v
         final ChildWriter<Vxlan> vxlanWriter = new CompositeChildWriter<>(Vxlan.class,
             new VxlanCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()));
 
+        final ChildWriter<Tap> tapWriter = new CompositeChildWriter<>(Tap.class,
+            new TapCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()));
+
         final ChildWriter<L2> l2Writer = new CompositeChildWriter<>(L2.class,
             new L2Customizer(getVppJvppIfcDependency(), getInterfaceContextDependency(), getBridgeDomainContextDependency()));
 
         final List<ChildWriter<? extends ChildOf<VppInterfaceAugmentation>>> vppIfcChildWriters = Lists.newArrayList();
-        // TODO what's the order here ?
         vppIfcChildWriters.add(vxlanWriter);
+        vppIfcChildWriters.add(tapWriter);
         vppIfcChildWriters.add(ethernetWriter);
         vppIfcChildWriters.add(l2Writer);
         vppIfcChildWriters.add(routingWriter);
index e753cf5..13fcf00 100644 (file)
@@ -6,20 +6,28 @@ import static io.fd.honeycomb.v3po.translate.util.RWUtils.emptyChildReaderList;
 import static io.fd.honeycomb.v3po.translate.util.RWUtils.singletonAugReaderList;
 import static io.fd.honeycomb.v3po.translate.util.RWUtils.singletonChildReaderList;
 
+import com.google.common.collect.Lists;
 import io.fd.honeycomb.v3po.translate.impl.read.CompositeChildReader;
 import io.fd.honeycomb.v3po.translate.impl.read.CompositeListReader;
 import io.fd.honeycomb.v3po.translate.impl.read.CompositeRootReader;
 import io.fd.honeycomb.v3po.translate.read.ChildReader;
 import io.fd.honeycomb.v3po.translate.util.read.CloseableReader;
+import io.fd.honeycomb.v3po.translate.util.read.ReflexiveAugmentReaderCustomizer;
 import io.fd.honeycomb.v3po.translate.util.read.ReflexiveRootReaderCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfacesstate.EthernetCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfacesstate.InterfaceCustomizer;
-import io.fd.honeycomb.v3po.translate.v3po.interfacesstate.VppInterfaceStateCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfacesstate.TapCustomizer;
+import java.util.List;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.InterfacesState;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.InterfacesStateBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.InterfaceBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.InterfaceKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceStateAugmentation;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceStateAugmentationBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces.state._interface.Ethernet;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces.state._interface.Tap;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
 
 public class InterfacesStateHoneycombReaderModule extends
     org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406.AbstractInterfacesStateHoneycombReaderModule {
@@ -43,10 +51,23 @@ public class InterfacesStateHoneycombReaderModule extends
     @Override
     public java.lang.AutoCloseable createInstance() {
 
+        final ChildReader<? extends ChildOf<VppInterfaceStateAugmentation>> ethernetReader =
+            new CompositeChildReader<>(Ethernet.class,
+            new EthernetCustomizer(getVppJvppDependency(), getInterfaceContextIfcStateDependency()));
+
+        final ChildReader<? extends ChildOf<VppInterfaceStateAugmentation>> tapReader =
+            new CompositeChildReader<>(Tap.class,
+            new TapCustomizer(getVppJvppDependency(), getInterfaceContextIfcStateDependency()));
+
+        final List<ChildReader<? extends ChildOf<VppInterfaceStateAugmentation>>> childReaders = Lists.newArrayList();
+        childReaders.add(ethernetReader);
+        childReaders.add(tapReader);
+
         final ChildReader<VppInterfaceStateAugmentation> vppInterfaceStateAugmentationChildReader =
             new CompositeChildReader<>(VppInterfaceStateAugmentation.class,
-                new VppInterfaceStateCustomizer(getVppJvppDependency()));
-
+                childReaders,
+                new ReflexiveAugmentReaderCustomizer<>(VppInterfaceStateAugmentationBuilder.class,
+                    VppInterfaceStateAugmentation.class));
 
         final CompositeListReader<Interface, InterfaceKey, InterfaceBuilder> interfaceReader =
             new CompositeListReader<>(Interface.class,
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/interfaces/TapCustomizerTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/interfaces/TapCustomizerTest.java
new file mode 100644 (file)
index 0000000..668eed4
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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.v3po.translate.v3po.interfaces;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import io.fd.honeycomb.v3po.translate.Context;
+import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
+import java.util.concurrent.CompletableFuture;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.InterfaceKey;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.PhysAddress;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceAugmentation;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Tap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.TapBuilder;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.openvpp.jvpp.dto.TapConnect;
+import org.openvpp.jvpp.dto.TapConnectReply;
+import org.openvpp.jvpp.dto.TapDelete;
+import org.openvpp.jvpp.dto.TapDeleteReply;
+import org.openvpp.jvpp.dto.TapModify;
+import org.openvpp.jvpp.dto.TapModifyReply;
+import org.openvpp.jvpp.future.FutureJVpp;
+
+public class TapCustomizerTest {
+
+    @Mock
+    private FutureJVpp vppApi;
+    private NamingContext ctx;
+    private TapCustomizer tapCustomizer;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        ctx = new NamingContext("ifcintest");
+        tapCustomizer = new TapCustomizer(vppApi, ctx);
+    }
+
+    @Test
+    public void testCreate() throws Exception {
+        doAnswer(new Answer() {
+
+            int idx = 0;
+
+            @Override
+            public Object answer(final InvocationOnMock invocation) throws Throwable {
+                final CompletableFuture<Object> reply = new CompletableFuture<>();
+                final TapConnectReply t = new TapConnectReply();
+                t.swIfIndex = idx++;
+                t.retval = 0;
+                reply.complete(t);
+                return reply;
+            }
+        }).when(vppApi).tapConnect(any(TapConnect.class));
+
+        tapCustomizer.writeCurrentAttributes(getTapId("tap"), getTapData("tap", "ff:ff:ff:ff:ff:ff"), new Context());
+        tapCustomizer.writeCurrentAttributes(getTapId("tap2"), getTapData("tap2", "ff:ff:ff:ff:ff:ff"), new Context());
+
+        verify(vppApi, times(2)).tapConnect(any(TapConnect.class));
+        assertTrue(ctx.containsIndex("tap"));
+        assertTrue(ctx.containsIndex("tap2"));
+    }
+
+    @Test
+    public void testModify() throws Exception {
+        final CompletableFuture<TapConnectReply> reply = new CompletableFuture<>();
+        final TapConnectReply t = new TapConnectReply();
+        t.swIfIndex = 0;
+        reply.complete(t);
+        doReturn(reply).when(vppApi).tapConnect(any(TapConnect.class));
+
+        final CompletableFuture<TapModifyReply> replyModif = new CompletableFuture<>();
+        final TapModifyReply tmodif = new TapModifyReply();
+        tmodif.swIfIndex = 0;
+        tmodif.retval = 0;
+        replyModif.complete(tmodif);
+        doReturn(replyModif).when(vppApi).tapModify(any(TapModify.class));
+
+        tapCustomizer.writeCurrentAttributes(getTapId("tap"), getTapData("tap", "ff:ff:ff:ff:ff:ff"), new Context());
+        tapCustomizer.updateCurrentAttributes(getTapId("tap"), getTapData("tap", "ff:ff:ff:ff:ff:ff"), getTapData("tap", "ff:ff:ff:ff:ff:f1"), new Context());
+
+        verify(vppApi).tapConnect(any(TapConnect.class));
+        verify(vppApi).tapModify(any(TapModify.class));
+        assertTrue(ctx.containsIndex("tap"));
+        assertFalse(ctx.containsIndex("tap2"));
+    }
+
+    @Test
+    public void testDelete() throws Exception {
+        final CompletableFuture<TapConnectReply> reply = new CompletableFuture<>();
+        final TapConnectReply t = new TapConnectReply();
+        t.swIfIndex = 0;
+        reply.complete(t);
+        doReturn(reply).when(vppApi).tapConnect(any(TapConnect.class));
+
+        final CompletableFuture<TapDeleteReply> replyDelete = new CompletableFuture<>();
+        final TapDeleteReply tmodif = new TapDeleteReply();
+        tmodif.retval = 0;
+        replyDelete.complete(tmodif);
+        doReturn(replyDelete).when(vppApi).tapDelete(any(TapDelete.class));
+
+        tapCustomizer.writeCurrentAttributes(getTapId("tap"), getTapData("tap", "ff:ff:ff:ff:ff:ff"), new Context());
+        tapCustomizer.deleteCurrentAttributes(getTapId("tap"), getTapData("tap", "ff:ff:ff:ff:ff:ff"), new Context());
+
+        verify(vppApi).tapConnect(any(TapConnect.class));
+        verify(vppApi).tapDelete(any(TapDelete.class));
+        assertFalse(ctx.containsIndex("tap"));
+    }
+
+    private InstanceIdentifier<Tap> getTapId(final String tap) {
+        return InstanceIdentifier.create(Interfaces.class).child(Interface.class, new InterfaceKey(tap)).augmentation(
+            VppInterfaceAugmentation.class).child(Tap.class);
+    }
+
+    private Tap getTapData(final String tap, final String mac) {
+        return new TapBuilder().setTapName(tap).setMac(new PhysAddress(mac)).build();
+    }
+}
\ No newline at end of file
index 7c88c31..3f33d14 100644 (file)
 
 package io.fd.honeycomb.v3po.translate.v3po.interfacesstate;
 
-import static io.fd.honeycomb.v3po.translate.v3po.interfacesstate.InterfaceUtils.YangIfIndexToVpp;
+import static io.fd.honeycomb.v3po.translate.v3po.interfacesstate.InterfaceUtils.yangIfIndexToVpp;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import io.fd.honeycomb.v3po.translate.read.ReadFailedException;
 import io.fd.honeycomb.v3po.translate.spi.read.RootReaderCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.test.ListReaderCustomizerTest;
 import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
@@ -63,6 +63,8 @@ public class InterfaceCustomizerTest extends
 
     @Override
     protected RootReaderCustomizer<Interface, InterfaceBuilder> initCustomizer() {
+        interfacesContext.addName(0, "eth0");
+        interfacesContext.addName(1, "eth1");
         return new InterfaceCustomizer(api, interfacesContext);
     }
 
@@ -75,10 +77,11 @@ public class InterfaceCustomizerTest extends
         verify(builder).setInterface(value);
     }
 
-    private void verifyBridgeDomainDumpUpdateWasInvoked(final int nameFilterValid, final String ifaceName) {
+    private void verifyBridgeDomainDumpUpdateWasInvoked(final int nameFilterValid, final String ifaceName,
+                                                        final int dumpIfcsInvocationCount) {
         // TODO adding equals methods for jvpp DTOs would make ArgumentCaptor usage obsolete
         ArgumentCaptor<SwInterfaceDump> argumentCaptor = ArgumentCaptor.forClass(SwInterfaceDump.class);
-        verify(api).swInterfaceDump(argumentCaptor.capture());
+        verify(api, times(dumpIfcsInvocationCount)).swInterfaceDump(argumentCaptor.capture());
         final SwInterfaceDump actual = argumentCaptor.getValue();
         assertEquals(nameFilterValid, actual.nameFilterValid);
         assertArrayEquals(ifaceName.getBytes(), actual.nameFilter);
@@ -86,7 +89,7 @@ public class InterfaceCustomizerTest extends
 
     private static void assertIfacesAreEqual(final Interface iface, final SwInterfaceDetails details) {
         assertEquals(iface.getName(), new String(details.interfaceName));
-        assertEquals(YangIfIndexToVpp(iface.getIfIndex().intValue()), details.swIfIndex);
+        assertEquals(yangIfIndexToVpp(iface.getIfIndex().intValue()), details.swIfIndex);
         assertEquals(iface.getPhysAddress().getValue(), InterfaceUtils.vppPhysAddrToYang(details.l2Address));
     }
 
@@ -119,7 +122,7 @@ public class InterfaceCustomizerTest extends
 
         getCustomizer().readCurrentAttributes(id, builder, ctx);
 
-        verifyBridgeDomainDumpUpdateWasInvoked(1, ifaceName);
+        verifyBridgeDomainDumpUpdateWasInvoked(1, ifaceName, 1);
         assertIfacesAreEqual(builder.build(), iface);
     }
 
@@ -134,8 +137,8 @@ public class InterfaceCustomizerTest extends
 
         try {
             getCustomizer().readCurrentAttributes(id, builder, ctx);
-        } catch (ReadFailedException e) {
-            verifyBridgeDomainDumpUpdateWasInvoked(1, ifaceName);
+        } catch (IllegalArgumentException e) {
+            verifyBridgeDomainDumpUpdateWasInvoked(0, ifaceName, 2);
             return;
         }
 
@@ -149,16 +152,18 @@ public class InterfaceCustomizerTest extends
 
         final String swIf0Name = "eth0";
         final SwInterfaceDetails swIf0 = new SwInterfaceDetails();
+        swIf0.swIfIndex = 0;
         swIf0.interfaceName = swIf0Name.getBytes();
-        final String swIf1Name = "eth0";
+        final String swIf1Name = "eth1";
         final SwInterfaceDetails swIf1 = new SwInterfaceDetails();
+        swIf1.swIfIndex = 1;
         swIf1.interfaceName = swIf1Name.getBytes();
         whenSwInterfaceDumpThenReturn(Arrays.asList(swIf0, swIf1));
 
         final List<InterfaceKey> expectedIds = Arrays.asList(new InterfaceKey(swIf0Name), new InterfaceKey(swIf1Name));
         final List<InterfaceKey> actualIds = getCustomizer().getAllIds(id, ctx);
 
-        verifyBridgeDomainDumpUpdateWasInvoked(0, "");
+        verifyBridgeDomainDumpUpdateWasInvoked(0, "", 1);
 
         assertEquals(expectedIds, actualIds);
     }
index 115eb2b..b28234d 100644 (file)
@@ -1,6 +1,7 @@
 package io.fd.honeycomb.v3po.translate.v3po.utils;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 
 import org.junit.Test;
 
@@ -14,4 +15,41 @@ public class V3poUtilsTest {
         final String jString = V3poUtils.toString(cString);
         assertArrayEquals(expected, jString.getBytes());
     }
-}
\ No newline at end of file
+
+    @Test
+    public void testParseMac() throws Exception {
+        byte[] bytes = V3poUtils.parseMac("00:fF:7f:15:5e:A9");
+        assertMac(bytes);
+    }
+
+    private void assertMac(final byte[] bytes) {
+        assertEquals(6, bytes.length);
+        assertEquals((byte)0, bytes[0]);
+        assertEquals((byte)255, bytes[1]);
+        assertEquals((byte)127, bytes[2]);
+        assertEquals((byte)21, bytes[3]);
+        assertEquals((byte)94, bytes[4]);
+        assertEquals((byte)169, bytes[5]);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseMacLonger() throws Exception {
+        byte[] bytes = V3poUtils.parseMac("00:fF:7f:15:5e:A9:88:77");
+        assertMac(bytes);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseMacShorter() throws Exception {
+        V3poUtils.parseMac("00:fF:7f");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseRandomString() throws Exception {
+        V3poUtils.parseMac("random{}}@$*&*!");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseMacNumberFormatEx() throws Exception {
+        V3poUtils.parseMac("00:XX:7f:15:5e:77\"");
+    }
+}