HONEYCOMB-142 - Subnet validation
authorJan Srnicek <[email protected]>
Mon, 12 Sep 2016 11:12:14 +0000 (13:12 +0200)
committerMaros Marsalek <[email protected]>
Mon, 12 Sep 2016 15:41:32 +0000 (15:41 +0000)
Added validation of address to not be from same
subnet
Refactored to detect conflicts with mixed types of subnets and report them better

Change-Id: Ib815a79c9f61e88b5097884e346320028bbfe914
Signed-off-by: Jan Srnicek <[email protected]>
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/Ipv4AddressCustomizer.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/Ipv4Customizer.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/Ipv4WriteUtils.java
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidationException.java [new file with mode: 0644]
v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidator.java [new file with mode: 0644]
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/ip/Ipv4AddressCustomizerTest.java
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidatorTest.java [new file with mode: 0644]

index e7b932a..574a42e 100644 (file)
@@ -18,13 +18,18 @@ package io.fd.honeycomb.translate.v3po.interfaces.ip;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.common.base.Optional;
 import io.fd.honeycomb.translate.spi.write.ListWriterCustomizer;
+import io.fd.honeycomb.translate.util.RWUtils;
+import io.fd.honeycomb.translate.v3po.interfaces.ip.subnet.validation.SubnetValidationException;
+import io.fd.honeycomb.translate.v3po.interfaces.ip.subnet.validation.SubnetValidator;
 import io.fd.honeycomb.translate.v3po.util.FutureJVppCustomizer;
 import io.fd.honeycomb.translate.v3po.util.NamingContext;
 import io.fd.honeycomb.translate.write.WriteContext;
 import io.fd.honeycomb.translate.write.WriteFailedException;
 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.ip.rev140616.interfaces._interface.Ipv4;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Address;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.AddressKey;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.address.Subnet;
@@ -44,37 +49,65 @@ public class Ipv4AddressCustomizer extends FutureJVppCustomizer implements ListW
 
     private static final Logger LOG = LoggerFactory.getLogger(Ipv4AddressCustomizer.class);
     private final NamingContext interfaceContext;
+    private final SubnetValidator subnetValidator;
 
