From ce23509536c00cb2649c36b4b64ac261946445a1 Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Mon, 14 Oct 2013 14:43:05 +0200 Subject: [PATCH] Updated RESTCONF implementation - Restconf implementation now implements new SAL readign contracts in form of where String is Restconf version of instance identifier. Translation of Restconf InstanceIdentifier into Binding-Independent Restconf Instance-Identifier is done by ControllerContext class which implements stateful translation of identifier based on schema context. After translation of instence-identifier most of actions maps directly to Binding-Independent APIs. Change-Id: I248470e282826a3f04f6e99f54aae8fd14ea2eb3 Signed-off-by: Tony Tkacik --- .../md-sal/sal-binding-broker/pom.xml | 1 - .../md-sal/sal-rest-connector/pom.xml | 80 +++++-- .../sal/restconf/impl/BrokerFacade.xtend | 65 ++++++ .../sal/restconf/impl/ControllerContext.xtend | 212 ++++++++++++++++++ .../sal/restconf/impl/MediaTypes.java | 17 ++ .../sal/restconf/impl/RestconfImpl.xtend | 41 ++++ .../sal/restconf/impl/RestconfService.java | 87 +++++++ 7 files changed, 482 insertions(+), 21 deletions(-) create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/MediaTypes.java create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfService.java diff --git a/opendaylight/md-sal/sal-binding-broker/pom.xml b/opendaylight/md-sal/sal-binding-broker/pom.xml index 34c56435d5..2c4f9c606d 100644 --- a/opendaylight/md-sal/sal-binding-broker/pom.xml +++ b/opendaylight/md-sal/sal-binding-broker/pom.xml @@ -19,7 +19,6 @@ org.apache.felix maven-bundle-plugin - ${bundle.plugin.version} true diff --git a/opendaylight/md-sal/sal-rest-connector/pom.xml b/opendaylight/md-sal/sal-rest-connector/pom.xml index bee4280a32..b28e9677b6 100644 --- a/opendaylight/md-sal/sal-rest-connector/pom.xml +++ b/opendaylight/md-sal/sal-rest-connector/pom.xml @@ -1,24 +1,64 @@ - 4.0.0 - - org.opendaylight.controller - sal-parent - 1.0-SNAPSHOT - - sal-rest-connector - - scm:git:ssh://git.opendaylight.org:29418/controller.git - scm:git:ssh://git.opendaylight.org:29418/controller.git - https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.opendaylight.controller + sal-parent + 1.0-SNAPSHOT + + sal-rest-connector + + scm:git:ssh://git.opendaylight.org:29418/controller.git + scm:git:ssh://git.opendaylight.org:29418/controller.git + https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL + - - - ${project.groupId} - sal-connector-api - - + + + ${project.groupId} + sal-core-api + 1.0-SNAPSHOT + + + ${project.groupId} + sal-connector-api + + + org.eclipse.xtend + org.eclipse.xtend.lib + + + org.jboss.resteasy + jaxrs-api + 3.0.4.Final + provided + + - bundle + + + + org.eclipse.xtend + xtend-maven-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + MD SAL Restconf Connector + + org.opendaylight.controller.sal.rest.*, + org.opendaylight.controller.sal.restconf.impl, + org.eclipse.xtend2.lib, + org.eclipse.xtend.lib, + org.eclipse.xtext.xbase.* + + + + + + + bundle diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend new file mode 100644 index 0000000000..4c6eb3b106 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend @@ -0,0 +1,65 @@ +package org.opendaylight.controller.sal.restconf.impl + +import org.opendaylight.controller.md.sal.common.api.data.DataReader +import java.net.URI +import org.opendaylight.yangtools.yang.data.api.CompositeNode +import org.opendaylight.controller.sal.core.api.data.DataBrokerService +import org.opendaylight.controller.sal.core.api.model.SchemaService +import static com.google.common.base.Preconditions.*; +import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession +import org.opendaylight.yangtools.yang.common.RpcResult +import org.opendaylight.controller.md.sal.common.api.data.DataModificationTransactionFactory + +class BrokerFacade implements DataReader { + + @Property + private ConsumerSession context; + + @Property + private DataBrokerService dataService; + + @Property + private SchemaService schemaService; + + @Property + private extension ControllerContext schemaContext; + + + def void init() { + checkState(dataService !== null) + checkState(schemaService !== null) + schemaContext = new ControllerContext(); + schemaContext.schemas = schemaService.globalContext; + } + + override readConfigurationData(String path) { + val processedPath = path.removePrefixes(); + return dataService.readConfigurationData(processedPath.toInstanceIdentifier); + } + + override readOperationalData(String path) { + val processedPath = path.removePrefixes(); + return dataService.readOperationalData(processedPath.toInstanceIdentifier); + } + + def RpcResult invokeRpc(String type,CompositeNode payload) { + val future = context.rpc(type.toRpcQName(),payload); + return future.get; + } + + def commitConfigurationDataUpdate(String path, CompositeNode payload) { + val transaction = dataService.beginTransaction; + transaction.putConfigurationData(path.toInstanceIdentifier,payload); + return transaction.commit() + } + + def commitConfigurationDataCreate(String path, CompositeNode payload) { + val transaction = dataService.beginTransaction; + transaction.putConfigurationData(path.toInstanceIdentifier,payload); + return transaction.commit() + } + + private def String removePrefixes(String path) { + return path; + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend new file mode 100644 index 0000000000..daad4cff4f --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend @@ -0,0 +1,212 @@ +package org.opendaylight.controller.sal.restconf.impl + +import org.opendaylight.yangtools.yang.model.api.SchemaContext +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates +import java.net.URI +import java.util.Map +import java.util.HashMap +import org.opendaylight.yangtools.yang.common.QName +import org.opendaylight.yangtools.yang.model.api.ChoiceNode +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode + +import java.net.URLEncoder +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder +import java.util.List +import static com.google.common.base.Preconditions.*; +import com.google.common.collect.Collections2 +import com.google.common.collect.BiMap +import com.google.common.collect.HashBiMap +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode +import java.net.URLDecoder + +class ControllerContext { + + @Property + SchemaContext schemas; + + private val BiMap uriToModuleName = HashBiMap.create(); + private val Map moduleNameToUri = uriToModuleName.inverse(); + + public def InstanceIdentifier toInstanceIdentifier(String restconfInstance) { + val ret = InstanceIdentifier.builder(); + val pathArgs = restconfInstance.split("/"); + val first = pathArgs.get(0); + val startModule = first.toModuleName(); + val module = schemas.findModuleByNamespace(moduleNameToUri.get(startModule)); + checkArgument(module.size == 1); // Only one version supported now + ret.collectPathArguments(pathArgs, module.iterator.next); + return ret.toInstance + } + + def String toFullRestconfIdentifier(InstanceIdentifier path) { + val elements = path.path; + val ret = new StringBuilder(); + val startQName = elements.get(0).nodeType; + val initialModule = schemas.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision) + var node = initialModule as DataSchemaNode; + for (element : elements) { + node = node.childByQName(element.nodeType); + ret.append(element.toRestconfIdentifier(node)); + } + return ret.toString + } + + private def dispatch CharSequence toRestconfIdentifier(NodeIdentifier argument, DataSchemaNode node) { + '''/«argument.nodeType.toRestconfIdentifier()»''' + } + + private def dispatch CharSequence toRestconfIdentifier(NodeIdentifierWithPredicates argument, ListSchemaNode node) { + val nodeIdentifier = argument.nodeType.toRestconfIdentifier(); + val keyValues = argument.keyValues; + return '''/«nodeIdentifier»/«FOR key : node.keyDefinition SEPARATOR "/"»«keyValues.get(key).toUriString»«ENDFOR»''' + } + + private def dispatch CharSequence toRestconfIdentifier(PathArgument argument, DataSchemaNode node) { + throw new IllegalArgumentException("Conversion of generic path argument is not supported"); + } + + public def CharSequence toRestconfIdentifier(QName qname) { + var module = uriToModuleName.get(qname.namespace) + if (module == null) { + val moduleSchema = schemas.findModuleByNamespaceAndRevision(qname.namespace, qname.revision); + if(moduleSchema == null) throw new IllegalArgumentException() + uriToModuleName.put(qname.namespace, moduleSchema.name) + module = moduleSchema.name; + } + return '''«module»:«qname.localName»'''; + } + + private static dispatch def DataSchemaNode childByQName(ChoiceNode container, QName name) { + for (caze : container.cases) { + val ret = caze.childByQName(name) + if (ret !== null) { + return ret; + } + } + return null; + } + + private static dispatch def DataSchemaNode childByQName(ChoiceCaseNode container, QName name) { + val ret = container.getDataChildByName(name); + return ret; + } + + private static dispatch def DataSchemaNode childByQName(ContainerSchemaNode container, QName name) { + return container.dataNodeChildByQName(name); + } + + private static dispatch def DataSchemaNode childByQName(ListSchemaNode container, QName name) { + return container.dataNodeChildByQName(name); + } + + private static dispatch def DataSchemaNode childByQName(DataSchemaNode container, QName name) { + return null; + } + + private static def DataSchemaNode dataNodeChildByQName(DataNodeContainer container, QName name) { + var ret = container.getDataChildByName(name); + if (ret === null) { + + // Find in Choice Cases + for (node : container.childNodes) { + if (node instanceof ChoiceCaseNode) { + val caseNode = (node as ChoiceCaseNode); + ret = caseNode.childByQName(name); + if (ret !== null) { + return ret; + } + } + } + } + return ret; + } + + private def toUriString(Object object) { + if(object == null) return ""; + return URLEncoder.encode(object.toString) + } + + def void collectPathArguments(InstanceIdentifierBuilder builder, List strings, DataNodeContainer parentNode) { + checkNotNull(strings) + if (strings.length == 0) { + return; + } + val nodeRef = strings.get(0); + + //val moduleName = nodeRef.toModuleName(); + val nodeName = nodeRef.toNodeName(); + val naiveTargetNode = parentNode.getDataChildByName(nodeName); + + //var URI namespace; + var DataSchemaNode targetNode = naiveTargetNode; + + /*if(moduleName !== null) { + namespace = moduleNameToUri.get(moduleName); + + }*/ + // Number of consumed elements + var consumed = 1; + if (targetNode instanceof ListSchemaNode) { + val listNode = targetNode as ListSchemaNode; + val keysSize = listNode.keyDefinition.size + val uriKeyValues = strings.subList(1, keysSize); + val keyValues = new HashMap(); + var i = 0; + for (key : listNode.keyDefinition) { + val uriKeyValue = uriKeyValues.get(i); + keyValues.addKeyValue(listNode.getDataChildByName(key), uriKeyValue); + i = i + 1; + } + consumed = consumed + i; + builder.nodeWithKey(targetNode.QName, keyValues); + } else { + + // Only one instance of node is allowed + builder.node(targetNode.QName); + } + if (targetNode instanceof DataNodeContainer) { + val remaining = strings.subList(consumed, strings.length - 1); + builder.collectPathArguments(remaining, targetNode as DataNodeContainer); + } + } + + def void addKeyValue(HashMap map, DataSchemaNode node, String uriValue) { + checkNotNull(uriValue); + checkArgument(node instanceof LeafSchemaNode); + val decoded = URLDecoder.decode(uriValue); + map.put(node.QName, decoded); + + } + + def String toModuleName(String str) { + if (str.contains(":")) { + val args = str.split(":"); + checkArgument(args.size === 2); + return args.get(0); + } else { + return null; + } + } + + def String toNodeName(String str) { + if (str.contains(":")) { + val args = str.split(":"); + checkArgument(args.size === 2); + return args.get(1); + } else { + return str; + } + } + + public def QName toRpcQName(String name) { + + + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/MediaTypes.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/MediaTypes.java new file mode 100644 index 0000000000..af18828bfb --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/MediaTypes.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2013 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.controller.sal.restconf.impl; + +public class MediaTypes { + public static final String API = "application/vnd.yang.api"; + public static final String DATASTORE = "application/vnd.yang.datastore"; + public static final String DATA = "application/vnd.yang.data"; + public static final String EVENT = "application/vnd.yang.event"; + public static final String OPERATION = "application/vnd.yang.operation"; + public static final String PATCH = "application/vnd.yang.patch"; +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend new file mode 100644 index 0000000000..4a23c76387 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend @@ -0,0 +1,41 @@ +package org.opendaylight.controller.sal.restconf.impl + +import org.opendaylight.yangtools.yang.data.api.CompositeNode + +class RestconfImpl implements RestconfService { + + @Property + BrokerFacade broker; + + override readAllData() { + return broker.readOperationalData(""); + } + + + override getModules() { + throw new UnsupportedOperationException("TODO: auto-generated method stub") + } + + override getRoot() { + throw new UnsupportedOperationException("TODO: auto-generated method stub") + + } + + override readData(String identifier) { + return broker.readOperationalData(identifier); + } + + override createConfigurationData(String identifier, CompositeNode payload) { + return broker.commitConfigurationDataCreate(identifier,payload); + } + + + override updateConfigurationData(String identifier, CompositeNode payload) { + return broker.commitConfigurationDataCreate(identifier,payload); + } + + override invokeRpc(String identifier, CompositeNode payload) { + return broker.invokeRpc(identifier,payload); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfService.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfService.java new file mode 100644 index 0000000000..beea6b8d93 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfService.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013 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.controller.sal.restconf.impl; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; + +import static org.opendaylight.controller.sal.restconf.impl.MediaTypes.*; + +/** + * The URI hierarchy for the RESTCONF resources consists of an entry + * point container, 3 top-level resources, and 1 field. Refer to + * Section 5 for details on each URI. + *
    + *
  • /restconf - {@link #getRoot()} + *
    • /datastore - {@link #readAllData()} + *
        + *
      • /(top-level-data-nodes) (config=true or false) + *
      + *
    • /modules + *
      • /module + *
      • /name + *
      • /revision + *
      • /namespace + *
      • /feature + *
      • /deviation + *
      + *
    • /operations + *
        + *
      • /(custom protocol operations) + *
      + *
    • /version (field) + *
    + */ +@Path("restconf") +public interface RestconfService { + + public static final String XML = "+xml"; + public static final String JSON = "+json"; + + @GET + public Object getRoot(); + + @GET + @Path("/datastore") + @Produces({API+JSON,API+XML}) + public Object readAllData(); + + @GET + @Path("/datastore/{identifier}") + @Produces({API+XML}) + public Object readData(@PathParam("identifier") String identifier); + + @PUT + @Path("/datastore/{identifier}") + @Produces({API+XML}) + public Object createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); + + @POST + @Path("/datastore/{identifier}") + @Produces({API+XML}) + public Object updateConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); + + + + @GET + @Path("/modules") + @Produces(API+XML) + public Object getModules(); + + @POST + @Path("/operations/{identifier}") + @Produces(API+XML) + public RpcResult invokeRpc(@PathParam("identifier") String identifier, CompositeNode payload); +} -- 2.36.6