Bug 2343 - Sideload of models for nodes from remote yang sources repository 30/37630/9
authorMaros Marsalek <mmarsale@cisco.com>
Sun, 19 Jul 2015 11:19:47 +0000 (13:19 +0200)
committerJakub Morvay <jmorvay@cisco.com>
Thu, 5 May 2016 14:14:52 +0000 (16:14 +0200)
Add support for sideloading schemas for devices from remote yang
schemas sources repositories.

Remote repository is queried for list of provided models and URLs
representing their schemas resources. These schemas are preregistered
to device schema registry as fallback schemas.

Change-Id: Iba3d77dd1a868a89a2f260ed751e36250fa30a47
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
Signed-off-by: Jakub Morvay <jmorvay@cisco.com>
21 files changed:
features/netconf/pom.xml
features/netconf/src/main/features/features.xml
netconf/models/ietf-netconf-yang-library/pom.xml [new file with mode: 0644]
netconf/models/ietf-netconf-yang-library/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/yang/library/rev160201/OptionalRevisionBuilder.java [new file with mode: 0644]
netconf/models/ietf-netconf-yang-library/src/main/yang/ietf-netconf-yang-library.yang [new file with mode: 0644]
netconf/models/pom.xml
netconf/netconf-artifacts/pom.xml
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java
netconf/sal-netconf-connector/pom.xml
netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/LibraryModulesSchemas.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/NetconfRemoteSchemaYangSourceProvider.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/YangLibrarySchemaYangSourceProvider.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/yang/netconf-node-topology.yang
netconf/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/LibraryModulesSchemasTest.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/YangLibrarySchemaYangSourceProviderTest.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/resources/yang-library-fail-completely.xml [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/resources/yang-library-fail.xml [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/resources/yang-library.json [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/resources/yang-library.xml [new file with mode: 0644]

index c82dc1c23a602530d8632f164a58a2cdfa38155c..c8c36a262909226dfb7c50fd4b79a8b58d21da56 100644 (file)
       <groupId>${project.groupId}</groupId>
       <artifactId>ietf-netconf-monitoring</artifactId>
     </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>ietf-netconf-yang-library</artifactId>
+    </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
       <artifactId>ietf-netconf-monitoring-extension</artifactId>
index ac0ad75e53a982c79f2e4be8140d91c04a38bfd2..493d6d97773b36d0f45e01d5ea82ad6cabd845ec 100644 (file)
@@ -31,6 +31,7 @@
     <bundle>mvn:org.opendaylight.netconf/ietf-netconf/{{VERSION}}</bundle>
     <bundle>mvn:org.opendaylight.netconf/ietf-netconf-notifications/{{VERSION}}</bundle>
     <bundle>mvn:org.opendaylight.netconf/ietf-netconf-monitoring-extension/{{VERSION}}</bundle>
+    <bundle>mvn:org.opendaylight.netconf/ietf-netconf-yang-library/{{VERSION}}</bundle>
     <feature version='${mdsal.model.version}'>odl-mdsal-models</feature>
   </feature>
 
diff --git a/netconf/models/ietf-netconf-yang-library/pom.xml b/netconf/models/ietf-netconf-yang-library/pom.xml
new file mode 100644 (file)
index 0000000..5c3fb74
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+  ~
+  ~ This program and the accompanying materials are made available under the
+  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+  ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.opendaylight.mdsal</groupId>
+        <artifactId>binding-parent</artifactId>
+        <version>0.9.0-SNAPSHOT</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.opendaylight.netconf</groupId>
+    <artifactId>ietf-netconf-yang-library</artifactId>
+    <version>1.1.0-SNAPSHOT</version>
+    <name>${project.artifactId}</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.mdsal.model</groupId>
+            <artifactId>ietf-yang-types-20130715</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal.model</groupId>
+            <artifactId>ietf-inet-types</artifactId>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/netconf/models/ietf-netconf-yang-library/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/yang/library/rev160201/OptionalRevisionBuilder.java b/netconf/models/ietf-netconf-yang-library/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/yang/library/rev160201/OptionalRevisionBuilder.java
new file mode 100644 (file)
index 0000000..653dc03
--- /dev/null
@@ -0,0 +1,19 @@
+package org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160201;
+
+
+/**
+ * The purpose of generated class in src/main/java for Union types is to create new instances of unions from a string representation.
+ * In some cases it is very difficult to automate it since there can be unions such as (uint32 - uint16), or (string - uint32).
+ *
+ * The reason behind putting it under src/main/java is:
+ * This class is generated in form of a stub and needs to be finished by the user. This class is generated only once to prevent
+ * loss of user code.
+ *
+ */
+public class OptionalRevisionBuilder {
+
+    public static OptionalRevision getDefaultInstance(java.lang.String defaultValue) {
+        throw new java.lang.UnsupportedOperationException("Not yet implemented");
+    }
+
+}
diff --git a/netconf/models/ietf-netconf-yang-library/src/main/yang/ietf-netconf-yang-library.yang b/netconf/models/ietf-netconf-yang-library/src/main/yang/ietf-netconf-yang-library.yang
new file mode 100644 (file)
index 0000000..ed6b2e3
--- /dev/null
@@ -0,0 +1,269 @@
+module ietf-yang-library {
+
+    yang-version 1;
+    namespace "urn:ietf:params:xml:ns:yang:ietf-yang-library";
+    prefix "yanglib";
+
+    import ietf-yang-types {
+      prefix yang;
+      revision-date "2013-07-15";
+    }
+
+    import ietf-inet-types {
+      prefix inet;
+      revision-date "2010-09-24";
+    }
+
+
+    organization
+      "IETF NETCONF (Network Configuration) Working Group";
+
+    contact
+      "WG Web:   <http://tools.ietf.org/wg/netconf/>
+      WG List:  <mailto:netconf@ietf.org>
+
+      WG Chair: Mehmet Ersue
+                <mailto:mehmet.ersue@nsn.com>
+
+      WG Chair: Mahesh Jethanandani
+                <mailto:mjethanandani@gmail.com>
+
+      Editor:   Andy Bierman
+                <mailto:andy@yumaworks.com>
+
+      Editor:   Martin Bjorklund
+                <mailto:mbj@tail-f.com>
+
+      Editor:   Kent Watsen
+                <mailto:kwatsen@juniper.net>";
+
+    description
+      "This module contains monitoring information about the YANG
+       modules and submodules that are used within a YANG-based
+       server.
+
+       Copyright (c) 2016 IETF Trust and the persons identified as
+       authors of the code.  All rights reserved.
+
+       Redistribution and use in source and binary forms, with or
+       without modification, is permitted pursuant to, and subject
+       to the license terms contained in, the Simplified BSD License
+       set forth in Section 4.c of the IETF Trust's Legal Provisions
+       Relating to IETF Documents
+       (http://trustee.ietf.org/license-info).
+
+       This version of this YANG module is part of RFC XXXX; see
+       the RFC itself for full legal notices.";
+
+       // RFC Ed.: replace XXXX with actual RFC number and remove this
+       // note.
+
+       // RFC Ed.: remove this note
+       // Note: extracted from draft-ietf-netconf-yang-library-04.txt
+
+       // RFC Ed.: update the date below with the date of RFC publication
+       // and remove this note.
+
+    revision 2016-02-01 {
+      description
+        "Initial revision.";
+      reference
+        "RFC XXXX: YANG Module Library.";
+    }
+
+    /*
+     * Typedefs
+     */
+    // FIXME inline this union after https://bugs.opendaylight.org/show_bug.cgi?id=5826 is fixed
+    typedef optional-revision {
+      type union {
+        type revision-identifier;
+        type string { length 0; }
+      }
+    }
+
+    typedef revision-identifier {
+      type string {
+        pattern '\d{4}-\d{2}-\d{2}';
+      }
+      description
+        "Represents a specific date in YYYY-MM-DD format.";
+    }
+
+    /*
+     * Groupings
+     */
+    grouping module-list {
+      description
+        "The module data structure is represented as a grouping
+         so it can be reused in configuration or another monitoring
+         data structure.";
+
+
+      grouping common-leafs {
+        description
+          "Common parameters for YANG modules and submodules.";
+
+        leaf name {
+          type yang:yang-identifier;
+          description
+            "The YANG module or submodule name.";
+        }
+        leaf revision {
+          type optional-revision;
+            description
+              "The YANG module or submodule revision date.
+               A zero-length string is used if no revision statement
+               is present in the YANG module or submodule.";
+        }
+      }
+
+      grouping schema-leaf {
+        description
+          "Common schema leaf parameter for modules and submodules.";
+
+        leaf schema {
+          type inet:uri;
+          description
+            "Contains a URL that represents the YANG schema
+             resource for this module or submodule.
+
+             This leaf will only be present if there is a URL
+             available for retrieval of the schema for this entry.";
+        }
+      }
+
+      list module {
+        key "name revision";
+        description
+          "Each entry represents one module currently
+           supported by the server.";
+
+        uses common-leafs;
+        uses schema-leaf;
+
+        leaf namespace {
+          type inet:uri;
+          mandatory true;
+          description
+            "The XML namespace identifier for this module.";
+        }
+        leaf-list feature {
+          type yang:yang-identifier;
+          description
+            "List of YANG feature names from this module that are
+             supported by the server, regardless whether they are
+             defined in the module or any included submodule.";
+        }
+        list deviation {
+          key "name revision";
+          description
+            "List of YANG deviation module names and revisions
+             used by this server to modify the conformance of
+             the module associated with this entry.  Note that
+             the same module can be used for deviations for
+             multiple modules, so the same entry MAY appear
+             within multiple 'module' entries.
+
+             The deviation module MUST be present in the 'module'
+             list, with the same name and revision values.
+             The 'conformance-type' value will be 'implement' for
+             the deviation module.";
+          uses common-leafs;
+        }
+        leaf conformance-type {
+          type enumeration {
+            enum implement {
+              description
+                "Indicates that the server implements one or more
+                 protocol-accessible objects defined in the YANG module
+                 identified in this entry.  This includes deviation
+                 statements defined in the module.
+
+                 For YANG version 1.1 modules, there is at most one
+                 module entry with conformance type 'implement' for a
+                 particular module name, since YANG 1.1 requires that
+                 at most one revision of a module is implemented.
+
+                 For YANG version 1 modules, there SHOULD NOT be more
+                 than one module entry for a particular module name.";
+            }
+            enum import {
+              description
+                "Indicates that the server imports reusable definitions
+                 from the specified revision of the module, but does
+                 not implement any protocol accessible objects from
+                 this revision.
+
+                 Multiple module entries for the same module name MAY
+                 exist. This can occur if multiple modules import the
+                 same module, but specify different revision-dates in
+                 the import statements.";
+            }
+          }
+          mandatory true;
+          description
+            "Indicates the type of conformance the server is claiming
+             for the YANG module identified by this entry.";
+        }
+        container submodules {
+          description
+            "Contains information about all the submodules used
+             by the parent module entry";
+
+          list submodule {
+            key "name revision";
+            description
+              "Each entry represents one submodule within the
+               parent module.";
+             uses common-leafs;
+             uses schema-leaf;
+          }
+        }
+      }
+    }
+
+    /*
+     * Operational state data nodes
+     */
+
+    container modules-state {
+      config false;
+      description
+        "Contains YANG module monitoring information.";
+
+      leaf module-set-id {
+        type string;
+        mandatory true;
+        description
+          "Contains a server-specific identifier representing
+           the current set of modules and submodules.  The
+           server MUST change the value of this leaf if the
+           information represented by the 'module' list instances
+           has changed.";
+      }
+
+      uses module-list;
+    }
+
+    /*
+     * Notifications
+     */
+    notification yang-library-change {
+      description
+        "Generated when the set of modules and submodules supported
+         by the server has changed.";
+      leaf module-set-id {
+        type leafref {
+          path "/yanglib:modules-state/yanglib:module-set-id";
+        }
+        mandatory true;
+        description
+          "Contains the module-set-id value representing the
+           set of modules and submodules supported at the server at
+           the time the notification is generated.";
+      }
+    }
+
+}
+
index a25a78a9b573f8d8045eaf27cf3df5d55eeb29e6..456d7cc6af88e2d2dee3a153cc8defd1e10b7274 100644 (file)
@@ -17,5 +17,6 @@
     <module>ietf-netconf-monitoring</module>
     <module>ietf-netconf-notifications</module>
     <module>ietf-netconf-monitoring-extension</module>
+    <module>ietf-netconf-yang-library</module>
   </modules>
 </project>
index f8dcad625bc820f24b38c778d17cb9f1cf625a4d..8cf36a33a8bf6c68b72dbfdf67e788988f78a490 100644 (file)
                 <artifactId>ietf-netconf-notifications</artifactId>
                 <version>${project.version}</version>
             </dependency>
+
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>ietf-netconf-yang-library</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>${project.groupId}</groupId>
                 <artifactId>netconf-notifications-api</artifactId>
index dfb9303b20ad280214d193725146f1701035c279..c09dca9162846b272b48845bd36a25502fc9c769 100644 (file)
@@ -11,6 +11,7 @@ package org.opendaylight.netconf.topology;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -18,6 +19,7 @@ import io.netty.util.concurrent.EventExecutor;
 import java.io.File;
 import java.math.BigDecimal;
 import java.net.InetSocketAddress;
+import java.net.URL;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -41,6 +43,7 @@ import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurati
 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
+import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfStateSchemas;
@@ -49,6 +52,7 @@ import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommun
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
+import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.netconf.topology.pipeline.TopologyMountPointFacade.ConnectionStatusListenerRegistration;
 import org.opendaylight.protocol.framework.ReconnectStrategy;
@@ -63,7 +67,10 @@ import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
 import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache;
 import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
@@ -274,6 +281,32 @@ public abstract class AbstractNetconfTopology implements NetconfTopology, Bindin
             salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay, defaultRequestTimeoutMillis);
         }
 
+        // pre register yang library sources as fallback schemas to schema registry
+        List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
+        if (node.getYangLibrary() != null) {
+            final String yangLibURL = node.getYangLibrary().getYangLibraryUrl().getValue();
+            final String yangLibUsername = node.getYangLibrary().getUsername();
+            final String yangLigPassword = node.getYangLibrary().getPassword();
+
+            LibraryModulesSchemas libraryModulesSchemas;
+            if(yangLibURL != null) {
+                if(yangLibUsername != null && yangLigPassword != null) {
+                    libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
+                } else {
+                    libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL);
+                }
+
+                for (Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry : libraryModulesSchemas.getAvailableModels().entrySet()) {
+                    registeredYangLibSources.
+                            add(schemaRegistry.registerSchemaSource(
+                                    new YangLibrarySchemaYangSourceProvider(remoteDeviceId, libraryModulesSchemas.getAvailableModels()),
+                                    PotentialSchemaSource
+                                            .create(sourceIdentifierURLEntry.getKey(), YangTextSchemaSource.class,
+                                            PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
+                }
+            }
+        }
+
         final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node);
 
         final NetconfDevice device = new NetconfDeviceBuilder()
index 40fe3b2f70a17303d8138ac24e84a130ecde168f..b4c915f44cdcbb1436899e9c066d39c9012901bd 100644 (file)
       <groupId>${project.groupId}</groupId>
       <artifactId>ietf-netconf-notifications</artifactId>
     </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>ietf-netconf-yang-library</artifactId>
+    </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
       <artifactId>netconf-client</artifactId>
       <groupId>org.opendaylight.yangtools</groupId>
       <artifactId>yang-parser-impl</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-data-codec-gson</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.opendaylight.mdsal.model</groupId>
       <artifactId>ietf-inet-types</artifactId>
       <groupId>org.hamcrest</groupId>
       <artifactId>hamcrest-core</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
   </dependencies>
 
   <scm>
index bbf55f9d378aad8c784195e0a89ff81ed2f1b086..f6c6f02443c90938cbc2da88acde99bffb1a8a9f 100644 (file)
@@ -12,10 +12,12 @@ import static org.opendaylight.controller.config.api.JmxAttributeValidationExcep
 
 import com.google.common.base.Optional;
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import io.netty.util.concurrent.EventExecutor;
 import java.io.File;
 import java.math.BigDecimal;
 import java.net.InetSocketAddress;
+import java.net.URL;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -34,6 +36,7 @@ import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurati
 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
+import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfStateSchemas;
@@ -43,6 +46,7 @@ import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceSalFacade;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
 import org.opendaylight.protocol.framework.ReconnectStrategy;
 import org.opendaylight.protocol.framework.ReconnectStrategyFactory;
 import org.opendaylight.protocol.framework.TimedReconnectStrategy;
@@ -55,6 +59,7 @@ import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
 import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache;
 import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
@@ -212,7 +217,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
         NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = null;
         final String moduleSchemaCacheDirectory = getSchemaCacheDirectory();
         // Only checks to ensure the String is not empty or null;  further checks related to directory accessibility and file permissions
-        // are handled during the FilesystemScehamSourceCache initialization.
+        // are handled during the FilesystemSchemaSourceCache initialization.
         if (!Strings.isNullOrEmpty(moduleSchemaCacheDirectory)) {
             // If a custom schema cache directory is specified, create the backing DTO; otherwise, the SchemaRegistry and
             // SchemaContextFactory remain the default values.
@@ -247,6 +252,32 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
                     instanceName, QUALIFIED_DEFAULT_CACHE_DIRECTORY);
         }
 
+        // pre register yang library sources as fallback schemas to schema registry
+        List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
+        if (getYangLibrary() != null) {
+            final String yangLibURL = getYangLibrary().getYangLibraryUrl().getValue();
+            final String yangLibUsername = getYangLibrary().getUsername();
+            final String yangLigPassword = getYangLibrary().getPassword();
+
+            LibraryModulesSchemas libraryModulesSchemas;
+            if(yangLibURL != null) {
+                if(yangLibUsername != null && yangLigPassword != null) {
+                    libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
+                } else {
+                    libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL);
+                }
+
+                for (Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry : libraryModulesSchemas.getAvailableModels().entrySet()) {
+                    registeredYangLibSources.
+                            add(schemaRegistry.registerSchemaSource(
+                                    new YangLibrarySchemaYangSourceProvider(id, libraryModulesSchemas.getAvailableModels()),
+                                    PotentialSchemaSource
+                                            .create(sourceIdentifierURLEntry.getKey(), YangTextSchemaSource.class,
+                                                    PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
+                }
+            }
+        }
+
         if (schemaResourcesDTO == null) {
             schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry, schemaContextFactory,
                     new NetconfStateSchemas.NetconfStateSchemasResolverImpl());
@@ -272,7 +303,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
         final NetconfReconnectingClientConfiguration clientConfig = getClientConfig(listener);
         listener.initializeRemoteConnection(clientDispatcher, clientConfig);
 
-        return new SalConnectorCloseable(listener, salFacade);
+        return new SalConnectorCloseable(listener, salFacade, registeredYangLibSources);
     }
 
     /**
@@ -379,16 +410,22 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
 
     private static final class SalConnectorCloseable implements AutoCloseable {
         private final RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
+        private final List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources;
         private final NetconfDeviceCommunicator listener;
 
         public SalConnectorCloseable(final NetconfDeviceCommunicator listener,
-                                     final RemoteDeviceHandler<NetconfSessionPreferences> salFacade) {
+                                     final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
+                                     final List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources) {
             this.listener = listener;
             this.salFacade = salFacade;
+            this.registeredYangLibSources = registeredYangLibSources;
         }
 
         @Override
         public void close() {
+            for (SchemaSourceRegistration<YangTextSchemaSource> registeredYangLibSource : registeredYangLibSources) {
+                registeredYangLibSource.close();
+            }
             listener.close();
             salFacade.close();
         }
diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/LibraryModulesSchemas.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/LibraryModulesSchemas.java
new file mode 100644 (file)
index 0000000..020b443
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf;
+
+import static javax.xml.bind.DatatypeConverter.printBase64Binary;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.Map;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160201.ModulesState;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160201.module.list.Module;
+import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
+
+/**
+ * Holds URLs with YANG schema resources for all yang modules reported in
+ * ietf-netconf-yang-library/modules-state/modules node
+ */
+public class LibraryModulesSchemas {
+
+    private static final Logger LOG = LoggerFactory.getLogger(LibraryModulesSchemas.class);
+
+    private static SchemaContext libraryContext;
+
+    private final Map<SourceIdentifier, URL> availableModels;
+
+    static {
+        final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
+        moduleInfoBackedContext.registerModuleInfo(
+                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160201.
+                        $YangModuleInfoImpl.getInstance());
+        libraryContext = moduleInfoBackedContext.tryToCreateSchemaContext().get();
+    }
+
+    private LibraryModulesSchemas(final Map<SourceIdentifier, URL> availableModels) {
+        this.availableModels = availableModels;
+    }
+
+    public Map<SourceIdentifier, URL> getAvailableModels() {
+        return availableModels;
+    }
+
+
+    /**
+     * Resolves URLs with YANG schema resources from modules-state. Uses basic http authenticaiton
+     * @param url URL pointing to yang library
+     * @return Resolved URLs with YANG schema resources for all yang modules from yang library
+     */
+    public static LibraryModulesSchemas create(final String url, final String username, final String password) {
+        Preconditions.checkNotNull(url);
+        try {
+            final URL urlConnection = new URL(url);
+            final URLConnection connection = urlConnection.openConnection();
+
+            if(connection instanceof HttpURLConnection) {
+                connection.setRequestProperty("Accept", "application/xml");
+                String userpass = username + ":" + password;
+                String basicAuth = "Basic " + printBase64Binary(userpass.getBytes());
+
+                connection.setRequestProperty("Authorization", basicAuth);
+            }
+
+            return createFromURLConnection(connection);
+
+        } catch (IOException e) {
+            LOG.warn("Unable to download yang library from {}", url, e);
+            return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
+        }
+    }
+
+
+    private static LibraryModulesSchemas createFromURLConnection(URLConnection connection) {
+
+        String contentType = connection.getContentType();
+
+        // TODO try to guess Json also from intput stream
+        if (guessJsonFromFileName(connection.getURL().getFile())) {
+            contentType = "application/json";
+        }
+
+        Preconditions.checkNotNull(contentType, "Content type unknown");
+        Preconditions.checkState(contentType.equals("application/json") || contentType.equals("application/xml"),
+                "Only XML and JSON types are supported.");
+        try (final InputStream in = connection.getInputStream()) {
+            final Optional<NormalizedNode<?, ?>> optionalModulesStateNode =
+                    contentType.equals("application/json") ? readJson(in) : readXml(in);
+
+            if (!optionalModulesStateNode.isPresent()) {
+                return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
+            }
+
+            final NormalizedNode<?, ?> modulesStateNode = optionalModulesStateNode.get();
+            Preconditions.checkState(modulesStateNode.getNodeType().equals(ModulesState.QNAME),
+                    "Wrong QName %s", modulesStateNode.getNodeType());
+            Preconditions.checkState(modulesStateNode instanceof ContainerNode,
+                    "Expecting container containing module list, but was %s", modulesStateNode);
+
+            final YangInstanceIdentifier.NodeIdentifier moduleListNodeId =
+                    new YangInstanceIdentifier.NodeIdentifier(Module.QNAME);
+            final Optional<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> moduleListNode =
+                    ((ContainerNode) modulesStateNode).getChild(moduleListNodeId);
+            Preconditions.checkState(moduleListNode.isPresent(),
+                    "Unable to find list: %s in %s", moduleListNodeId, modulesStateNode);
+            Preconditions.checkState(moduleListNode.get() instanceof MapNode,
+                    "Unexpected structure for container: %s in : %s. Expecting a list",
+                    moduleListNodeId, modulesStateNode);
+
+            final ImmutableMap.Builder<SourceIdentifier, URL> schemasMapping = new ImmutableMap.Builder<>();
+            for (final MapEntryNode moduleNode : ((MapNode) moduleListNode.get()).getValue()) {
+                final Optional<Map.Entry<SourceIdentifier, URL>> schemaMappingEntry = createFromEntry(moduleNode);
+                if (schemaMappingEntry.isPresent()) {
+                    schemasMapping.put(createFromEntry(moduleNode).get());
+                }
+            }
+
+            return new LibraryModulesSchemas(schemasMapping.build());
+        } catch (IOException e) {
+            LOG.warn("Unable to download yang library from {}", connection.getURL(), e);
+            return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
+        }
+    }
+
+    /**
+     * Resolves URLs with YANG schema resources from modules-state
+     * @param url URL pointing to yang library
+     * @return Resolved URLs with YANG schema resources for all yang modules from yang library
+     */
+    public static LibraryModulesSchemas create(final String url) {
+        Preconditions.checkNotNull(url);
+        try {
+            final URL urlConnection = new URL(url);
+            final URLConnection connection = urlConnection.openConnection();
+
+            if(connection instanceof HttpURLConnection) {
+                connection.setRequestProperty("Accept", "application/xml");
+            }
+
+            return createFromURLConnection(connection);
+
+        } catch (IOException e) {
+            LOG.warn("Unable to download yang library from {}", url, e);
+            return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
+        }
+    }
+
+    private static boolean guessJsonFromFileName(final String fileName) {
+        String extension = "";
+        final int i = fileName.lastIndexOf(46);
+        if(i != -1) {
+            extension = fileName.substring(i).toLowerCase();
+        }
+
+        return extension.equals(".json");
+    }
+
+    private static Optional<NormalizedNode<?, ?>> readJson(final InputStream in) {
+        final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+        final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+        final JsonParserStream jsonParser = JsonParserStream.create(writer, libraryContext);
+        final JsonReader reader = new JsonReader(new InputStreamReader(in));
+
+        jsonParser.parse(reader);
+
+        return resultHolder.isFinished() ?
+                Optional.of(resultHolder.getResult()) :
+                Optional.<NormalizedNode<?, ?>>absent();
+    }
+
+    private static Optional<NormalizedNode<?, ?>> readXml(final InputStream in) {
+        final DomToNormalizedNodeParserFactory parserFactory =
+                DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, libraryContext);
+
+        try {
+            final NormalizedNode<?, ?> parsed =
+                    parserFactory.getContainerNodeParser().parse(Collections.singleton(XmlUtil.readXmlToElement(in)),
+                            (ContainerSchemaNode) libraryContext.getDataChildByName(ModulesState.QNAME));
+            return Optional.of(parsed);
+        } catch (IOException|SAXException e) {
+            LOG.warn("Unable to parse yang library xml content", e);
+        }
+
+        return Optional.<NormalizedNode<?, ?>>absent();
+    }
+
+    private static Optional<Map.Entry<SourceIdentifier, URL>> createFromEntry(final MapEntryNode moduleNode) {
+        Preconditions.checkArgument(
+                moduleNode.getNodeType().equals(Module.QNAME), "Wrong QName %s", moduleNode.getNodeType());
+
+        YangInstanceIdentifier.NodeIdentifier childNodeId =
+                new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "name"));
+        final String moduleName = getSingleChildNodeValue(moduleNode, childNodeId).get();
+
+        childNodeId = new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "revision"));
+        final Optional<String> revision = getSingleChildNodeValue(moduleNode, childNodeId);
+        if(revision.isPresent()) {
+            if(!SourceIdentifier.REVISION_PATTERN.matcher(revision.get()).matches()) {
+                LOG.warn("Skipping library schema for {}. Revision {} is in wrong format.", moduleNode, revision.get());
+                return Optional.<Map.Entry<SourceIdentifier, URL>>absent();
+            }
+        }
+
+        // FIXME leaf schema with url that represents the yang schema resource for this module is not mandatory
+        // don't fail if schema node is not present, just skip the entry or add some default URL
+        childNodeId = new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "schema"));
+        final Optional<String> schemaUriAsString = getSingleChildNodeValue(moduleNode, childNodeId);
+
+
+        final SourceIdentifier sourceId = revision.isPresent()
+                ? new SourceIdentifier(moduleName, revision.get())
+                : new SourceIdentifier(moduleName);
+
+        try {
+            return Optional.<Map.Entry<SourceIdentifier, URL>>of(new AbstractMap.SimpleImmutableEntry<>(
+                    sourceId, new URL(schemaUriAsString.get())));
+        } catch (MalformedURLException e) {
+            LOG.warn("Skipping library schema for {}. URL {} representing yang schema resource is not valid",
+                    moduleNode, schemaUriAsString.get());
+            return Optional.<Map.Entry<SourceIdentifier, URL>>absent();
+        }
+    }
+
+    private static Optional<String> getSingleChildNodeValue(final DataContainerNode<?> schemaNode,
+                                                            final YangInstanceIdentifier.NodeIdentifier childNodeId) {
+        final Optional<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> node =
+                schemaNode.getChild(childNodeId);
+        Preconditions.checkArgument(node.isPresent(), "Child node %s not present", childNodeId.getNodeType());
+        return getValueOfSimpleNode(node.get());
+    }
+
+    private static Optional<String> getValueOfSimpleNode(
+            final NormalizedNode<? extends YangInstanceIdentifier.PathArgument, ?> node) {
+        final Object value = node.getValue();
+        return value == null || Strings.isNullOrEmpty(value.toString())
+                ? Optional.<String>absent() : Optional.of(value.toString().trim());
+    }
+
+}
index 72d2b391a2c32276768789774c9bddde053c455e..3a65ce74324f2b7c80e277085e2a86f4279e991a 100644 (file)
@@ -177,7 +177,7 @@ public final class NetconfRemoteSchemaYangSourceProvider implements SchemaSource
 
     }
 