-    public Ipv4AddressCustomizer(FutureJVppCore futureJVppCore, NamingContext interfaceContext) {
+    Ipv4AddressCustomizer(@Nonnull final FutureJVppCore futureJVppCore, @Nonnull final NamingContext interfaceContext,
+                          @Nonnull final SubnetValidator subnetValidator) {
         super(futureJVppCore);
-        this.interfaceContext = interfaceContext;
+        this.interfaceContext = checkNotNull(interfaceContext, "Interface context cannot be null");
+        this.subnetValidator = checkNotNull(subnetValidator, "Subnet validator cannot be null");
+    }
+
+    public Ipv4AddressCustomizer(@Nonnull final FutureJVppCore futureJVppCore,
+                                 @Nonnull final NamingContext interfaceContext) {
+        this(futureJVppCore, interfaceContext, new SubnetValidator());
     }
 
     @Override
     public void writeCurrentAttributes(InstanceIdentifier<Address> id, Address dataAfter, WriteContext writeContext)
-        throws WriteFailedException {
-        setAddress(true, id, dataAfter, writeContext);
+            throws WriteFailedException {
+
+        final String interfaceName = id.firstKeyOf(Interface.class).getName();
+        final int interfaceIndex = interfaceContext.getIndex(interfaceName, writeContext.getMappingContext());
+
+        // checks whether address is not from same subnet of some address already defined on this interface
+        try {
+
+            final InstanceIdentifier<Ipv4> parentId = RWUtils.cutId(id, InstanceIdentifier.create(Ipv4.class));
+            final Optional<Ipv4> ipv4Optional = writeContext.readAfter(parentId);
+
+            //no need to check isPresent() - we are inside of address customizer, therefore there must be Address data
+            //that is being processed by infrastructure
+
+            subnetValidator.checkNotAddingToSameSubnet(ipv4Optional.get().getAddress());
+        } catch (SubnetValidationException e) {
+            throw new WriteFailedException(id, e);
+        }
+
+        setAddress(true, id, interfaceName, interfaceIndex, dataAfter, writeContext);
     }
 
     @Override
     public void updateCurrentAttributes(InstanceIdentifier<Address> id, Address dataBefore, Address dataAfter,
                                         WriteContext writeContext) throws WriteFailedException {
         throw new WriteFailedException.UpdateFailedException(id, dataBefore, dataAfter,
-            new UnsupportedOperationException("Operation not supported"));
+                new UnsupportedOperationException("Operation not supported"));
     }
 
     @Override
     public void deleteCurrentAttributes(InstanceIdentifier<Address> id, Address dataBefore, WriteContext writeContext)
-        throws WriteFailedException {
-        setAddress(false, id, dataBefore, writeContext);
-    }
-
-    private void setAddress(boolean add, final InstanceIdentifier<Address> id, final Address address,
-                            final WriteContext writeContext) throws WriteFailedException {
+            throws WriteFailedException {
 
         final String interfaceName = id.firstKeyOf(Interface.class).getName();
         final int interfaceIndex = interfaceContext.getIndex(interfaceName, writeContext.getMappingContext());
 
+        setAddress(false, id, interfaceName, interfaceIndex, dataBefore, writeContext);
+    }
+
+    private void setAddress(boolean add, final InstanceIdentifier<Address> id, final String interfaceName,
+                            final int interfaceIndex, final Address address,
+                            final WriteContext writeContext) throws WriteFailedException {
+
         Subnet subnet = address.getSubnet();
 
         if (subnet instanceof PrefixLength) {
@@ -90,10 +123,10 @@ public class Ipv4AddressCustomizer extends FutureJVppCustomizer implements ListW
     private void setNetmaskSubnet(final boolean add, @Nonnull final InstanceIdentifier<Address> id,
                                   @Nonnull final String interfaceName, final int interfaceIndex,
                                   @Nonnull final Address address, @Nonnull final Netmask subnet)
-        throws WriteFailedException {
+            throws WriteFailedException {
         try {
             LOG.debug("Setting Subnet(subnet-mask) for interface: {}(id={}). Subnet: {}, address: {}",
-                interfaceName, interfaceIndex, subnet, address);
+                    interfaceName, interfaceIndex, subnet, address);
 
             final DottedQuad netmask = subnet.getNetmask();
             checkNotNull(netmask, "netmask value should not be null");
@@ -102,7 +135,7 @@ public class Ipv4AddressCustomizer extends FutureJVppCustomizer implements ListW
             Ipv4WriteUtils.addDelAddress(getFutureJVpp(), add, id, interfaceIndex, address.getIp(), subnetLength);
         } catch (VppBaseCallException e) {
             LOG.warn("Failed to set Subnet(subnet-mask) for interface: {}(id={}). Subnet: {}, address: {}",
-                interfaceName, interfaceIndex, subnet, address);
+                    interfaceName, interfaceIndex, subnet, address);
             throw new WriteFailedException(id, "Unable to handle subnet of type " + subnet.getClass(), e);
         }
     }
@@ -110,19 +143,19 @@ public class Ipv4AddressCustomizer extends FutureJVppCustomizer implements ListW
     private void setPrefixLengthSubnet(final boolean add, @Nonnull final InstanceIdentifier<Address> id,
                                        @Nonnull final String interfaceName, final int interfaceIndex,
                                        @Nonnull final Address address, @Nonnull final PrefixLength subnet)
-        throws WriteFailedException {
+            throws WriteFailedException {
         try {
             LOG.debug("Setting Subnet(prefix-length) for interface: {}(id={}). Subnet: {}, address: {}",
-                interfaceName, interfaceIndex, subnet, address);
+                    interfaceName, interfaceIndex, subnet, address);
 
             Ipv4WriteUtils.addDelAddress(getFutureJVpp(), add, id, interfaceIndex, address.getIp(),
-                subnet.getPrefixLength().byteValue());
+                    subnet.getPrefixLength().byteValue());
 
             LOG.debug("Subnet(prefix-length) set successfully for interface: {}(id={}). Subnet: {}, address: {}",
-                interfaceName, interfaceIndex, subnet, address);
+                    interfaceName, interfaceIndex, subnet, address);
         } catch (VppBaseCallException e) {
             LOG.warn("Failed to set Subnet(prefix-length) for interface: {}(id={}). Subnet: {}, address: {}",
-                interfaceName, interfaceIndex, subnet, address);
+                    interfaceName, interfaceIndex, subnet, address);
             throw new WriteFailedException(id, "Unable to handle subnet of type " + subnet.getClass(), e);
         }
     }
