summaryrefslogtreecommitdiffstats
path: root/v3po/v3po2vpp/src/test
diff options
context:
space:
mode:
authorMaros Marsalek <mmarsale@cisco.com>2016-04-12 10:13:14 +0200
committerMaros Marsalek <mmarsale@cisco.com>2016-04-12 10:13:14 +0200
commitc7ca517b00f2682987aef3ac390dfc04155a8aee (patch)
tree662791d6e857234dab467d9cddd55300060ccaca /v3po/v3po2vpp/src/test
parent4a3dce3e0e59df4e091b4f8d4efc3e20831bf22f (diff)
HONEYCOMB-9: Split impl module into smaller parts
Change-Id: I9232e0adfe611cb97951080839b28a7b62ba5484 Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
Diffstat (limited to 'v3po/v3po2vpp/src/test')
-rw-r--r--v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/BridgeDomainCustomizerTest.java269
-rw-r--r--v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/BridgeDomainTestUtils.java64
-rw-r--r--v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/VppTest.java174
-rw-r--r--v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/VppUtils.java62
-rw-r--r--v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vppstate/VppStateTest.java259
-rw-r--r--v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vppstate/VppStateUtils.java77
6 files changed, 905 insertions, 0 deletions
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/BridgeDomainCustomizerTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/BridgeDomainCustomizerTest.java
new file mode 100644
index 000000000..059713544
--- /dev/null
+++ b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/BridgeDomainCustomizerTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.vpp.facade.v3po.vpp;
+
+import static io.fd.honeycomb.v3po.vpp.facade.v3po.vpp.BridgeDomainTestUtils.BD_NAME_TO_ID_ANSWER;
+import static io.fd.honeycomb.v3po.vpp.facade.v3po.vpp.BridgeDomainTestUtils.bdIdentifierForName;
+import static io.fd.honeycomb.v3po.vpp.facade.v3po.vpp.BridgeDomainTestUtils.bdNameToID;
+import static io.fd.honeycomb.v3po.vpp.facade.v3po.vpp.BridgeDomainTestUtils.booleanToByte;
+import static io.fd.honeycomb.v3po.vpp.facade.v3po.vpp.BridgeDomainTestUtils.intToBoolean;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import io.fd.honeycomb.v3po.vpp.facade.Context;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainKey;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.openvpp.vppjapi.vppApi;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@SuppressStaticInitializationFor("org.openvpp.vppjapi.vppConn")
+@PrepareForTest(vppApi.class)
+public class BridgeDomainCustomizerTest {
+
+ private static final int RESPONSE_NOT_READY = -77;
+ private static final byte ADD_OR_UPDATE_BD = (byte) 1;
+ private static final byte ZERO = 0;
+ private vppApi api;
+
+ @Mock
+ private Context ctx;
+
+ private BridgeDomainCustomizer customizer;
+
+ @Before
+ public void setUp() throws Exception {
+ // TODO create base class for tests using vppApi
+ api = PowerMockito.mock(vppApi.class);
+ initMocks(this);
+ customizer = new BridgeDomainCustomizer(api);
+
+ PowerMockito.doAnswer(BD_NAME_TO_ID_ANSWER).when(api).findOrAddBridgeDomainId(anyString());
+ PowerMockito.doAnswer(BD_NAME_TO_ID_ANSWER).when(api).bridgeDomainIdFromName(anyString());
+ PowerMockito.when(api.getRetval(anyInt(), anyInt())).thenReturn(RESPONSE_NOT_READY).thenReturn(0);
+ PowerMockito.doReturn(0).when(api).getRetval(anyInt(), anyInt());
+ }
+
+ private BridgeDomain generateBridgeDomain(final String bdName) {
+ final byte arpTerm = 0;
+ final byte flood = 1;
+ final byte forward = 0;
+ final byte learn = 1;
+ final byte uuf = 0;
+ return generateBridgeDomain(bdName, arpTerm, flood, forward, learn, uuf);
+ }
+
+ private BridgeDomain generateBridgeDomain(final String bdName, final int arpTerm, final int flood,
+ final int forward, final int learn, final int uuf) {
+ return new BridgeDomainBuilder()
+ .setName(bdName)
+ .setArpTermination(intToBoolean(arpTerm))
+ .setFlood(intToBoolean(flood))
+ .setForward(intToBoolean(forward))
+ .setLearn(intToBoolean(learn))
+ .setUnknownUnicastFlood(intToBoolean(uuf))
+ .build();
+ }
+
+ private final int verifyBridgeDomainAddOrUpdateWasInvoked(final BridgeDomain bd) {
+ final int bdn1Id = bdNameToID(bd.getName());
+ final byte arpTerm = booleanToByte(bd.isArpTermination());
+ final byte flood = booleanToByte(bd.isFlood());
+ final byte forward = booleanToByte(bd.isForward());
+ final byte learn = booleanToByte(bd.isLearn());
+ final byte uuf = booleanToByte(bd.isUnknownUnicastFlood());
+ return verify(api).bridgeDomainAddDel(bdn1Id, flood, forward, learn, uuf, arpTerm, ADD_OR_UPDATE_BD);
+ }
+
+ private int verifyBridgeDomainAddOrUpdateWasNotInvoked(final BridgeDomain bd) {
+ final int bdn1Id = bdNameToID(bd.getName());
+ final byte arpTerm = booleanToByte(bd.isArpTermination());
+ final byte flood = booleanToByte(bd.isFlood());
+ final byte forward = booleanToByte(bd.isForward());
+ final byte learn = booleanToByte(bd.isLearn());
+ final byte uuf = booleanToByte(bd.isUnknownUnicastFlood());
+ return verify(api, never()).bridgeDomainAddDel(bdn1Id, flood, forward, learn, uuf, arpTerm, ADD_OR_UPDATE_BD);
+ }
+
+ private int verifyBridgeDomainDeletedWasInvoked(final BridgeDomain bd) {
+ final int bdn1Id = bdNameToID(bd.getName());
+ return verify(api).bridgeDomainAddDel(bdn1Id, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO);
+ }
+
+ private int verifyBridgeDomainDeletedWasNotInvoked(final BridgeDomain bd) {
+ final int bdn1Id = bdNameToID(bd.getName());
+ return verify(api, never()).bridgeDomainAddDel(bdn1Id, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO);
+ }
+
+ @Test
+ public void testAddBridgeDomain() {
+ final String bdName = "bd1";
+ final BridgeDomain bd = generateBridgeDomain("bd1");
+
+ customizer.writeCurrentAttributes(bdIdentifierForName(bdName), bd, ctx);
+
+ verifyBridgeDomainAddOrUpdateWasInvoked(bd);
+ }
+
+ @Test
+ public void testBridgeDomainNameCreateFailed() {
+ final String bdName = "bd1";
+ final BridgeDomain bd = generateBridgeDomain("bd1");
+
+ // make vpp api fail to create id for our bd name
+ PowerMockito.doReturn(-1).when(api).findOrAddBridgeDomainId(bdName);
+
+ try {
+ customizer.writeCurrentAttributes(bdIdentifierForName(bdName), bd, ctx);
+ } catch (IllegalStateException e) {
+ verifyBridgeDomainAddOrUpdateWasNotInvoked(bd);
+ return;
+ }
+ fail("IllegalStateException was expected");
+ }
+
+ @Test
+ public void testAddBridgeDomainFailed() {
+ // make any call to vpp fail
+ PowerMockito.doReturn(-1).when(api).getRetval(anyInt(), anyInt());
+
+ final String bdName = "bd1";
+ final BridgeDomain bd = generateBridgeDomain(bdName);
+
+ try {
+ customizer.writeCurrentAttributes(bdIdentifierForName(bdName), bd, ctx);
+ } catch (IllegalStateException e) {
+ verifyBridgeDomainAddOrUpdateWasInvoked(bd);
+ return;
+ }
+ fail("IllegalStateException was expected");
+ }
+
+ @Test
+ public void testDeleteBridgeDomain() {
+ final String bdName = "bd1";
+ final BridgeDomain bd = generateBridgeDomain("bd1");
+
+ customizer.deleteCurrentAttributes(bdIdentifierForName(bdName), bd, ctx);
+
+ verifyBridgeDomainDeletedWasInvoked(bd);
+ }
+
+ @Test
+ public void testDeleteUnknownBridgeDomain() {
+ final String bdName = "bd1";
+ final BridgeDomain bd = generateBridgeDomain("bd1");
+
+ // make vpp api not find our bd
+ PowerMockito.doReturn(-1).when(api).bridgeDomainIdFromName(bdName);
+
+ try {
+ customizer.deleteCurrentAttributes(bdIdentifierForName(bdName), bd, ctx);
+ } catch (IllegalStateException e) {
+ verifyBridgeDomainDeletedWasNotInvoked(bd);
+ return;
+ }
+ fail("IllegalStateException was expected");
+ }
+
+ @Test
+ public void testDeleteBridgeDomainFailed() {
+ // make any call to vpp fail
+ PowerMockito.doReturn(-1).when(api).getRetval(anyInt(), anyInt());
+
+ final String bdName = "bd1";
+ final BridgeDomain bd = generateBridgeDomain(bdName);
+
+ try {
+ customizer.deleteCurrentAttributes(bdIdentifierForName(bdName), bd, ctx);
+ } catch (IllegalStateException e) {
+ verifyBridgeDomainDeletedWasInvoked(bd);
+ return;
+ }
+ fail("IllegalStateException was expected");
+ }
+
+ @Test
+ public void testUpdateBridgeDomain() throws Exception {
+ final String bdName = "bd1";
+ final byte arpTermBefore = 1;
+ final byte floodBefore = 1;
+ final byte forwardBefore = 0;
+ final byte learnBefore = 1;
+ final byte uufBefore = 0;
+
+ final BridgeDomain dataBefore =
+ generateBridgeDomain(bdName, arpTermBefore, floodBefore, forwardBefore, learnBefore, uufBefore);
+ final BridgeDomain dataAfter =
+ generateBridgeDomain(bdName, arpTermBefore ^ 1, floodBefore ^ 1, forwardBefore ^ 1, learnBefore ^ 1,
+ uufBefore ^ 1);
+
+ final KeyedInstanceIdentifier<BridgeDomain, BridgeDomainKey> id = bdIdentifierForName(bdName);
+
+ customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx);
+
+ verifyBridgeDomainAddOrUpdateWasInvoked(dataAfter);
+ }
+
+ @Test
+ public void testUpdateUnknownBridgeDomain() throws Exception {
+ final String bdName = "bd1";
+ final BridgeDomain bd = generateBridgeDomain("bd1");
+
+ // make vpp api not find our bd
+ PowerMockito.doReturn(-1).when(api).bridgeDomainIdFromName(bdName);
+
+ try {
+ customizer.updateCurrentAttributes(bdIdentifierForName(bdName), bd, bd, ctx);
+ } catch (IllegalStateException e) {
+ verifyBridgeDomainAddOrUpdateWasNotInvoked(bd);
+ return;
+ }
+ fail("IllegalStateException was expected");
+ }
+
+ @Test
+ public void testUpdateBridgeDomainFailed() {
+ // make any call to vpp fail
+ PowerMockito.doReturn(-1).when(api).getRetval(anyInt(), anyInt());
+
+ final String bdName = "bd1";
+ final BridgeDomain bd = generateBridgeDomain(bdName);
+
+ try {
+ customizer.updateCurrentAttributes(bdIdentifierForName(bdName), bd, bd, ctx);
+ } catch (IllegalStateException e) {
+ verifyBridgeDomainAddOrUpdateWasInvoked(bd);
+ return;
+ }
+ fail("IllegalStateException was expected");
+ }
+
+} \ No newline at end of file
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/BridgeDomainTestUtils.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/BridgeDomainTestUtils.java
new file mode 100644
index 000000000..51a6b023e
--- /dev/null
+++ b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/BridgeDomainTestUtils.java
@@ -0,0 +1,64 @@
+/*
+ * 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.vpp.facade.v3po.vpp;
+
+import javax.annotation.Nullable;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainKey;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+
+final class BridgeDomainTestUtils {
+
+ private BridgeDomainTestUtils() {
+ throw new UnsupportedOperationException("Utility class cannot be instantiated.");
+ }
+
+ public static byte booleanToByte(@Nullable final Boolean value) {
+ return value != null && value ? (byte) 1 : (byte) 0;
+ }
+
+ @Nullable
+ public static Boolean intToBoolean(final int value) {
+ if (value == 0) {
+ return Boolean.FALSE;
+ }
+ if (value == 1) {
+ return Boolean.TRUE;
+ }
+ return null;
+ }
+
+ public static int bdNameToID(String bName) {
+ return Integer.parseInt(((Character)bName.charAt(bName.length() - 1)).toString());
+ }
+
+ public static KeyedInstanceIdentifier<BridgeDomain, BridgeDomainKey> bdIdentifierForName(
+ final String bdName) {
+ return InstanceIdentifier.create(BridgeDomains.class).child(BridgeDomain.class, new BridgeDomainKey(bdName));
+ }
+
+ public static final Answer<Integer> BD_NAME_TO_ID_ANSWER = new Answer<Integer>() {
+ @Override
+ public Integer answer(final InvocationOnMock invocationOnMock) throws Throwable {
+ return bdNameToID((String) invocationOnMock.getArguments()[0]);
+ }
+ };
+} \ No newline at end of file
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/VppTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/VppTest.java
new file mode 100644
index 000000000..bc5bda383
--- /dev/null
+++ b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/VppTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.vpp.facade.v3po.vpp;
+
+import static io.fd.honeycomb.v3po.vpp.facade.v3po.vpp.BridgeDomainTestUtils.BD_NAME_TO_ID_ANSWER;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.common.collect.Lists;
+import io.fd.honeycomb.v3po.vpp.facade.impl.write.CompositeRootVppWriter;
+import io.fd.honeycomb.v3po.vpp.facade.impl.write.util.DelegatingWriterRegistry;
+import io.fd.honeycomb.v3po.vpp.facade.write.VppWriter;
+import io.fd.honeycomb.v3po.vpp.facade.write.WriteContext;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomainsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainBuilder;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.openvpp.vppjapi.vppApi;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@SuppressStaticInitializationFor("org.openvpp.vppjapi.vppConn")
+@PrepareForTest(vppApi.class)
+public class VppTest {
+
+ private vppApi api;
+ private DelegatingWriterRegistry rootRegistry;
+ private CompositeRootVppWriter<Vpp> vppWriter;
+ private WriteContext ctx;
+
+ final byte zero = (byte) 0;
+ final byte flood = (byte) 1;
+ final byte forward = (byte) 0;
+ final byte learn = (byte) 1;
+ final byte uuf = (byte) 0;
+ final byte arpTerm = (byte) 0;
+ final byte add = (byte) 1;
+
+ @Before
+ public void setUp() throws Exception {
+ api = PowerMockito.mock(vppApi.class);
+ ctx = mock(WriteContext.class);
+ PowerMockito.doAnswer(BD_NAME_TO_ID_ANSWER).when(api).findOrAddBridgeDomainId(anyString());
+ PowerMockito.doAnswer(BD_NAME_TO_ID_ANSWER).when(api).bridgeDomainIdFromName(anyString());
+ PowerMockito.doReturn(1).when(api).getRetval(anyInt(), anyInt());
+ vppWriter = VppUtils.getVppWriter(api);
+ rootRegistry = new DelegatingWriterRegistry(
+ Collections.<VppWriter<? extends DataObject>>singletonList(vppWriter));
+ }
+
+ @Test
+ public void writeVpp() throws Exception {
+ rootRegistry.update(
+ InstanceIdentifier.create(Vpp.class),
+ null,
+ new VppBuilder().setBridgeDomains(getBridgeDomains("bdn1")).build(),
+ ctx);
+
+ verify(api).bridgeDomainAddDel(1, flood, forward, learn, uuf, arpTerm, add);
+
+ vppWriter.update(InstanceIdentifier.create(Vpp.class),
+ null,
+ new VppBuilder().setBridgeDomains(getBridgeDomains("bdn1")).build(),
+ ctx);
+
+ verify(api, times(2)).bridgeDomainAddDel(1, flood, forward, learn, uuf, arpTerm, add);
+ }
+
+ @Test
+ public void writeVppFromRoot() throws Exception {
+ final Vpp vpp = new VppBuilder().setBridgeDomains(getBridgeDomains("bdn1")).build();
+
+ rootRegistry.update(Collections.<InstanceIdentifier<?>, DataObject>emptyMap(),
+ Collections.<InstanceIdentifier<?>, DataObject>singletonMap(InstanceIdentifier.create(Vpp.class),
+ vpp), ctx);
+
+ verify(api).bridgeDomainAddDel(1, flood, forward, learn, uuf, arpTerm, add);
+ }
+
+ private BridgeDomains getBridgeDomains(String... name) {
+ final List<BridgeDomain> bdmns = Lists.newArrayList();
+ for (String s : name) {
+ bdmns.add(new BridgeDomainBuilder()
+ .setName(s)
+ .setArpTermination(false)
+ .setFlood(true)
+ .setForward(false)
+ .setLearn(true)
+ .build());
+ }
+ return new BridgeDomainsBuilder()
+ .setBridgeDomain(bdmns)
+ .build();
+ }
+
+ @Test
+ public void deleteVpp() throws Exception {
+ rootRegistry.update(
+ InstanceIdentifier.create(Vpp.class),
+ new VppBuilder().setBridgeDomains(getBridgeDomains("bdn1")).build(),
+ null,
+ ctx);
+
+ final byte zero = (byte) 0;
+
+ verify(api).bridgeDomainAddDel(1, zero, zero, zero, zero, zero, zero);
+ }
+
+ @Test
+ public void updateVppNoActualChange() throws Exception {
+ rootRegistry.update(
+ InstanceIdentifier.create(Vpp.class),
+ new VppBuilder().setBridgeDomains(getBridgeDomains("bdn1")).build(),
+ new VppBuilder().setBridgeDomains(getBridgeDomains("bdn1")).build(),
+ ctx);
+
+ verifyZeroInteractions(api);
+ }
+
+ @Test
+ public void writeUpdate() throws Exception {
+ final BridgeDomains domainsBefore = getBridgeDomains("bdn1");
+ final BridgeDomain bdn1Before = domainsBefore.getBridgeDomain().get(0);
+
+ final BridgeDomain bdn1After = new BridgeDomainBuilder(bdn1Before).setFlood(!bdn1Before.isFlood()).build();
+ final BridgeDomains domainsAfter = new BridgeDomainsBuilder()
+ .setBridgeDomain(Collections.singletonList(bdn1After))
+ .build();
+
+ rootRegistry.update(
+ InstanceIdentifier.create(Vpp.class),
+ new VppBuilder().setBridgeDomains(domainsBefore).build(),
+ new VppBuilder().setBridgeDomains(domainsAfter).build(),
+ ctx);
+
+ final int bdn1Id = 1;
+
+ // bdn1 is created with negated flood value
+ verify(api).bridgeDomainAddDel(bdn1Id, (byte) (flood ^ 1), forward, learn, uuf, arpTerm, add);
+ }
+
+ // TODO test unkeyed list
+ // TODO test update of a child without dedicated writer
+} \ No newline at end of file
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/VppUtils.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/VppUtils.java
new file mode 100644
index 000000000..ccf19c81c
--- /dev/null
+++ b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vpp/VppUtils.java
@@ -0,0 +1,62 @@
+/*
+ * 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.vpp.facade.v3po.vpp;
+
+import io.fd.honeycomb.v3po.vpp.facade.impl.util.VppRWUtils;
+import io.fd.honeycomb.v3po.vpp.facade.impl.write.CompositeChildVppWriter;
+import io.fd.honeycomb.v3po.vpp.facade.impl.write.CompositeListVppWriter;
+import io.fd.honeycomb.v3po.vpp.facade.impl.write.CompositeRootVppWriter;
+import io.fd.honeycomb.v3po.vpp.facade.impl.write.util.NoopWriterCustomizer;
+import io.fd.honeycomb.v3po.vpp.facade.impl.write.util.ReflexiveChildWriterCustomizer;
+import io.fd.honeycomb.v3po.vpp.facade.write.ChildVppWriter;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainKey;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.openvpp.vppjapi.vppApi;
+
+final class VppUtils {
+
+ public VppUtils() {}
+
+ /**
+ * Create root Vpp writer with all its children wired
+ */
+ static CompositeRootVppWriter<Vpp> getVppWriter(@Nonnull final vppApi vppApi) {
+
+ final CompositeListVppWriter<BridgeDomain, BridgeDomainKey> bridgeDomainWriter = new CompositeListVppWriter<>(
+ BridgeDomain.class,
+ new BridgeDomainCustomizer(vppApi));
+
+ final ChildVppWriter<BridgeDomains> bridgeDomainsReader = new CompositeChildVppWriter<>(
+ BridgeDomains.class,
+ VppRWUtils.singletonChildWriterList(bridgeDomainWriter),
+ new ReflexiveChildWriterCustomizer<BridgeDomains>());
+
+ final List<ChildVppWriter<? extends ChildOf<Vpp>>> childWriters = new ArrayList<>();
+ childWriters.add(bridgeDomainsReader);
+
+ return new CompositeRootVppWriter<>(
+ Vpp.class,
+ childWriters,
+ new NoopWriterCustomizer<Vpp>());
+ }
+}
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vppstate/VppStateTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vppstate/VppStateTest.java
new file mode 100644
index 000000000..059c98f56
--- /dev/null
+++ b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vppstate/VppStateTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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.vpp.facade.v3po.vppstate;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import io.fd.honeycomb.v3po.vpp.facade.impl.read.CompositeListVppReader;
+import io.fd.honeycomb.v3po.vpp.facade.impl.read.CompositeRootVppReader;
+import io.fd.honeycomb.v3po.vpp.facade.impl.read.util.DelegatingReaderRegistry;
+import io.fd.honeycomb.v3po.vpp.facade.read.ReadContext;
+import io.fd.honeycomb.v3po.vpp.facade.read.VppReader;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Matchers;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+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.VppState;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppStateBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.BridgeDomains;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.Version;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.VersionBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomain;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomainBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomainKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.bridge.domain.L2Fib;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.bridge.domain.L2FibKey;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.openvpp.vppjapi.vppApi;
+import org.openvpp.vppjapi.vppBridgeDomainDetails;
+import org.openvpp.vppjapi.vppBridgeDomainInterfaceDetails;
+import org.openvpp.vppjapi.vppL2Fib;
+import org.openvpp.vppjapi.vppVersion;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@SuppressStaticInitializationFor("org.openvpp.vppjapi.vppConn")
+@PrepareForTest(vppApi.class)
+public class VppStateTest {
+
+ public static final vppVersion VERSION = new vppVersion("test", "1", "2", "33");
+
+ private vppApi api;
+ private CompositeRootVppReader<VppState, VppStateBuilder> vppStateReader;
+ private DelegatingReaderRegistry readerRegistry;
+ private vppBridgeDomainDetails bdDetails;
+ private vppBridgeDomainDetails bdDetails2;
+ private ReadContext ctx;
+
+ @Before
+ public void setUp() throws Exception {
+ api = PowerMockito.mock(vppApi.class);
+
+ ctx = mock(ReadContext.class);
+
+ bdDetails = new vppBridgeDomainDetails();
+ setIfcs(bdDetails);
+ setBaseAttrs(bdDetails, "bdn1", 1);
+
+ bdDetails2 = new vppBridgeDomainDetails();
+ setIfcs(bdDetails2);
+ setBaseAttrs(bdDetails2, "bdn2", 2);
+
+ final vppL2Fib[] l2Fibs = getL2Fibs();
+ PowerMockito.doReturn(l2Fibs).when(api).l2FibTableDump(Matchers.anyInt());
+ PowerMockito.doAnswer(new Answer<vppBridgeDomainDetails>() {
+
+ @Override
+ public vppBridgeDomainDetails answer(final InvocationOnMock invocationOnMock) throws Throwable {
+ final Integer idx = (Integer) invocationOnMock.getArguments()[0];
+ switch (idx) {
+ case 1 : return bdDetails;
+ case 2 : return bdDetails2;
+ default: return null;
+ }
+ }
+ }).when(api).getBridgeDomainDetails(Matchers.anyInt());
+
+ PowerMockito.doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(final InvocationOnMock invocationOnMock) throws Throwable {
+ final String name = (String) invocationOnMock.getArguments()[0];
+ switch (name) {
+ case "bdn1" : return 1;
+ case "bdn2" : return 2;
+ default: return null;
+ }
+ }
+ }).when(api).bridgeDomainIdFromName(anyString());
+ PowerMockito.doReturn(new int[] {1, 2}).when(api).bridgeDomainDump(Matchers.anyInt());
+ PowerMockito.doReturn(VERSION).when(api).getVppVersion();
+ vppStateReader = VppStateUtils.getVppStateReader(api);
+ readerRegistry = new DelegatingReaderRegistry(Collections.<VppReader<? extends DataObject>>singletonList(vppStateReader));
+ }
+
+ private vppL2Fib[] getL2Fibs() {
+ return new vppL2Fib[] {
+ new vppL2Fib(new byte[]{1,2,3,4,5,6}, true, "ifc1", true, true),
+ new vppL2Fib(new byte[]{2,2,3,4,5,6}, true, "ifc2", true, true),
+ };
+ }
+
+ private void setIfcs(final vppBridgeDomainDetails bdDetails) {
+ final vppBridgeDomainInterfaceDetails ifcDetails = new vppBridgeDomainInterfaceDetails();
+ ifcDetails.interfaceName = "ifc";
+ ifcDetails.splitHorizonGroup = 2;
+ bdDetails.interfaces = new vppBridgeDomainInterfaceDetails[] {ifcDetails};
+ }
+
+ private void setBaseAttrs(final vppBridgeDomainDetails bdDetails, final String bdn, final int i) {
+ bdDetails.name = bdn;
+ bdDetails.arpTerm = true;
+ bdDetails.bdId = i;
+ bdDetails.bviInterfaceName = "ifc";
+ bdDetails.flood = true;
+ bdDetails.forward = true;
+ bdDetails.learn = true;
+ bdDetails.uuFlood = true;
+ }
+
+ @Test
+ public void testReadAll() throws Exception {
+ final Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> dataObjects = readerRegistry.readAll(ctx);
+ assertEquals(dataObjects.size(), 1);
+ final DataObject dataObject = Iterables.getOnlyElement(dataObjects.get(Iterables.getOnlyElement(dataObjects.keySet())));
+ assertTrue(dataObject instanceof VppState);
+ assertVersion((VppState) dataObject);
+ assertEquals(2, ((VppState) dataObject).getBridgeDomains().getBridgeDomain().size());
+ }
+
+ private void assertVersion(final VppState dataObject) {
+ assertEquals(
+ new VersionBuilder()
+ .setName("test")
+ .setBuildDirectory("1")
+ .setBranch("2")
+ .setBuildDate("33")
+ .build(),
+ dataObject.getVersion());
+ }
+
+ @Test
+ public void testReadSpecific() throws Exception {
+ final Optional<? extends DataObject> read = readerRegistry.read(InstanceIdentifier.create(VppState.class), ctx);
+ assertTrue(read.isPresent());
+ assertVersion((VppState) read.get());
+ }
+
+ @Test
+ public void testReadBridgeDomains() throws Exception {
+ VppState readRoot = (VppState) readerRegistry.read(InstanceIdentifier.create(VppState.class), ctx).get();
+
+ Optional<? extends DataObject> read =
+ readerRegistry.read(InstanceIdentifier.create(VppState.class).child(BridgeDomains.class), ctx);
+ assertTrue(read.isPresent());
+ assertEquals(readRoot.getBridgeDomains(), read.get());
+ }
+
+ /**
+ * L2fib does not have a dedicated reader, relying on auto filtering
+ */
+ @Test
+ public void testReadL2Fib() throws Exception {
+ // Deep child without a dedicated reader with specific l2fib key
+ Optional<? extends DataObject> read =
+ readerRegistry.read(InstanceIdentifier.create(VppState.class).child(BridgeDomains.class).child(
+ BridgeDomain.class, new BridgeDomainKey("bdn1"))
+ .child(L2Fib.class, new L2FibKey(new PhysAddress("01:02:03:04:05:06"))), ctx);
+ assertTrue(read.isPresent());
+
+ // non existing l2fib
+ read =
+ readerRegistry.read(InstanceIdentifier.create(VppState.class).child(BridgeDomains.class).child(
+ BridgeDomain.class, new BridgeDomainKey("bdn1"))
+ .child(L2Fib.class, new L2FibKey(new PhysAddress("FF:FF:FF:04:05:06"))), ctx);
+ assertFalse(read.isPresent());
+ }
+
+ @Test
+ public void testReadBridgeDomainAll() throws Exception {
+ VppState readRoot = (VppState) readerRegistry.read(InstanceIdentifier.create(VppState.class), ctx).get();
+
+ final CompositeListVppReader<BridgeDomain, BridgeDomainKey, BridgeDomainBuilder> bridgeDomainReader =
+ VppStateUtils.getBridgeDomainReader(api);
+
+ final List<BridgeDomain> read =
+ bridgeDomainReader.readList(InstanceIdentifier.create(VppState.class).child(BridgeDomains.class).child(
+ BridgeDomain.class), ctx);
+
+ assertEquals(readRoot.getBridgeDomains().getBridgeDomain(), read);
+ }
+
+ @Test
+ public void testReadBridgeDomain() throws Exception {
+ VppState readRoot = (VppState) readerRegistry.read(InstanceIdentifier.create(VppState.class), ctx).get();
+
+ final Optional<? extends DataObject> read =
+ readerRegistry.read(InstanceIdentifier.create(VppState.class).child(BridgeDomains.class).child(
+ BridgeDomain.class, new BridgeDomainKey("bdn1")), ctx);
+
+ assertTrue(read.isPresent());
+ assertEquals(Iterables.find(readRoot.getBridgeDomains().getBridgeDomain(), new Predicate<BridgeDomain>() {
+ @Override
+ public boolean apply(final BridgeDomain input) {
+ return input.getKey().getName().equals("bdn1");
+ }
+ }), read.get());
+ }
+
+ // FIXME
+ @Ignore("Bridge domain customizer does not check whether the bd exists or not and fails with NPE, add it there")
+ @Test
+ public void testReadBridgeDomainNotExisting() throws Exception {
+ final Optional<? extends DataObject> read =
+ readerRegistry.read(InstanceIdentifier.create(VppState.class).child(BridgeDomains.class).child(
+ BridgeDomain.class, new BridgeDomainKey("NOT EXISTING")), ctx);
+ assertFalse(read.isPresent());
+ }
+
+ @Test
+ public void testReadVersion() throws Exception {
+ VppState readRoot = (VppState) readerRegistry.read(InstanceIdentifier.create(VppState.class), ctx).get();
+
+ Optional<? extends DataObject> read =
+ readerRegistry.read(InstanceIdentifier.create(VppState.class).child(Version.class), ctx);
+ assertTrue(read.isPresent());
+ assertEquals(readRoot.getVersion(), read.get());
+ }
+} \ No newline at end of file
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vppstate/VppStateUtils.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vppstate/VppStateUtils.java
new file mode 100644
index 000000000..7d5441769
--- /dev/null
+++ b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/vpp/facade/v3po/vppstate/VppStateUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.vpp.facade.v3po.vppstate;
+
+import io.fd.honeycomb.v3po.vpp.facade.impl.read.CompositeChildVppReader;
+import io.fd.honeycomb.v3po.vpp.facade.impl.read.CompositeListVppReader;
+import io.fd.honeycomb.v3po.vpp.facade.impl.read.CompositeRootVppReader;
+import io.fd.honeycomb.v3po.vpp.facade.impl.read.util.ReflexiveChildReaderCustomizer;
+import io.fd.honeycomb.v3po.vpp.facade.impl.read.util.ReflexiveRootReaderCustomizer;
+import io.fd.honeycomb.v3po.vpp.facade.impl.util.VppRWUtils;
+import io.fd.honeycomb.v3po.vpp.facade.read.ChildVppReader;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppStateBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.BridgeDomains;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.BridgeDomainsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.Version;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomain;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomainBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomainKey;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.openvpp.vppjapi.vppApi;
+
+final class VppStateUtils {
+
+ public VppStateUtils() {}
+
+ /**
+ * Create root VppState reader with all its children wired
+ */
+ static CompositeRootVppReader<VppState, VppStateBuilder> getVppStateReader(@Nonnull final vppApi vppApi) {
+
+ final ChildVppReader<Version> versionReader = new CompositeChildVppReader<>(
+ Version.class, new VersionCustomizer(vppApi));
+
+ final CompositeListVppReader<BridgeDomain, BridgeDomainKey, BridgeDomainBuilder> bridgeDomainReader =
+ getBridgeDomainReader(vppApi);
+
+ final ChildVppReader<BridgeDomains> bridgeDomainsReader = new CompositeChildVppReader<>(
+ BridgeDomains.class,
+ VppRWUtils.singletonChildReaderList(bridgeDomainReader),
+ new ReflexiveChildReaderCustomizer<>(BridgeDomainsBuilder.class));
+
+ final List<ChildVppReader<? extends ChildOf<VppState>>> childVppReaders = new ArrayList<>();
+ childVppReaders.add(versionReader);
+ childVppReaders.add(bridgeDomainsReader);
+
+ return new CompositeRootVppReader<>(
+ VppState.class,
+ childVppReaders,
+ VppRWUtils.<VppState>emptyAugReaderList(),
+ new ReflexiveRootReaderCustomizer<>(VppStateBuilder.class));
+ }
+
+ static CompositeListVppReader<BridgeDomain, BridgeDomainKey, BridgeDomainBuilder> getBridgeDomainReader(
+ final @Nonnull vppApi vppApi) {
+ return new CompositeListVppReader<>(
+ BridgeDomain.class,
+ new BridgeDomainCustomizer(vppApi));
+ }
+}