-    private static class NetconfYangTextSchemaSource extends YangTextSchemaSource {
+    static class NetconfYangTextSchemaSource extends YangTextSchemaSource {
         private final RemoteDeviceId id;
         private final Optional<String> schemaString;
 
diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/YangLibrarySchemaYangSourceProvider.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/YangLibrarySchemaYangSourceProvider.java
new file mode 100644 (file)
index 0000000..0f27126
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf.schema;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Map;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides YANG schema sources from yang library
+ */
+public final class YangLibrarySchemaYangSourceProvider implements SchemaSourceProvider<YangTextSchemaSource> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(YangLibrarySchemaYangSourceProvider.class);
+
+    private final Map<SourceIdentifier, URL> availableSources;
+    private final RemoteDeviceId id;
+
+    public YangLibrarySchemaYangSourceProvider(
+            final RemoteDeviceId id, final Map<SourceIdentifier, URL> availableSources) {
+        this.id = id;
+        this.availableSources = Preconditions.checkNotNull(availableSources);
+    }
+
+    @Override
+    public CheckedFuture<? extends YangTextSchemaSource, SchemaSourceException> getSource(
+            final SourceIdentifier sourceIdentifier) {
+        Preconditions.checkNotNull(sourceIdentifier);
+        Preconditions.checkArgument(availableSources.containsKey(sourceIdentifier));
+        return download(sourceIdentifier);
+    }
+
+    private CheckedFuture<? extends YangTextSchemaSource, SchemaSourceException> download(final SourceIdentifier sId) {
+        final URL url = availableSources.get(sId);
+        try(final InputStream in = url.openStream()) {
+            final String schemaContent = new String(ByteStreams.toByteArray(in));
+            final NetconfRemoteSchemaYangSourceProvider.NetconfYangTextSchemaSource yangSource =
+                    new NetconfRemoteSchemaYangSourceProvider.
+                            NetconfYangTextSchemaSource(id, sId, Optional.of(schemaContent));
+            LOG.debug("Source {} downloaded from a yang library's url {}", sId, url);
+            return Futures.immediateCheckedFuture(yangSource);
+        } catch (IOException e) {
+            LOG.warn("Unable to download source {} from a yang library's url {}", sId, url, e);
+            return Futures.immediateFailedCheckedFuture(
+                    new SchemaSourceException("Unable to download remote schema for " + sId + " from " + url, e));
+        }
+    }
+}
index 141ff813b9446d995c0e4ee9ee03257dde9cf4cc..6dab3d6e1ae9afe0845ae44ae36230d7b5f10234 100644 (file)
@@ -188,6 +188,25 @@ module netconf-node-topology {
             description "The destination schema repository for yang files relative to the cache directory.  This may be specified per netconf mount
                          so that the loaded yang files are stored to a distinct directory to avoid potential conflict.";
         }
+
+        container yang-library {
+            leaf yang-library-url {
+                config true;
+                type inet:uri;
+                description "Yang library to be plugged as additional source provider into the shared schema repository";
+            }
+
+            // credentials for basic http authentication
+            leaf username {
+                config true;
+                type string;
+            }
+
+            leaf password {
+                config true;
+                type string;
+            }
+        }
     }
 
     grouping netconf-node-fields {
index 28c504f09315b9c9f7ccd66dc97885b8eb2e2d33..a7bb0827148efc97b99e6b96469c2086c382baa7 100644 (file)
@@ -190,6 +190,25 @@ module odl-sal-netconf-connector-cfg {
 
                 description "Dedicated solely to keepalive execution";
             }
+
+            container yang-library {
+                leaf yang-library-url {
+                    config true;
+                    type inet:uri;
+                    description "Yang library to be plugged as additional source provider into the shared schema repository";
+                }
+
+                // credentials for basic http authentication for get request for yanglib data
+                leaf username {
+                    config true;
+                    type string;
+                }
+
+                leaf password {
+                    config true;
+                    type string;
+                }
+            }
         }
     }
 }
diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/LibraryModulesSchemasTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/LibraryModulesSchemasTest.java
new file mode 100644 (file)
index 0000000..26e48c6
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf;
+
+import static org.hamcrest.CoreMatchers.is;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Map;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+
+public class LibraryModulesSchemasTest {
+
+    @Test
+    public void testCreate() throws Exception {
+        // test create from xml
+        LibraryModulesSchemas libraryModulesSchemas =
+                LibraryModulesSchemas.create(getClass().getResource("/yang-library.xml").toString());
+
+        verifySchemas(libraryModulesSchemas);
+
+        // test create from json
+        LibraryModulesSchemas libraryModuleSchemas =
+                LibraryModulesSchemas.create(getClass().getResource("/yang-library.json").toString());
+
+        verifySchemas(libraryModulesSchemas);
+    }
+
+
+    private void verifySchemas(final LibraryModulesSchemas libraryModulesSchemas) throws MalformedURLException {
+        final Map<SourceIdentifier, URL> resolvedModulesSchema = libraryModulesSchemas.getAvailableModels();
+        Assert.assertThat(resolvedModulesSchema.size(), is(3));
+
+        Assert.assertTrue(resolvedModulesSchema.containsKey(new SourceIdentifier("module-with-revision", "2014-04-08")));
+        Assert.assertThat(resolvedModulesSchema.get(
+                new SourceIdentifier("module-with-revision", "2014-04-08")),
+                is(new URL("http://localhost:8181/yanglib/schemas/module-with-revision/2014-04-08")));
+
+        Assert.assertTrue(resolvedModulesSchema.containsKey(
+                new SourceIdentifier("another-module-with-revision", "2013-10-21")));
+        Assert.assertThat(resolvedModulesSchema.get(
+                new SourceIdentifier("another-module-with-revision", "2013-10-21")),
+                is(new URL("http://localhost:8181/yanglib/schemas/another-module-with-revision/2013-10-21")));
+
+        Assert.assertTrue(resolvedModulesSchema.containsKey(new SourceIdentifier("module-without-revision")));
+        Assert.assertThat(resolvedModulesSchema.get(
+                new SourceIdentifier("module-without-revision")),
+                is(new URL("http://localhost:8181/yanglib/schemas/module-without-revision/")));
+    }
+
+    @Test
+    public void testCreateInvalidModulesEntries() throws Exception {
+        LibraryModulesSchemas libraryModulesSchemas =
+                LibraryModulesSchemas.create(getClass().getResource("/yang-library-fail.xml").toString());
+
+        final Map<SourceIdentifier, URL> resolvedModulesSchema = libraryModulesSchemas.getAvailableModels();
+        Assert.assertThat(resolvedModulesSchema.size(), is(1));
+
+        Assert.assertFalse(resolvedModulesSchema.containsKey(new SourceIdentifier("module-with-bad-url")));
+        Assert.assertFalse(resolvedModulesSchema.containsKey(
+                new SourceIdentifier("module-with-bad-revision", "bad-revision")));
+        Assert.assertTrue(resolvedModulesSchema.containsKey(new SourceIdentifier("good-ol-module")));
+    }
+
+
+    @Test
+    public void testCreateFromInvalidAll() throws Exception {
+        // test bad yang lib url
+        LibraryModulesSchemas libraryModulesSchemas = LibraryModulesSchemas.create("ObviouslyBadUrl");
+        Assert.assertThat(libraryModulesSchemas.getAvailableModels(), is(Collections.emptyMap()));
+
+        // TODO test also fail on json and xml parsing. But can we fail not on runtime exceptions?
+    }
+}
\ No newline at end of file
diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/YangLibrarySchemaYangSourceProviderTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/YangLibrarySchemaYangSourceProviderTest.java
new file mode 100644 (file)
index 0000000..15f0ab3
--- /dev/null
@@ -0,0 +1,58 @@
+package org.opendaylight.netconf.sal.connect.netconf.schema;
+
+import com.google.common.base.Optional;
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Map;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+
+public class YangLibrarySchemaYangSourceProviderTest {
+
+    private SourceIdentifier workingSid;
+    private YangLibrarySchemaYangSourceProvider yangLibrarySchemaYangSourceProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        final URL url = getClass().getResource("/schemas/config-test-rpc.yang");
+        workingSid = new SourceIdentifier("abc", Optional.<String>absent());
+        final Map<SourceIdentifier, URL> sourceIdentifierURLMap = Collections.singletonMap(workingSid, url);
+        final RemoteDeviceId id = new RemoteDeviceId("id", new InetSocketAddress("localhost", 22));
+        yangLibrarySchemaYangSourceProvider = new YangLibrarySchemaYangSourceProvider(id, sourceIdentifierURLMap);
+    }
+
+    @Test
+    public void testGetSource() throws Exception {
+        CheckedFuture<? extends YangTextSchemaSource, SchemaSourceException> source =
+                yangLibrarySchemaYangSourceProvider.getSource(workingSid);
+        final String x = new String(ByteStreams.toByteArray(source.checkedGet().openStream()));
+        Assert.assertThat(x, CoreMatchers.containsString("module config-test-rpc"));
+    }
+
+    @Test(expected = SchemaSourceException.class)
+    public void testGetSourceFailure() throws Exception {
+        final URL url = new URL("http://non-existing-entity.yang");
+        final Map<SourceIdentifier, URL> sourceIdentifierURLMap = Collections.singletonMap(workingSid, url);
+        final RemoteDeviceId id = new RemoteDeviceId("id", new InetSocketAddress("localhost", 22));
+        final YangLibrarySchemaYangSourceProvider failingYangLibrarySchemaYangSourceProvider =
+                new YangLibrarySchemaYangSourceProvider(id, sourceIdentifierURLMap);
+
+        CheckedFuture<? extends YangTextSchemaSource, SchemaSourceException> source =
+                failingYangLibrarySchemaYangSourceProvider.getSource(workingSid);
+        source.checkedGet();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetSourceNotAvailable() throws Exception {
+        yangLibrarySchemaYangSourceProvider.getSource(new SourceIdentifier("aaaaa", "0000-00-00"));
+    }
+}
\ No newline at end of file
diff --git a/netconf/sal-netconf-connector/src/test/resources/yang-library-fail-completely.xml b/netconf/sal-netconf-connector/src/test/resources/yang-library-fail-completely.xml
new file mode 100644 (file)
index 0000000..5a6fe21
--- /dev/null
@@ -0,0 +1,3 @@
+<modules-are-note-here xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+    <module-set-id>21429371628890</module-set-id>
+</modules-are-note-here>
\ No newline at end of file
diff --git a/netconf/sal-netconf-connector/src/test/resources/yang-library-fail.xml b/netconf/sal-netconf-connector/src/test/resources/yang-library-fail.xml
new file mode 100644 (file)
index 0000000..ef1feea
--- /dev/null
@@ -0,0 +1,18 @@
+<modules xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+    <module>
+        <name>module-with-bad-url</name>
+        <revision></revision>
+        <schema>
+            badurl
+        </schema>
+    </module>
+    <module>
+        <name>module-bad-revision</name>
+        <revision>bad-revision</revision>
+    </module>
+    <module>
+        <name>good-ol-module</name>
+        <revision></revision>
+        <schema>http://www.example.com</schema>
+    </module>
+</modules>
\ No newline at end of file
diff --git a/netconf/sal-netconf-connector/src/test/resources/yang-library.json b/netconf/sal-netconf-connector/src/test/resources/yang-library.json
new file mode 100644 (file)
index 0000000..d0f5552
--- /dev/null
@@ -0,0 +1,21 @@
+{
+  "ietf-yang-library:modules-state": {
+    "module": [
+      {
+        "name": "module-with-revision",
+        "revision": "2014-04-08",
+        "schema": "http://localhost:8181/yanglib/schemas/module-with-revision/2014-04-08"
+      },
+      {
+        "name": "another-module-with-revision",
+        "revision": "2014-06-08",
+        "schema": "http://localhost:8181/yanglib/schemas/another-module-with-revision/2013-10-21"
+      },
+      {
+        "name": "module-without-revision",
+        "revision": "",
+        "schema": "http://localhost:8181/yanglib/schemas/module-without-revision/2013-10-21"
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/netconf/sal-netconf-connector/src/test/resources/yang-library.xml b/netconf/sal-netconf-connector/src/test/resources/yang-library.xml
new file mode 100644 (file)
index 0000000..704ec67
--- /dev/null
@@ -0,0 +1,23 @@
+<modules xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+    <module>
+        <name>module-with-revision</name>
+        <revision>2014-04-08</revision>
+        <schema>
+            http://localhost:8181/yanglib/schemas/module-with-revision/2014-04-08
+        </schema>
+    </module>
+    <module>
+        <name>another-module-with-revision</name>
+        <revision>2013-10-21</revision>
+        <schema>
+            http://localhost:8181/yanglib/schemas/another-module-with-revision/2013-10-21
+        </schema>
+    </module>
+    <module>
+        <name>module-without-revision</name>
+        <revision></revision>
+        <schema>
+            http://localhost:8181/yanglib/schemas/module-without-revision/
+        </schema>
+    </module>
+</modules>
\ No newline at end of file