index 0e0bba3..2c5230c 100644 (file)
@@ -42,20 +42,19 @@ public class Ipv4Customizer extends FutureJVppCustomizer implements WriterCustom
     @Override
     public void writeCurrentAttributes(@Nonnull final InstanceIdentifier<Ipv4> id,
                                        @Nonnull final Ipv4 dataAfter, @Nonnull final WriteContext writeContext)
-        throws WriteFailedException {
-        final String ifcName = id.firstKeyOf(Interface.class).getName();
-        setIpv4(id, ifcName, dataAfter, writeContext);
+            throws WriteFailedException {
+
+        //TODO - add subnet validation after HONEYCOMB-201
     }
 
     @Override
     public void updateCurrentAttributes(@Nonnull final InstanceIdentifier<Ipv4> id,
                                         @Nonnull final Ipv4 dataBefore, @Nonnull final Ipv4 dataAfter,
                                         @Nonnull final WriteContext writeContext)
-        throws WriteFailedException {
+            throws WriteFailedException {
         final String ifcName = id.firstKeyOf(Interface.class).getName();
 
-        // TODO HONEYCOMB-180 handle update in a better way
-        setIpv4(id, ifcName, dataAfter, writeContext);
+        // TODO handle update in a better way
     }
 
     @Override
@@ -64,16 +63,4 @@ public class Ipv4Customizer extends FutureJVppCustomizer implements WriterCustom
         // TODO HONEYCOMB-180 implement delete
     }
 
-    private void setIpv4(final InstanceIdentifier<Ipv4> id, final String name, final Ipv4 ipv4,
-                         final WriteContext writeContext)
-        throws WriteFailedException {
-        final int swIfc = interfaceContext.getIndex(name, writeContext.getMappingContext());
-
-        LOG.warn("Ignoring Ipv4 leaf nodes (create/update is not supported)");
-        // TODO HONEYCOMB-180 add support for:
-        // enabled leaf
-        // forwarding leaf
-        // mtu leaf
-    }
-
 }
index ab31deb..4120302 100644 (file)
@@ -35,7 +35,7 @@ import org.openvpp.jvpp.core.future.FutureJVppCore;
  * Utility class providing Ipv4 CUD support.
  */
 // TODO HONEYCOMB-175 replace with interface with default methods or abstract class
