<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>
<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>
--- /dev/null
+<?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
--- /dev/null
+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");
+ }
+
+}
--- /dev/null
+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.";
+ }
+ }
+
+}
+
<module>ietf-netconf-monitoring</module>
<module>ietf-netconf-notifications</module>
<module>ietf-netconf-monitoring-extension</module>
+ <module>ietf-netconf-yang-library</module>
</modules>
</project>
<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>
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;
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;
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;
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;
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;
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()
<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>
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;
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;
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;
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;
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.
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());
final NetconfReconnectingClientConfiguration clientConfig = getClientConfig(listener);
listener.initializeRemoteConnection(clientDispatcher, clientConfig);
- return new SalConnectorCloseable(listener, salFacade);
+ return new SalConnectorCloseable(listener, salFacade, registeredYangLibSources);
}
/**
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();
}
--- /dev/null
+/*
+ * 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());
+ }
+
+}
}
- private static class NetconfYangTextSchemaSource extends YangTextSchemaSource {
+ static class NetconfYangTextSchemaSource extends YangTextSchemaSource {
private final RemoteDeviceId id;
private final Optional<String> schemaString;
--- /dev/null
+/*
+ * 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));
+ }
+ }
+}
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 {
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;
+ }
+ }
}
}
}
--- /dev/null
+/*
+ * 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
--- /dev/null
+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
--- /dev/null
+<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
--- /dev/null
+<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
--- /dev/null
+{
+ "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
--- /dev/null
+<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