From 04c94a48416e0c87f2d62164e11a6266630e6956 Mon Sep 17 00:00:00 2001 From: Peter Suna Date: Mon, 20 Feb 2023 09:43:58 +0100 Subject: [PATCH] Add yanglib API for non-revision model The Yanglib API previously used a single API for both models with revisions and those without. As a result, it was necessary to append a '/' at the end of the URL request for non-revision models. This issue has now been resolved by introducing a separate API specifically for non-revision models. Added tests to validate the handling of both revision and non-revision models. Additionally, the existing Yanglib tests have been reworked to use YANG models stored in resources. JIRA: NETCONF-968 Change-Id: I3a22de90457bd1b2dbb77f3228bcce83a97ba4e1 Signed-off-by: Peter Suna Signed-off-by: Ivan Hrasko --- .../yanglib/api/YangLibService.java | 9 ++ .../yanglib/impl/YangLibProvider.java | 26 ++-- .../yanglib/impl/YangLibProviderTest.java | 143 +++++++++--------- .../resources/model/model1@2023-02-21.yang | 15 ++ .../src/test/resources/model/model2.yang | 10 ++ 5 files changed, 121 insertions(+), 82 deletions(-) create mode 100644 netconf/yanglib/src/test/resources/model/model1@2023-02-21.yang create mode 100644 netconf/yanglib/src/test/resources/model/model2.yang diff --git a/netconf/yanglib/src/main/java/org/opendaylight/yanglib/api/YangLibService.java b/netconf/yanglib/src/main/java/org/opendaylight/yanglib/api/YangLibService.java index 8701eba24f..508c5d49b1 100644 --- a/netconf/yanglib/src/main/java/org/opendaylight/yanglib/api/YangLibService.java +++ b/netconf/yanglib/src/main/java/org/opendaylight/yanglib/api/YangLibService.java @@ -30,4 +30,13 @@ public interface YangLibService { @GET @Path("/schemas/{modelName}/{revision:([0-9\\-]*)}") String getSchema(@PathParam("modelName") String name, @PathParam("revision") String revision); + + /** + * Get module's source for each module from yang library without a revision. + * @param name Module's name + * @return Module's source + */ + @GET + @Path("/schemas/{modelName}") + String getSchema(@PathParam("modelName") String name); } diff --git a/netconf/yanglib/src/main/java/org/opendaylight/yanglib/impl/YangLibProvider.java b/netconf/yanglib/src/main/java/org/opendaylight/yanglib/impl/YangLibProvider.java index 68f9b2d4c3..e1140a2153 100644 --- a/netconf/yanglib/src/main/java/org/opendaylight/yanglib/impl/YangLibProvider.java +++ b/netconf/yanglib/src/main/java/org/opendaylight/yanglib/impl/YangLibProvider.java @@ -14,7 +14,6 @@ import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import java.io.File; import java.io.IOException; @@ -179,20 +178,23 @@ public class YangLibProvider implements AutoCloseable, SchemaSourceListener, Yan @Override public String getSchema(final String name, final String revision) { LOG.debug("Attempting load for schema source {}:{}", name, revision); - final SourceIdentifier sourceId = new SourceIdentifier(name, revision.isEmpty() ? null : revision); + return getYangModel(name, revision.isEmpty() ? null : revision); + } - final ListenableFuture sourceFuture = schemaRepository.getSchemaSource(sourceId, - YangTextSchemaSource.class); + @Override + public String getSchema(final String name) { + LOG.debug("Attempting load for schema source {}: no-revision", name); + return getYangModel(name, null); + } - final YangTextSchemaSource source; + private String getYangModel(final String name, final String revision) { + final var sourceId = new SourceIdentifier(name, revision); + final var yangTextSchemaFuture = schemaRepository.getSchemaSource(sourceId, YangTextSchemaSource.class); try { - source = sourceFuture.get(); + final var yangTextSchemaSource = yangTextSchemaFuture.get(); + return yangTextSchemaSource.asCharSource(StandardCharsets.UTF_8).read(); } catch (InterruptedException | ExecutionException e) { throw new IllegalStateException("Unable to get schema " + sourceId, e); - } - - try { - return source.asCharSource(StandardCharsets.UTF_8).read(); } catch (IOException e) { throw new IllegalStateException("Unable to read schema " + sourceId, e); } @@ -200,11 +202,11 @@ public class YangLibProvider implements AutoCloseable, SchemaSourceListener, Yan private Uri getUrlForModule(final SourceIdentifier sourceIdentifier) { return new Uri("http://" + yanglibConfig.getBindingAddr() + ':' + yanglibConfig.getBindingPort() - + "/yanglib/schemas/" + sourceIdentifier.name().getLocalName() + '/' + revString(sourceIdentifier)); + + "/yanglib/schemas/" + sourceIdentifier.name().getLocalName() + revString(sourceIdentifier)); } private static String revString(final SourceIdentifier id) { final var rev = id.revision(); - return rev != null ? rev.toString() : ""; + return rev != null ? "/" + rev : ""; } } diff --git a/netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibProviderTest.java b/netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibProviderTest.java index 8430e5b63b..794946ab1b 100644 --- a/netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibProviderTest.java +++ b/netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibProviderTest.java @@ -7,23 +7,19 @@ */ package org.opendaylight.yanglib.impl; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.opendaylight.mdsal.common.api.CommitInfo.emptyFluentFuture; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.commons.io.FileUtils; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -41,7 +37,6 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.librar import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.ModuleBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.ModuleKey; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.YangIdentifier; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.yanglib.impl.rev141210.YanglibConfig; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.yanglib.impl.rev141210.YanglibConfigBuilder; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.Uint32; @@ -55,89 +50,56 @@ import org.opendaylight.yangtools.yang.parser.impl.DefaultYangParserFactory; @RunWith(MockitoJUnitRunner.StrictStubs.class) public class YangLibProviderTest { - private static final File CACHE_DIR = new File("target/yanglib"); - @Mock private DataBroker dataBroker; - @Mock private WriteTransaction writeTransaction; private YangLibProvider yangLibProvider; - @BeforeClass - public static void staticSetup() { - if (!CACHE_DIR.exists() && !CACHE_DIR.mkdirs()) { - throw new RuntimeException("Failed to create " + CACHE_DIR); - } - } - - @AfterClass - public static void staticCleanup() { - FileUtils.deleteQuietly(CACHE_DIR); - } - @Before public void setUp() { - try { - if (CACHE_DIR.exists()) { - FileUtils.cleanDirectory(CACHE_DIR); - } - } catch (IOException e) { - // Ignore - } - doReturn(emptyFluentFuture()).when(writeTransaction).commit(); doReturn(writeTransaction).when(dataBroker).newWriteOnlyTransaction(); - final YanglibConfig yanglibConfig = new YanglibConfigBuilder().setBindingAddr("www.fake.com") - .setBindingPort(Uint32.valueOf(300)).setCacheFolder(CACHE_DIR.getAbsolutePath()).build(); + final var yanglibConfig = new YanglibConfigBuilder().setBindingAddr("www.fake.com") + .setBindingPort(Uint32.valueOf(300)) + .setCacheFolder(YangLibProviderTest.class.getResource("/model").getPath()) + .build(); yangLibProvider = new YangLibProvider(yanglibConfig, dataBroker, new DefaultYangParserFactory()); + // this will automatically register all models from /model directory + yangLibProvider.init(); } @Test public void testSchemaSourceRegistered() { - yangLibProvider.init(); - - List> list = new ArrayList<>(); - list.add( - PotentialSchemaSource.create(new SourceIdentifier("no-revision"), - YangTextSchemaSource.class, PotentialSchemaSource.Costs.IMMEDIATE.getValue())); - - list.add( - PotentialSchemaSource.create(new SourceIdentifier("with-revision", "2016-04-28"), - YangTextSchemaSource.class, PotentialSchemaSource.Costs.IMMEDIATE.getValue())); - - yangLibProvider.schemaSourceRegistered(list); - - Map newModulesList = new HashMap<>(); - - Module newModule = new ModuleBuilder() - .setName(new YangIdentifier("no-revision")) - .setRevision(LegacyRevisionUtils.emptyRevision()) - .setSchema(new Uri("http://www.fake.com:300/yanglib/schemas/no-revision/")) - .build(); - - newModulesList.put(newModule.key(), newModule); - - newModule = new ModuleBuilder() - .setName(new YangIdentifier("with-revision")) - .setRevision(new Revision(new RevisionIdentifier("2016-04-28"))) - .setSchema(new Uri("http://www.fake.com:300/yanglib/schemas/with-revision/2016-04-28")) - .build(); - - newModulesList.put(newModule.key(), newModule); + // test that initial models are registered + final var newModulesMap = new HashMap(); + + final var model1 = new ModuleBuilder() + .setName(new YangIdentifier("model1")) + .setRevision(new Revision(new RevisionIdentifier("2023-02-21"))) + .setSchema(new Uri("http://www.fake.com:300/yanglib/schemas/model1/2023-02-21")) + .build(); + newModulesMap.put(model1.key(), model1); + + final var model2 = new ModuleBuilder() + .setName(new YangIdentifier("model2")) + .setRevision(LegacyRevisionUtils.emptyRevision()) + .setSchema(new Uri("http://www.fake.com:300/yanglib/schemas/model2")) + .build(); + newModulesMap.put(model2.key(), model2); verify(dataBroker).newWriteOnlyTransaction(); verify(writeTransaction).merge(eq(LogicalDatastoreType.OPERATIONAL), - eq(InstanceIdentifier.create(ModulesState.class)), - eq(new ModulesStateBuilder().setModule(newModulesList).build())); + eq(InstanceIdentifier.create(ModulesState.class)), + eq(new ModulesStateBuilder().setModule(newModulesMap).build())); verify(writeTransaction).commit(); } @Test public void testFilteringEmptySchemaSourceRegistered() { - yangLibProvider.init(); + clearInvocations(dataBroker, writeTransaction); // test empty list of schema sources registered yangLibProvider.schemaSourceRegistered(Collections.emptyList()); @@ -147,7 +109,7 @@ public class YangLibProviderTest { @Test public void testFilteringNonYangSchemaSourceRegistered() { - yangLibProvider.init(); + clearInvocations(dataBroker, writeTransaction); // test list of non yang schema sources registered final var nonYangSources = new ArrayList>(); @@ -163,7 +125,7 @@ public class YangLibProviderTest { @Test public void testSchemaSourceWithRevisionUnregistered() { - yangLibProvider.init(); + clearInvocations(dataBroker, writeTransaction); // try to unregister YANG source with revision final var schemaSourceWithRevision = PotentialSchemaSource.create( @@ -183,7 +145,7 @@ public class YangLibProviderTest { @Test public void testSchemaSourceWithoutRevisionUnregistered() { - yangLibProvider.init(); + clearInvocations(dataBroker, writeTransaction); // try to unregister YANG source without revision final var schemaSourceWithoutRevision = PotentialSchemaSource.create( @@ -203,7 +165,7 @@ public class YangLibProviderTest { @Test public void testNonYangSchemaSourceUnregistered() { - yangLibProvider.init(); + clearInvocations(dataBroker, writeTransaction); // try to unregister non-YANG source final var nonYangSources = PotentialSchemaSource.create(new SourceIdentifier("yin-source-representation"), @@ -213,4 +175,45 @@ public class YangLibProviderTest { // expected behaviour is to do nothing if non yang based source is unregistered verifyNoMoreInteractions(dataBroker, writeTransaction); } + + @Test + public void testGetSchemaWithRevision() { + final var modelWithRevision = yangLibProvider.getSchema("model1", "2023-02-21"); + assertNotNull(modelWithRevision); + assertEquals(""" + module model1 { + namespace "model:with:revision"; + prefix mwr; + + revision 2023-02-21 { + description + "Initial revision;"; + } + + container test { + leaf test-leaf { + type string; + } + } + } + """, modelWithRevision); + } + + @Test + public void testGetSchemaWithoutRevision() { + final var modelWithoutRevision = yangLibProvider.getSchema("model2"); + assertNotNull(modelWithoutRevision); + assertEquals(""" + module model2 { + namespace "model:with:no:revision"; + prefix mwnr; + + container test { + leaf test-leaf { + type string; + } + } + } + """, modelWithoutRevision); + } } diff --git a/netconf/yanglib/src/test/resources/model/model1@2023-02-21.yang b/netconf/yanglib/src/test/resources/model/model1@2023-02-21.yang new file mode 100644 index 0000000000..0a3f08a2d4 --- /dev/null +++ b/netconf/yanglib/src/test/resources/model/model1@2023-02-21.yang @@ -0,0 +1,15 @@ +module model1 { + namespace "model:with:revision"; + prefix mwr; + + revision 2023-02-21 { + description + "Initial revision;"; + } + + container test { + leaf test-leaf { + type string; + } + } +} diff --git a/netconf/yanglib/src/test/resources/model/model2.yang b/netconf/yanglib/src/test/resources/model/model2.yang new file mode 100644 index 0000000000..6c815501a5 --- /dev/null +++ b/netconf/yanglib/src/test/resources/model/model2.yang @@ -0,0 +1,10 @@ +module model2 { + namespace "model:with:no:revision"; + prefix mwnr; + + container test { + leaf test-leaf { + type string; + } + } +} -- 2.36.6