-final class Ipv4WriteUtils {
+public final class Ipv4WriteUtils {
 
     private static final int DOTTED_QUAD_MASK_LENGTH = 4;
     private static final int IPV4_ADDRESS_PART_BITS_COUNT = 8;
@@ -83,7 +83,7 @@ final class Ipv4WriteUtils {
      * @param mask the subnet mask in dot notation 255.255.255.255
      * @return the prefix length as number of bits
      */
-    static byte getSubnetMaskLength(final String mask) {
+    public static byte getSubnetMaskLength(final String mask) {
         String[] maskParts = mask.split("\\.");
 
         checkArgument(maskParts.length == DOTTED_QUAD_MASK_LENGTH,
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidationException.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidationException.java
new file mode 100644 (file)
index 0000000..ae82910
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.ip.subnet.validation;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collection;
+import javax.annotation.Nonnull;
+import org.apache.commons.lang3.StringUtils;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Address;
+
+/**
+ * Thrown as negative result of subnet validation
+ */
+public class SubnetValidationException extends Exception {
+
+    private SubnetValidationException(@Nonnull final String message) {
+        super(message);
+    }
+
+    public static SubnetValidationException forConflictingData(@Nonnull final Short prefix, @Nonnull Collection<Address> addresses) {
+        return new SubnetValidationException(
+                "Attempt to define multiple addresses for same subnet[prefixLen = " + prefixToString(prefix) + "], "
+                        + "addresses : " + addressesToString(addresses));
+    }
+
+    private static String prefixToString(final Short prefix) {
+        return checkNotNull(prefix, "Cannot create " + SubnetValidationException.class.getName() + " for null prefix")
+                .toString();
+    }
+
+    private static String addressesToString(final Collection<Address> addresses) {
+        return StringUtils.join(checkNotNull(addresses,
+                "Cannot create " + SubnetValidationException.class.getName() + " for null address list"), " | ");
+    }
+}
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidator.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidator.java
new file mode 100644 (file)
index 0000000..3611138
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.ip.subnet.validation;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import io.fd.honeycomb.translate.v3po.interfaces.ip.Ipv4WriteUtils;
+import java.util.List;
+import javax.annotation.Nonnull;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.address.Subnet;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.address.subnet.Netmask;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.address.subnet.PrefixLength;
+
+/**
+ * Validator for detecting if there is an attempt to assign multiple addresses from same subnet
+ */
+public class SubnetValidator {
+
+    /**
+     * Checks whether data provided for writing are not in collision with already existing data
+     */
+    public void checkNotAddingToSameSubnet(@Nonnull final List<Address> addresses)
+            throws SubnetValidationException {
+
+        final Multimap<Short, Address> prefixLengthRegister = Multimaps.index(addresses, toPrefixLength());
+        final int keySetSize = prefixLengthRegister.keySet().size();
+
+        if (keySetSize == 0 || keySetSize == addresses.size()) {
+            //this means that every key is unique(has only one value) or no addresses were prefix-length based ,so there is no conflict
+            return;
+        }
+
+        //finds conflicting prefix
+        final Short conflictingPrefix = prefixLengthRegister.keySet()
+                .stream()
+                .filter(a -> prefixLengthRegister.get(a).size() > 1)
+                .findFirst()
+                .get();
+
+        //and reports it with affected addresses
+        throw SubnetValidationException
+                .forConflictingData(conflictingPrefix, prefixLengthRegister.get(conflictingPrefix));
+    }
+
+    private static Function<Address, Short> toPrefixLength() {
+        return (final Address address) -> {
+            final Subnet subnet = address.getSubnet();
+
+            if (subnet instanceof PrefixLength) {
+                return ((PrefixLength) subnet).getPrefixLength();
+            }
+
+            if (address.getSubnet() instanceof Netmask) {
+                return (short) Ipv4WriteUtils.getSubnetMaskLength(
+                        checkNotNull(((Netmask) subnet).getNetmask(), "No netmask defined for %s", subnet)
+                                .getValue());
+            }
+
+            throw new IllegalArgumentException("Unsupported subnet : " + subnet);
+        };
+    }
+}
index 4988194..a358512 100644 (file)
@@ -21,21 +21,36 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
-import io.fd.honeycomb.translate.v3po.test.ContextTestUtils;
-import io.fd.honeycomb.translate.write.WriteContext;
+import com.google.common.base.Optional;
 import io.fd.honeycomb.translate.MappingContext;
+import io.fd.honeycomb.translate.ModificationCache;
+import io.fd.honeycomb.translate.v3po.interfaces.ip.subnet.validation.SubnetValidationException;
+import io.fd.honeycomb.translate.v3po.interfaces.ip.subnet.validation.SubnetValidator;
+import io.fd.honeycomb.translate.v3po.test.ContextTestUtils;
 import io.fd.honeycomb.translate.v3po.test.TestHelperUtils;
 import io.fd.honeycomb.translate.v3po.util.NamingContext;
+import io.fd.honeycomb.translate.write.WriteContext;
 import io.fd.honeycomb.translate.write.WriteFailedException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
 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.Interfaces;
@@ -43,6 +58,7 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.
 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.ip.rev140616.Interface1;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.Ipv4;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.Ipv4Builder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Address;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.AddressBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.address.subnet.Netmask;
@@ -53,6 +69,7 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.openvpp.jvpp.VppBaseCallException;
 import org.openvpp.jvpp.VppInvocationException;
+import org.openvpp.jvpp.core.dto.IpAddressDetailsReplyDump;
 import org.openvpp.jvpp.core.dto.SwInterfaceAddDelAddress;
 import org.openvpp.jvpp.core.dto.SwInterfaceAddDelAddressReply;
 import org.openvpp.jvpp.core.future.FutureJVppCore;
@@ -63,15 +80,21 @@ public class Ipv4AddressCustomizerTest {
     private static final String IFACE_NAME = "eth0";
     private static final int IFACE_ID = 123;
 
+    @Captor
+    private ArgumentCaptor<List<Address>> addressesCaptor;
+
     @Mock
     private WriteContext writeContext;
     @Mock
     private MappingContext mappingContext;
     @Mock
     private FutureJVppCore api;
+    @Mock
+    private SubnetValidator subnetValidator;
 
     private NamingContext interfaceContext;
     private Ipv4AddressCustomizer customizer;
+    private ModificationCache cache;
 
     @Before
     public void setUp() throws Exception {
@@ -79,16 +102,25 @@ public class Ipv4AddressCustomizerTest {
         doReturn(mappingContext).when(writeContext).getMappingContext();
         interfaceContext = new NamingContext("generatedlIfaceName", IFC_CTX_NAME);
 
-        customizer = new Ipv4AddressCustomizer(api, interfaceContext);
+        customizer = new Ipv4AddressCustomizer(api, interfaceContext, subnetValidator);
+        cache = new ModificationCache();
+        when(writeContext.getModificationCache()).thenReturn(cache);
+
+        CompletableFuture future = new CompletableFuture();
+        future.complete(new IpAddressDetailsReplyDump());
+
+        when(api.ipAddressDump(Mockito.any())).thenReturn(future);
+        when(writeContext.readAfter(Mockito.any()))
+                .thenReturn(Optional.of(new Ipv4Builder().setAddress(Collections.emptyList()).build()));
     }
 
     private static InstanceIdentifier<Address> getAddressId(final String ifaceName) {
         return InstanceIdentifier.builder(Interfaces.class)
-            .child(Interface.class, new InterfaceKey(ifaceName))
-            .augmentation(Interface1.class)
-            .child(Ipv4.class)
-            .child(Address.class)
-            .build();
+                .child(Interface.class, new InterfaceKey(ifaceName))
+                .augmentation(Interface1.class)
+                .child(Ipv4.class)
+                .child(Address.class)
+                .build();
     }
 
     private void whenSwInterfaceAddDelAddressThenSuccess() {
@@ -100,20 +132,20 @@ public class Ipv4AddressCustomizerTest {
 
     private void whenSwInterfaceAddDelAddressThenFailure() {
         doReturn(TestHelperUtils.createFutureException()).when(api)
-            .swInterfaceAddDelAddress(any(SwInterfaceAddDelAddress.class));
+                .swInterfaceAddDelAddress(any(SwInterfaceAddDelAddress.class));
     }
 
     private void verifySwInterfaceAddDelAddressWasInvoked(final SwInterfaceAddDelAddress expected) throws
-        VppInvocationException {
+            VppInvocationException {
         ArgumentCaptor<SwInterfaceAddDelAddress> argumentCaptor =
-            ArgumentCaptor.forClass(SwInterfaceAddDelAddress.class);
+                ArgumentCaptor.forClass(SwInterfaceAddDelAddress.class);
         verify(api).swInterfaceAddDelAddress(argumentCaptor.capture());
         verifySwInterfaceAddDelAddressWasInvoked(expected, argumentCaptor.getValue());
     }
 
     private void verifySwInterfaceAddDelAddressWasInvoked(final SwInterfaceAddDelAddress expected,
                                                           final SwInterfaceAddDelAddress actual) throws
-        VppInvocationException {
+            VppInvocationException {
         assertArrayEquals(expected.address, actual.address);
         assertEquals(expected.addressLength, actual.addressLength);
         assertEquals(expected.delAll, actual.delAll);
@@ -124,7 +156,10 @@ public class Ipv4AddressCustomizerTest {
 
     @Test
     public void testAddPrefixLengthIpv4Address() throws Exception {
+        doNothing().when(subnetValidator).checkNotAddingToSameSubnet(Mockito.anyList());
+
         final InstanceIdentifier<Address> id = getAddressId(IFACE_NAME);
+        when(writeContext.readBefore(id)).thenReturn(Optional.absent());
 
         Ipv4AddressNoZone noZoneIp = new Ipv4AddressNoZone(new Ipv4Address("192.168.2.1"));
         PrefixLength length = new PrefixLengthBuilder().setPrefixLength(new Integer(24).shortValue()).build();
@@ -135,13 +170,14 @@ public class Ipv4AddressCustomizerTest {
 
         customizer.writeCurrentAttributes(id, data, writeContext);
 
-        verifySwInterfaceAddDelAddressWasInvoked(generateSwInterfaceAddDelAddressRequest(new byte[] {-64, -88, 2, 1},
-            (byte) 1, (byte) 24));
+        verifySwInterfaceAddDelAddressWasInvoked(generateSwInterfaceAddDelAddressRequest(new byte[]{-64, -88, 2, 1},
+                (byte) 1, (byte) 24));
     }
 
     @Test
     public void testAddPrefixLengthIpv4AddressFailed() throws Exception {
         final InstanceIdentifier<Address> id = getAddressId(IFACE_NAME);
+        when(writeContext.readBefore(id)).thenReturn(Optional.absent());
 
         Ipv4AddressNoZone noZoneIp = new Ipv4AddressNoZone(new Ipv4Address("192.168.2.1"));
         PrefixLength length = new PrefixLengthBuilder().setPrefixLength(new Integer(24).shortValue()).build();
@@ -155,13 +191,57 @@ public class Ipv4AddressCustomizerTest {
         } catch (WriteFailedException e) {
             assertTrue(e.getCause() instanceof VppBaseCallException);
             verifySwInterfaceAddDelAddressWasInvoked(
-                generateSwInterfaceAddDelAddressRequest(new byte[] {-64, -88, 2, 1},
-                    (byte) 1, (byte) 24));
+                    generateSwInterfaceAddDelAddressRequest(new byte[]{-64, -88, 2, 1},
+                            (byte) 1, (byte) 24));
             return;
         }
         fail("WriteFailedException was expected");
     }
 
+    @Test
+    public void testAddPrefixLengthIpv4AddressConflicted() throws Exception {
+
+        final InstanceIdentifier<Address> id = getAddressId(IFACE_NAME);
+        when(writeContext.readBefore(id)).thenReturn(Optional.absent());
+
+        Ipv4AddressNoZone noZoneIp = new Ipv4AddressNoZone(new Ipv4Address("192.168.2.1"));
+        PrefixLength length = new PrefixLengthBuilder().setPrefixLength(new Integer(24).shortValue()).build();
+        Address data = new AddressBuilder().setIp(noZoneIp).setSubnet(length).build();
+        final List<Address> addressList = Arrays.asList(data);
+
+        //throws when validation invoked
+        doThrow(SubnetValidationException.forConflictingData((short) 24, Arrays.asList(data))).when(subnetValidator)
+                .checkNotAddingToSameSubnet(addressList);
+
+        //fake data return from WriteContext
+        doReturn(Optional.of(new Ipv4Builder().setAddress(addressList).build())).when(writeContext)
+                .readAfter(argThat(matchInstanceIdentifier(Ipv4.class)));
+
+        ContextTestUtils.mockMapping(mappingContext, IFACE_NAME, IFACE_ID, IFC_CTX_NAME);
+
+        try {
+            customizer.writeCurrentAttributes(id, data, writeContext);
+        } catch (WriteFailedException e) {
+            //verifies if cause of exception is correct type
+            assertTrue(e.getCause() instanceof SubnetValidationException);
+
+            //verify that validation call was invoked with data from writeContext
+            verify(subnetValidator, times(1)).checkNotAddingToSameSubnet(addressesCaptor.capture());
+            assertEquals(addressList, addressesCaptor.getValue());
+        }
+
+    }
+
+    private static ArgumentMatcher<InstanceIdentifier<?>> matchInstanceIdentifier(
+            Class<?> desiredClass) {
+        return new ArgumentMatcher<InstanceIdentifier<?>>() {
+            @Override
+            public boolean matches(final Object o) {
+                return o instanceof InstanceIdentifier && ((InstanceIdentifier) o).getTargetType().equals(desiredClass);
+            }
+        };
+    }
+
     private SwInterfaceAddDelAddress generateSwInterfaceAddDelAddressRequest(final byte[] address, final byte isAdd,
                                                                              final byte prefixLength) {
         final SwInterfaceAddDelAddress request = new SwInterfaceAddDelAddress();
@@ -187,8 +267,8 @@ public class Ipv4AddressCustomizerTest {
 
         customizer.deleteCurrentAttributes(id, data, writeContext);
 
-        verifySwInterfaceAddDelAddressWasInvoked(generateSwInterfaceAddDelAddressRequest(new byte[] {-64, -88, 2, 1},
-            (byte) 0, (byte) 24));
+        verifySwInterfaceAddDelAddressWasInvoked(generateSwInterfaceAddDelAddressRequest(new byte[]{-64, -88, 2, 1},
+                (byte) 0, (byte) 24));
     }
 
     @Test
@@ -207,8 +287,8 @@ public class Ipv4AddressCustomizerTest {
         } catch (WriteFailedException e) {
             assertTrue(e.getCause() instanceof VppBaseCallException);
             verifySwInterfaceAddDelAddressWasInvoked(
-                generateSwInterfaceAddDelAddressRequest(new byte[] {-64, -88, 2, 1},
-                    (byte) 0, (byte) 24));
+                    generateSwInterfaceAddDelAddressRequest(new byte[]{-64, -88, 2, 1},
+                            (byte) 0, (byte) 24));
             return;
         }
         fail("WriteFailedException was expec16ted");
@@ -216,6 +296,7 @@ public class Ipv4AddressCustomizerTest {
 
     private void testSingleNetmask(final int expectedPrefixLength, final String stringMask) throws Exception {
         final InstanceIdentifier<Address> id = getAddressId(IFACE_NAME);
+        when(writeContext.readBefore(id)).thenReturn(Optional.absent());
 
         Ipv4AddressNoZone noZoneIp = new Ipv4AddressNoZone(new Ipv4Address("192.168.2.1"));
         Netmask subnet = new NetmaskBuilder().setNetmask(new DottedQuad(stringMask)).build();
@@ -226,18 +307,19 @@ public class Ipv4AddressCustomizerTest {
         final CompletableFuture<SwInterfaceAddDelAddressReply> replyFuture = new CompletableFuture<>();
         replyFuture.complete(new SwInterfaceAddDelAddressReply());
         ArgumentCaptor<SwInterfaceAddDelAddress> argumentCaptor =
-            ArgumentCaptor.forClass(SwInterfaceAddDelAddress.class);
+                ArgumentCaptor.forClass(SwInterfaceAddDelAddress.class);
         doReturn(replyFuture).when(api).swInterfaceAddDelAddress(argumentCaptor.capture());
 
         customizer.writeCurrentAttributes(id, data, writeContext);
 
-        verifySwInterfaceAddDelAddressWasInvoked(generateSwInterfaceAddDelAddressRequest(new byte[] {-64, -88, 2, 1},
-            (byte) 1, (byte) expectedPrefixLength), argumentCaptor.getValue());
+        verifySwInterfaceAddDelAddressWasInvoked(generateSwInterfaceAddDelAddressRequest(new byte[]{-64, -88, 2, 1},
+                (byte) 1, (byte) expectedPrefixLength), argumentCaptor.getValue());
     }
 
     private void testSingleIllegalNetmask(final String stringMask) throws Exception {
         try {
             final InstanceIdentifier<Address> id = getAddressId(IFACE_NAME);
+            when(writeContext.readBefore(id)).thenReturn(Optional.absent());
 
             Ipv4AddressNoZone noZoneIp = new Ipv4AddressNoZone(new Ipv4Address("192.168.2.1"));
             Netmask subnet = new NetmaskBuilder().setNetmask(new DottedQuad(stringMask)).build();
@@ -248,7 +330,7 @@ public class Ipv4AddressCustomizerTest {
             final CompletableFuture<SwInterfaceAddDelAddressReply> replyFuture = new CompletableFuture<>();
             replyFuture.complete(new SwInterfaceAddDelAddressReply());
             ArgumentCaptor<SwInterfaceAddDelAddress> argumentCaptor =
-                ArgumentCaptor.forClass(SwInterfaceAddDelAddress.class);
+                    ArgumentCaptor.forClass(SwInterfaceAddDelAddress.class);
             doReturn(replyFuture).when(api).swInterfaceAddDelAddress(argumentCaptor.capture());
 
             customizer.writeCurrentAttributes(id, data, writeContext);
@@ -288,5 +370,4 @@ public class Ipv4AddressCustomizerTest {
         testSingleIllegalNetmask("255.1.255.0");
         testSingleIllegalNetmask("255.255.255.255");
     }
-
 }
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidatorTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/ip/subnet/validation/SubnetValidatorTest.java
new file mode 100644 (file)
index 0000000..3a29a19
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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.ip.subnet.validation;
+
+
+import com.google.common.collect.Lists;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.AddressBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.address.subnet.NetmaskBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.address.subnet.PrefixLengthBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DottedQuad;
+
+public class SubnetValidatorTest {
+
+    private SubnetValidator subnetValidator;
+
+    @Before
+    public void init() {
+        MockitoAnnotations.initMocks(this);
+        subnetValidator = new SubnetValidator();
+    }
+
+    @Test(expected = SubnetValidationException.class)
+    public void testValidateNegativeSameTypes() throws SubnetValidationException {
+        List<Address> addresses = Lists.newArrayList();
+
+        addresses.add(new AddressBuilder().setSubnet(new PrefixLengthBuilder().setPrefixLength((short) 24).build())
+                .build());
+        addresses.add(new AddressBuilder().setSubnet(new PrefixLengthBuilder().setPrefixLength((short) 24).build())
+                .build());
+
+        subnetValidator.checkNotAddingToSameSubnet(addresses);
+    }
+
+    @Test(expected = SubnetValidationException.class)
+    public void testValidateNegativeMixedTypes() throws SubnetValidationException {
+        List<Address> addresses = Lists.newArrayList();
+
+        addresses.add(new AddressBuilder().setSubnet(new PrefixLengthBuilder().setPrefixLength((short) 24).build())
+                .build());
+        addresses.add(new AddressBuilder()
+                .setSubnet(new NetmaskBuilder().setNetmask(new DottedQuad("255.255.255.0")).build())
+                .build());
+
+        subnetValidator.checkNotAddingToSameSubnet(addresses);
+    }
+
+    @Test
+    public void testValidatePositiveSameTypes() throws SubnetValidationException {
+        List<Address> addresses = Lists.newArrayList();
+
+        addresses.add(new AddressBuilder().setSubnet(new PrefixLengthBuilder().setPrefixLength((short) 24).build())
+                .build());
+        addresses.add(new AddressBuilder().setSubnet(new PrefixLengthBuilder().setPrefixLength((short) 25).build())
+                .build());
+
+        subnetValidator.checkNotAddingToSameSubnet(addresses);
+    }
+
+    @Test
+    public void testValidatePositiveMixedTypes() throws SubnetValidationException {
+        List<Address> addresses = Lists.newArrayList();
+
+        addresses.add(new AddressBuilder().setSubnet(new PrefixLengthBuilder().setPrefixLength((short) 24).build())
+                .build());
+        addresses.add(new AddressBuilder()
+                .setSubnet(new NetmaskBuilder().setNetmask(new DottedQuad("255.255.0.0")).build())
+                .build());
+
+        subnetValidator.checkNotAddingToSameSubnet(addresses);
+    }
+}