From 32aa07e5517fba7f78ae79d2ba83b56f72a53293 Mon Sep 17 00:00:00 2001 From: Jan Srnicek Date: Thu, 1 Dec 2016 18:15:52 +0100 Subject: HONEYCOMB-58 - Routing Plugin Structure Read/Write support for ipv4/6 static routes. Restriction due to vpp implementation described in readme. Change-Id: I328f406a9b7cb8781f8becf98eca293cebe66859 Signed-off-by: Jan Srnicek --- vpp-common/vpp-translate-utils/pom.xml | 13 +- .../common/translate/util/AddressTranslator.java | 3 + .../common/translate/util/ByteDataTranslator.java | 37 +++- .../common/translate/util/Ipv4Translator.java | 11 ++ .../common/translate/util/Ipv6Translator.java | 31 +++- .../common/translate/util/MultiNamingContext.java | 155 ++++++++++++++++ .../common/translate/util/NamingContext.java | 30 ++++ .../translate/util/ByteDataTranslatorTest.java | 8 +- .../common/translate/util/Ipv4TranslatorTest.java | 5 + .../common/translate/util/Ipv6TranslatorTest.java | 7 + .../translate/util/MultiNamingContextTest.java | 200 +++++++++++++++++++++ .../common/translate/util/NamingContextTest.java | 116 ++++++++++++ .../src/test/resources/multi-mapping.json | 35 ++++ .../src/test/resources/naming.json | 14 ++ 14 files changed, 651 insertions(+), 14 deletions(-) create mode 100644 vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/MultiNamingContext.java create mode 100644 vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/MultiNamingContextTest.java create mode 100644 vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/NamingContextTest.java create mode 100644 vpp-common/vpp-translate-utils/src/test/resources/multi-mapping.json create mode 100644 vpp-common/vpp-translate-utils/src/test/resources/naming.json (limited to 'vpp-common/vpp-translate-utils') diff --git a/vpp-common/vpp-translate-utils/pom.xml b/vpp-common/vpp-translate-utils/pom.xml index c217bdb26..94bb07aab 100644 --- a/vpp-common/vpp-translate-utils/pom.xml +++ b/vpp-common/vpp-translate-utils/pom.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - io.fd.hc2vpp.common @@ -93,5 +93,16 @@ ${system.rules.version} test + + io.fd.honeycomb.infra + test-tools + ${project.version} + test + + + org.hamcrest + hamcrest-all + test + diff --git a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/AddressTranslator.java b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/AddressTranslator.java index eaa966479..170ff43e5 100644 --- a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/AddressTranslator.java +++ b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/AddressTranslator.java @@ -30,6 +30,9 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types. */ public interface AddressTranslator extends Ipv4Translator, Ipv6Translator, MacTranslator { + AddressTranslator INSTANCE = new AddressTranslator() { + }; + default byte[] ipAddressToArray(IpAddress address) { checkNotNull(address, "Cannot resolve null adddress"); diff --git a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/ByteDataTranslator.java b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/ByteDataTranslator.java index a96542623..17aff0e2e 100644 --- a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/ByteDataTranslator.java +++ b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/ByteDataTranslator.java @@ -24,6 +24,12 @@ import javax.annotation.Nullable; */ public interface ByteDataTranslator { + ByteDataTranslator INSTANCE = new ByteDataTranslator() { + }; + + byte BYTE_FALSE = 0; + byte BYTE_TRUE = 1; + /** * Returns 0 if argument is null or false, 1 otherwise. * @@ -32,8 +38,22 @@ public interface ByteDataTranslator { */ default byte booleanToByte(@Nullable final Boolean value) { return value != null && value - ? (byte) 1 - : (byte) 0; + ? BYTE_TRUE + : BYTE_FALSE; + } + + /** + * Converts int to byte + */ + default byte toByte(final int value) { + return Integer.valueOf(value).byteValue(); + } + + /** + * Converts short to byte + */ + default byte toByte(final short value) { + return Short.valueOf(value).byteValue(); } /** @@ -45,9 +65,9 @@ public interface ByteDataTranslator { */ @Nonnull default Boolean byteToBoolean(final byte value) { - if (value == 0) { + if (value == BYTE_FALSE) { return Boolean.FALSE; - } else if (value == 1) { + } else if (value == BYTE_TRUE) { return Boolean.TRUE; } throw new IllegalArgumentException(String.format("0 or 1 was expected but was %d", value)); @@ -76,4 +96,13 @@ public interface ByteDataTranslator { default String toString(final byte[] cString) { return new String(cString).replaceAll("\\u0000", "").intern(); } + + /** + * Converts signed byte(filled with unsigned value from vpp) to java integer + * + * For example unsigned C byte 128 is converted by jvpp to -128, this will return 128 + */ + default int toJavaByte(final byte vppByte) { + return Byte.toUnsignedInt(vppByte); + } } diff --git a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/Ipv4Translator.java b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/Ipv4Translator.java index 99d1757b4..02f89d9f8 100644 --- a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/Ipv4Translator.java +++ b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/Ipv4Translator.java @@ -108,4 +108,15 @@ public interface Ipv4Translator extends ByteDataTranslator { } return retval; } + + default Ipv4Prefix toIpv4Prefix(final byte[] address, final int prefix) { + try { + return new Ipv4Prefix( + String.format("%s/%s", InetAddress.getByAddress(address).getHostAddress(), + String.valueOf(prefix))); + } catch (UnknownHostException e) { + throw new IllegalArgumentException( + "Cannot create prefix for address[" + Arrays.toString(address) + "],prefix[" + prefix + "]"); + } + } } diff --git a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/Ipv6Translator.java b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/Ipv6Translator.java index cb8b2ac87..2d0b51b40 100644 --- a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/Ipv6Translator.java +++ b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/Ipv6Translator.java @@ -39,17 +39,11 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types. */ public interface Ipv6Translator extends ByteDataTranslator { - /** - * Transform Ipv6 address to a byte array acceptable by VPP. VPP expects incoming byte array to be in the same order - * as the address. - * - * @return byte array with address bytes - */ - default byte[] ipv6AddressNoZoneToArray(@Nonnull final Ipv6AddressNoZone ipv6Addr) { + default byte[] ipv6AddressNoZoneToArray(@Nonnull final String address) { byte[] retval = new byte[16]; //splits address and add ommited zeros for easier parsing - List segments = Arrays.asList(ipv6Addr.getValue().split(":")) + List segments = Arrays.asList(address.split(":")) .stream() .map(segment -> StringUtils.repeat('0', 4 - segment.length()) + segment) .collect(Collectors.toList()); @@ -73,6 +67,16 @@ public interface Ipv6Translator extends ByteDataTranslator { return retval; } + /** + * Transform Ipv6 address to a byte array acceptable by VPP. VPP expects incoming byte array to be in the same order + * as the address. + * + * @return byte array with address bytes + */ + default byte[] ipv6AddressNoZoneToArray(@Nonnull final Ipv6AddressNoZone ipv6Addr) { + return ipv6AddressNoZoneToArray(ipv6Addr.getValue()); + } + /** * Creates address array from address part of {@link Ipv6Prefix} */ @@ -136,4 +140,15 @@ public interface Ipv6Translator extends ByteDataTranslator { checkState(!(address.getIpv4Prefix() == null && address.getIpv6Prefix() == null), "Invalid address"); return address.getIpv6Prefix() != null; } + + default Ipv6Prefix toIpv6Prefix(final byte[] address, final int prefix) { + try { + return new Ipv6Prefix( + String.format("%s/%s", InetAddress.getByAddress(address).getHostAddress(), + String.valueOf(prefix))); + } catch (UnknownHostException e) { + throw new IllegalArgumentException( + "Cannot create prefix for address[" + Arrays.toString(address) + "],prefix[" + prefix + "]"); + } + } } diff --git a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/MultiNamingContext.java b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/MultiNamingContext.java new file mode 100644 index 000000000..6b10881d6 --- /dev/null +++ b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/MultiNamingContext.java @@ -0,0 +1,155 @@ +/* + * 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.hc2vpp.common.translate.util; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.Optional; +import io.fd.honeycomb.translate.MappingContext; +import io.fd.honeycomb.translate.util.RWUtils; +import java.util.Collections; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.MultiMappingCtxAugmentation; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.MultiNamingContexts; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.MultiNaming; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.MultiNamingKey; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.Mappings; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.Mapping; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.MappingBuilder; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.MappingKey; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.mapping.Value; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.mapping.ValueBuilder; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.Contexts; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; + +/** + * One to many context mapping + */ +public class MultiNamingContext { + + private final KeyedInstanceIdentifier + multiNamingContextIid; + + private final int startIndex; + + public MultiNamingContext(@Nonnull final String instanceName, final int startIndex) { + multiNamingContextIid = InstanceIdentifier.create(Contexts.class) + .augmentation(MultiMappingCtxAugmentation.class) + .child(MultiNamingContexts.class) + .child(MultiNaming.class, new MultiNamingKey(instanceName)); + this.startIndex = startIndex; + } + + public synchronized void addChild(@Nonnull final String parentName, final int childIndex, + @Nonnull final String childName, + @Nonnull final MappingContext mappingContext) { + checkArgument(childIndex >= startIndex, "Index cannot be lower than start index %s", startIndex); + final KeyedInstanceIdentifier mappingIid = getMappingIid(parentName); + + //uses merge to preserve previous + mappingContext.merge(mappingIid, + new MappingBuilder().setName(parentName).setValue(Collections.singletonList(new ValueBuilder() + .setIndex(childIndex) + .setName(childName) + .build())).build()); + } + + public synchronized void addChild(@Nonnull final String parentName, + @Nonnull final String childName, + @Nonnull final MappingContext mappingContext) { + addChild(parentName, getNextAvailableChildIndex(parentName, mappingContext), childName, mappingContext); + } + + public synchronized String getChildName(@Nonnull final String parentName, + @Nonnull final int childIndex, + @Nonnull final MappingContext mappingContext) { + final Optional read = mappingContext.read(getMappingIid(parentName)); + + checkState(read.isPresent(), "Mapping not present"); + + return read.get().getValue().stream() + .filter(value -> value.getIndex().equals(childIndex)) + .collect(RWUtils.singleItemCollector()).getName(); + } + + public synchronized int getChildIndex(@Nonnull final String parentName, + @Nonnull final String childName, + @Nonnull final MappingContext mappingContext) { + final Optional read = mappingContext.read(getMappingIid(parentName)); + + checkState(read.isPresent(), "Mapping not present"); + + return read.get().getValue().stream() + .filter(value -> value.getName().equals(childName)) + .collect(RWUtils.singleItemCollector()).getIndex(); + } + + + public synchronized void removeChild(@Nonnull final String parentName, + @Nonnull final String childName, + @Nonnull final MappingContext mappingContext) { + + final Optional read = mappingContext.read(getMappingIid(parentName)); + + // ignore delete's for non-existing parent + if (read.isPresent()) { + final Mapping mapping = read.get(); + + // overrides old data with new(without removed child) + mappingContext.put(getMappingIid(parentName), new MappingBuilder() + .setName(mapping.getName()) + .setKey(mapping.getKey()) + .setValue(mapping.getValue() + .stream() + .filter(value -> !value.getName().equals(childName)) + .collect(Collectors.toList())) + .build()); + } + } + + /** + * Returns next available index for mapping + */ + private int getNextAvailableChildIndex(final String parentName, final MappingContext mappingContext) { + final Optional read = mappingContext.read(mappingIdBase()); + + if (!read.isPresent()) { + return startIndex; + } + + return read.get().getMapping() + .stream() + .filter(mapping -> mapping.getName().equals(parentName)) + .flatMap(mapping -> mapping.getValue().stream()) + .mapToInt(Value::getIndex) + // do not use i++(need increase before, not after + .map(i -> ++i) + .max() + .orElse(startIndex); + } + + private KeyedInstanceIdentifier getMappingIid(final String name) { + return mappingIdBase().child(Mapping.class, new MappingKey(name)); + } + + private InstanceIdentifier mappingIdBase() { + return multiNamingContextIid.child(Mappings.class); + } +} diff --git a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/NamingContext.java b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/NamingContext.java index 393df0898..5e0eea71b 100644 --- a/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/NamingContext.java +++ b/vpp-common/vpp-translate-utils/src/main/java/io/fd/hc2vpp/common/translate/util/NamingContext.java @@ -128,6 +128,17 @@ public final class NamingContext implements AutoCloseable { mappingContext.put(mappingIid, new MappingBuilder().setIndex(index).setName(name).build()); } + /** + * Add mapping to current context with next available index. + * Suitable for learned data mappings + * + * @param name name of a mapped item + * @param mappingContext mapping context providing context data for current transaction + */ + public synchronized void addName(final String name, final MappingContext mappingContext) { + addName(getNextAvailableIndex(mappingContext), name, mappingContext); + } + private KeyedInstanceIdentifier getMappingIid(final String name) { return namingContextIid.child(Mappings.class).child(Mapping.class, new MappingKey(name)); } @@ -172,6 +183,25 @@ public final class NamingContext implements AutoCloseable { return artificialNamePrefix + index; } + /** + * Returns next available index for mapping + */ + private int getNextAvailableIndex(final MappingContext mappingContext) { + final Optional read = mappingContext.read(namingContextIid.child(Mappings.class)); + + if (!read.isPresent()) { + return 0; + } + + return read.get().getMapping() + .stream() + .mapToInt(Mapping::getIndex) + // do not use i++(need increase before, not after + .map(i -> ++i) + .max() + .orElse(0); + } + @Override public void close() throws Exception { /// Not removing the mapping from backing storage diff --git a/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/ByteDataTranslatorTest.java b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/ByteDataTranslatorTest.java index 8494568ec..6e801db13 100644 --- a/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/ByteDataTranslatorTest.java +++ b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/ByteDataTranslatorTest.java @@ -19,7 +19,6 @@ package io.fd.hc2vpp.common.translate.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import io.fd.hc2vpp.common.translate.util.ByteDataTranslator; import org.junit.Test; public class ByteDataTranslatorTest implements ByteDataTranslator { @@ -50,4 +49,11 @@ public class ByteDataTranslatorTest implements ByteDataTranslator { final String jString = toString(cString); assertArrayEquals(expected, jString.getBytes()); } + + @Test + public void testToJavaByte() { + assertEquals(128, toJavaByte((byte) -128)); + assertEquals(129, toJavaByte((byte) -127)); + assertEquals(127, toJavaByte((byte) 127)); + } } \ No newline at end of file diff --git a/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/Ipv4TranslatorTest.java b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/Ipv4TranslatorTest.java index 37e29d88c..484ea2cf7 100644 --- a/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/Ipv4TranslatorTest.java +++ b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/Ipv4TranslatorTest.java @@ -44,4 +44,9 @@ public class Ipv4TranslatorTest implements Ipv4Translator { public void testExtractPrefix() { assertEquals(24, extractPrefix(new Ipv4Prefix("192.168.2.1/24"))); } + + @Test + public void testToPrefix() { + assertEquals("192.168.2.1/24", toIpv4Prefix(new byte[]{-64, -88, 2, 1}, (byte) 24).getValue()); + } } \ No newline at end of file diff --git a/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/Ipv6TranslatorTest.java b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/Ipv6TranslatorTest.java index 43327ea56..1099f6809 100644 --- a/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/Ipv6TranslatorTest.java +++ b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/Ipv6TranslatorTest.java @@ -51,4 +51,11 @@ public class Ipv6TranslatorTest implements Ipv6Translator { public void testExtractPrefix() { assertEquals(48, extractPrefix(new Ipv6Prefix("3ffe:1900:4545:3:200:f8ff:fe21:67cf/48"))); } + + @Test + public void toPrefix() { + assertEquals("2001:db8:a0b:12f0:0:0:0:1/48", + toIpv6Prefix(new byte[]{32, 1, 13, -72, 10, 11, 18, -16, 0, 0, 0, 0, 0, 0, 0, 1}, + (byte) 48).getValue()); + } } \ No newline at end of file diff --git a/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/MultiNamingContextTest.java b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/MultiNamingContextTest.java new file mode 100644 index 000000000..b38ce6e26 --- /dev/null +++ b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/MultiNamingContextTest.java @@ -0,0 +1,200 @@ +/* + * 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.hc2vpp.common.translate.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.base.Optional; +import io.fd.honeycomb.test.tools.HoneycombTestRunner; +import io.fd.honeycomb.test.tools.annotations.InjectTestData; +import io.fd.honeycomb.test.tools.annotations.InjectablesProcessor; +import io.fd.honeycomb.test.tools.annotations.SchemaContextProvider; +import io.fd.honeycomb.translate.MappingContext; +import io.fd.honeycomb.translate.util.RWUtils; +import java.util.Collections; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.$YangModuleInfoImpl; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.MultiMappingCtxAugmentation; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.MultiNamingContexts; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.MultiNaming; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.MultiNamingKey; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.Mappings; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.Mapping; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.MappingKey; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.mapping.Value; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.mapping.ValueBuilder; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.multi.naming.context.rev160411.multi.naming.contexts.attributes.multi.naming.contexts.multi.naming.mappings.mapping.ValueKey; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.Contexts; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; + +@RunWith(HoneycombTestRunner.class) +public class MultiNamingContextTest implements InjectablesProcessor { + + private static final String NON_EXISTING_PARENT = "non-existing-parent"; + private static final String PARENT_1 = "parent-1"; + private static final String PARENT_2 = "parent-2"; + private static final String PARENT_3 = "parent-3"; + private static final String CHILD_1 = "child-1"; + private static final String CHILD_2 = "child-2"; + private static final String CHILD_3 = "child-3"; + + @Mock + private MappingContext mappingContext; + + @Captor + private ArgumentCaptor instanceIdentifierArgumentCaptor; + + @Captor + private ArgumentCaptor mappingArgumentCaptor; + + private MultiNamingContext namingContext; + private KeyedInstanceIdentifier multiNamingContextIid; + + @InjectTestData(resourcePath = "/multi-mapping.json", + id = "/naming-context:contexts/" + + "multi-naming-context:multi-naming-contexts" + + "/multi-naming-context:multi-naming[multi-naming-context:name='context']" + + "/multi-naming-context:mappings") + private Mappings mappings; + + @SchemaContextProvider + public ModuleInfoBackedContext schemaContext() { + return provideSchemaContextFor(Collections.singleton($YangModuleInfoImpl.getInstance())); + } + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + this.namingContext = new MultiNamingContext("context", 3); + this.multiNamingContextIid = InstanceIdentifier.create(Contexts.class) + .augmentation(MultiMappingCtxAugmentation.class) + .child(MultiNamingContexts.class) + .child(MultiNaming.class, new MultiNamingKey("context")); + + when(mappingContext.read(multiNamingContextIid.child(Mappings.class))).thenReturn(Optional.of(mappings)); + when(mappingContext.read(parentKey(NON_EXISTING_PARENT))).thenReturn(Optional.absent()); + when(mappingContext.read(parentKey(PARENT_1))).thenReturn(Optional.of(filterForParent(PARENT_1))); + when(mappingContext.read(parentKey(PARENT_2))).thenReturn(Optional.of(filterForParent(PARENT_2))); + when(mappingContext.read(parentKey(PARENT_3))).thenReturn(Optional.of(filterForParent(PARENT_3))); + } + + private Mapping filterForParent(final String parent) { + return mappings.getMapping().stream() + .filter(mapping -> mapping.getName().equals(parent)) + .collect(RWUtils.singleItemCollector()); + } + + private KeyedInstanceIdentifier parentKey(final String parent) { + return multiNamingContextIid.child(Mappings.class).child(Mapping.class, new MappingKey(parent)); + } + + @Test + public void addChildSpecificIndex() throws Exception { + namingContext.addChild(PARENT_1, 3, CHILD_1, mappingContext); + + verify(mappingContext, times(1)) + .merge(instanceIdentifierArgumentCaptor.capture(), mappingArgumentCaptor.capture()); + + assertEquals(instanceIdentifierArgumentCaptor.getValue(), parentKey(PARENT_1)); + + final Mapping mapping = mappingArgumentCaptor.getValue(); + final List values = mapping.getValue(); + assertEquals(PARENT_1, mapping.getName()); + assertThat(values, hasSize(1)); + + final Value child = values.get(0); + assertEquals(CHILD_1, child.getName()); + assertEquals(3, child.getIndex().intValue()); + } + + @Test(expected = IllegalArgumentException.class) + public void addInvalidIndex() { + namingContext.addChild(PARENT_1, 2, CHILD_1, mappingContext); + } + + @Test + public void addChildNextAvailableIndex() throws Exception { + namingContext.addChild(PARENT_1, CHILD_1, mappingContext); + + verify(mappingContext, times(1)) + .merge(instanceIdentifierArgumentCaptor.capture(), mappingArgumentCaptor.capture()); + assertEquals(instanceIdentifierArgumentCaptor.getValue(), parentKey(PARENT_1)); + + final Mapping mapping = mappingArgumentCaptor.getValue(); + final List values = mapping.getValue(); + assertEquals(PARENT_1, mapping.getName()); + assertThat(values, hasSize(1)); + + final Value child = values.get(0); + assertEquals(CHILD_1, child.getName()); + assertEquals(4, child.getIndex().intValue()); + } + + @Test + public void getChildName() throws Exception { + assertEquals(CHILD_1, namingContext.getChildName(PARENT_1, 1, mappingContext)); + } + + @Test + public void getChildIndex() throws Exception { + assertEquals(1, namingContext.getChildIndex(PARENT_1, CHILD_1, mappingContext)); + } + + @Test + public void removeChild() throws Exception { + namingContext.removeChild(PARENT_1, CHILD_1, mappingContext); + + verify(mappingContext, times(1)) + .put(instanceIdentifierArgumentCaptor.capture(), mappingArgumentCaptor.capture()); + + assertEquals(instanceIdentifierArgumentCaptor.getValue(), parentKey(PARENT_1)); + final Mapping mapping = mappingArgumentCaptor.getValue(); + final List values = mapping.getValue(); + + assertEquals(PARENT_1, mapping.getName()); + assertThat(values, hasSize(2)); + assertThat(values, contains(valueFor(CHILD_2, 2), valueFor(CHILD_3, 3))); + } + + @Test + public void removeChildNonExistingParent() { + namingContext.removeChild(NON_EXISTING_PARENT, CHILD_1, mappingContext); + // if parent doest not exist, do nothing + verify(mappingContext, times(0)).put(Mockito.any(), Mockito.any()); + } + + private Value valueFor(final String name, final int index) { + return new ValueBuilder().setName(name).setIndex(index).setKey(new ValueKey(name)).build(); + } +} + diff --git a/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/NamingContextTest.java b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/NamingContextTest.java new file mode 100644 index 000000000..4e66315e1 --- /dev/null +++ b/vpp-common/vpp-translate-utils/src/test/java/io/fd/hc2vpp/common/translate/util/NamingContextTest.java @@ -0,0 +1,116 @@ +/* + * 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.hc2vpp.common.translate.util; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.base.Optional; +import io.fd.honeycomb.test.tools.HoneycombTestRunner; +import io.fd.honeycomb.test.tools.annotations.InjectTestData; +import io.fd.honeycomb.test.tools.annotations.InjectablesProcessor; +import io.fd.honeycomb.test.tools.annotations.SchemaContextProvider; +import io.fd.honeycomb.translate.MappingContext; +import io.fd.honeycomb.translate.util.RWUtils; +import java.util.Collections; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.$YangModuleInfoImpl; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.Contexts; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.contexts.NamingContextKey; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.contexts.naming.context.Mappings; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.contexts.naming.context.mappings.Mapping; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.contexts.naming.context.mappings.MappingBuilder; +import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.contexts.naming.context.mappings.MappingKey; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; + +@RunWith(HoneycombTestRunner.class) +public class NamingContextTest implements InjectablesProcessor { + + private static final String NAME_1 = "name-1"; + private static final String NAME_2 = "name-2"; + + @InjectTestData(resourcePath = "/naming.json", id = "/naming-context:contexts" + + "/naming-context:naming-context[naming-context:name='context']" + + "/naming-context:mappings") + private Mappings mappings; + + @Mock + private MappingContext mappingContext; + + @Captor + private ArgumentCaptor instanceIdentifierArgumentCaptor; + + @Captor + private ArgumentCaptor mappingArgumentCaptor; + + private NamingContext namingContext; + private KeyedInstanceIdentifier + namingContextIid; + + @SchemaContextProvider + public ModuleInfoBackedContext schemaContext() { + return provideSchemaContextFor(Collections.singleton($YangModuleInfoImpl.getInstance())); + } + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + + this.namingContext = new NamingContext("prefix", "context"); + this.namingContextIid = InstanceIdentifier.create(Contexts.class).child( + org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.contexts.NamingContext.class, + new NamingContextKey("context")); + + when(mappingContext.read(namingContextIid.child(Mappings.class))).thenReturn(Optional.of(mappings)); + when(mappingContext.read(parentKey(NAME_1))).thenReturn(Optional.of(filterForParent(NAME_1))); + when(mappingContext.read(parentKey(NAME_2))).thenReturn(Optional.of(filterForParent(NAME_2))); + + } + + @Test + public void addNameNextIndex() throws Exception { + namingContext.addName("name-3", mappingContext); + verify(mappingContext, times(1)) + .put(instanceIdentifierArgumentCaptor.capture(), mappingArgumentCaptor.capture()); + + assertEquals(instanceIdentifierArgumentCaptor.getValue(), parentKey("name-3")); + assertEquals(mappingArgumentCaptor.getValue(), new MappingBuilder() + .setIndex(3) + .setName("name-3") + .build()); + } + + private Mapping filterForParent(final String parent) { + return mappings.getMapping().stream() + .filter(mapping -> mapping.getName().equals(parent)) + .collect(RWUtils.singleItemCollector()); + } + + private KeyedInstanceIdentifier parentKey(final String parent) { + return namingContextIid.child(Mappings.class).child(Mapping.class, new MappingKey(parent)); + } +} diff --git a/vpp-common/vpp-translate-utils/src/test/resources/multi-mapping.json b/vpp-common/vpp-translate-utils/src/test/resources/multi-mapping.json new file mode 100644 index 000000000..c20401fc0 --- /dev/null +++ b/vpp-common/vpp-translate-utils/src/test/resources/multi-mapping.json @@ -0,0 +1,35 @@ +{ + "mappings": { + "mapping": [ + { + "name": "parent-1", + "value": [ + { + "index": 1, + "name": "child-1" + }, + { + "index": 2, + "name": "child-2" + }, + { + "index": 3, + "name": "child-3" + } + ] + }, + { + "name": "parent-2" + }, + { + "name": "parent-3", + "value": [ + { + "index": 1, + "name": "child-1" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/vpp-common/vpp-translate-utils/src/test/resources/naming.json b/vpp-common/vpp-translate-utils/src/test/resources/naming.json new file mode 100644 index 000000000..0248264d2 --- /dev/null +++ b/vpp-common/vpp-translate-utils/src/test/resources/naming.json @@ -0,0 +1,14 @@ +{ + "mappings": { + "mapping": [ + { + "name": "name-1", + "index": 1 + }, + { + "name": "name-2", + "index": 2 + } + ] + } +} \ No newline at end of file -- cgit 1.2.3-korg