From: Ed Warnicke Date: Fri, 10 Jan 2014 17:40:32 +0000 (+0000) Subject: Merge changes I85b49247,Icca28a4a X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~111 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=817e66a52d537af6127472fa6ca7b460ce30f938;hp=9edc56d3e129b0e80ff8efbf4c8d43b9aac217af;p=controller.git Merge changes I85b49247,Icca28a4a * changes: Added DELETE operation Changed POST operation --- diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java index 012b51fb5e..60a8f285a2 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java @@ -7,6 +7,8 @@ */ package org.opendaylight.controller.sal.rest.api; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -66,30 +68,50 @@ public interface RestconfService extends RestconfServiceLegacy { @Produces({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) + @Consumes({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, + Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, + MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) public StructuredData invokeRpc(@PathParam("identifier") String identifier, CompositeNode payload); + @POST + @Path("/operations/{identifier}") + @Produces({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, + Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, + MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) + public StructuredData invokeRpc(@PathParam("identifier") String identifier); + @GET @Path("/config/{identifier:.+}") @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) public StructuredData readConfigurationData(@PathParam("identifier") String identifier); - - @POST - @Path("/config/{identifier:.+}") + + @GET + @Path("/operational/{identifier:.+}") @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) - public Response createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); + public StructuredData readOperationalData(@PathParam("identifier") String identifier); @PUT @Path("/config/{identifier:.+}") - @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, + @Consumes({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) public Response updateConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); - @GET - @Path("/operational/{identifier:.+}") - @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, + @POST + @Path("/config/{identifier:.+}") + @Consumes({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) - public StructuredData readOperationalData(@PathParam("identifier") String identifier); + public Response createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); + + @POST + @Path("/config") + @Consumes({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML, + MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) + public Response createConfigurationData(CompositeNode payload); + + @DELETE + @Path("/config/{identifier:.+}") + public Response deleteConfigurationData(@PathParam("identifier") String identifier); } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java index 35da98b1a0..9b69c1f09a 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java @@ -1,5 +1,6 @@ package org.opendaylight.controller.sal.rest.api; +import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -33,14 +34,14 @@ public interface RestconfServiceLegacy { @Deprecated @POST @Path("/datastore/{identifier:.+}") - @Produces({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, + @Consumes({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) public Response createConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload); @Deprecated @PUT @Path("/datastore/{identifier:.+}") - @Produces({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, + @Consumes({Draft01.MediaTypes.DATA+JSON,Draft01.MediaTypes.DATA+XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML}) public Response updateConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload); diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java index 8b1bdef8ba..7f7e8606c3 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java @@ -194,7 +194,7 @@ class JsonMapper { if (node.getValue() instanceof QName) { IdentityValuesDTO valueDTO = (IdentityValuesDTO) RestCodec.from(baseType).serialize(node.getValue()); IdentityValue valueFromDTO = valueDTO.getValuesWithNamespaces().get(0); - String moduleName = ControllerContext.getInstance().findModuleByNamespace( + String moduleName = ControllerContext.getInstance().findModuleNameByNamespace( URI.create(valueFromDTO.getNamespace())); writer.value(moduleName + ":" + valueFromDTO.getValue()); } else { 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 index df6b58c897..343601865d 100644 --- 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 @@ -41,13 +41,13 @@ class BrokerFacade implements DataReader { override readConfigurationData(InstanceIdentifier path) { checkPreconditions - LOG.info("Read Configuration via Restconf: {}",path) + LOG.info("Read Configuration via Restconf: {}", path) return dataService.readConfigurationData(path); } override readOperationalData(InstanceIdentifier path) { checkPreconditions - LOG.info("Read Operational via Restconf: {}",path) + LOG.info("Read Operational via Restconf: {}", path) return dataService.readOperationalData(path); } @@ -60,15 +60,28 @@ class BrokerFacade implements DataReader { def commitConfigurationDataPut(InstanceIdentifier path, CompositeNode payload) { checkPreconditions val transaction = dataService.beginTransaction; + LOG.info("Put Configuration via Restconf: {}", path) transaction.putConfigurationData(path, payload); - return transaction.commit() + return transaction.commit } - def commitOperationalDataPut(InstanceIdentifier path, CompositeNode payload) { + def commitConfigurationDataPost(InstanceIdentifier path, CompositeNode payload) { checkPreconditions val transaction = dataService.beginTransaction; - transaction.putOperationalData(path, payload); - return transaction.commit() + transaction.putConfigurationData(path, payload); + if (payload == transaction.createdConfigurationData.get(path)) { + LOG.info("Post Configuration via Restconf: {}", path) + return transaction.commit + } + LOG.info("Post Configuration via Restconf was not executed because data already exists: {}", path) + return null; } - + + def commitConfigurationDataDelete(InstanceIdentifier path) { + checkPreconditions + val transaction = dataService.beginTransaction; + transaction.removeConfigurationData(path) + return transaction.commit + } + } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/CompositeNodeWrapper.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/CompositeNodeWrapper.java index 0ded60dae4..74a32d452e 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/CompositeNodeWrapper.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/CompositeNodeWrapper.java @@ -78,6 +78,11 @@ public final class CompositeNodeWrapper implements NodeWrapper, C Preconditions.checkState(compositeNode == null, "Data can be inconsistent."); return Collections.unmodifiableList(values); } + + @Override + public boolean isChangeAllowed() { + return compositeNode == null ? true : false; + } @Override public CompositeNode unwrap() { @@ -230,4 +235,5 @@ public final class CompositeNodeWrapper implements NodeWrapper, C public Set>>> entrySet() { return unwrap().entrySet(); } + } 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 index 1a60e14589..308975c8c5 100644 --- 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 @@ -12,6 +12,7 @@ import java.util.Map import java.util.concurrent.ConcurrentHashMap import javax.ws.rs.core.Response import org.opendaylight.controller.sal.core.api.model.SchemaServiceListener +import org.opendaylight.controller.sal.core.api.mount.MountService import org.opendaylight.controller.sal.rest.impl.RestUtil import org.opendaylight.controller.sal.rest.impl.RestconfProvider import org.opendaylight.yangtools.yang.common.QName @@ -26,16 +27,17 @@ import org.opendaylight.yangtools.yang.model.api.ChoiceNode import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode import org.opendaylight.yangtools.yang.model.api.DataNodeContainer import org.opendaylight.yangtools.yang.model.api.DataSchemaNode +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode import org.opendaylight.yangtools.yang.model.api.ListSchemaNode +import org.opendaylight.yangtools.yang.model.api.Module import org.opendaylight.yangtools.yang.model.api.RpcDefinition import org.opendaylight.yangtools.yang.model.api.SchemaContext import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition import org.slf4j.LoggerFactory import static com.google.common.base.Preconditions.* -import org.opendaylight.controller.sal.core.api.mount.MountService -import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode +import java.util.ArrayList class ControllerContext implements SchemaServiceListener { val static LOG = LoggerFactory.getLogger(ControllerContext) @@ -73,6 +75,7 @@ class ControllerContext implements SchemaServiceListener { } public def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) { + checkPreconditions val ret = InstanceIdentifier.builder(); val pathArgs = restconfInstance.split("/"); if (pathArgs.empty) { @@ -81,24 +84,28 @@ class ControllerContext implements SchemaServiceListener { if (pathArgs.head.empty) { pathArgs.remove(0) } - val schemaNode = ret.collectPathArguments(pathArgs, globalSchema.findModule(pathArgs.head)); + val mountPoints = new ArrayList + val schemaNode = ret.collectPathArguments(pathArgs, globalSchema.findModule(pathArgs.head), mountPoints); if (schemaNode === null) { return null } - return new InstanceIdWithSchemaNode(ret.toInstance, schemaNode) + return new InstanceIdWithSchemaNode(ret.toInstance, schemaNode, mountPoints.last) } - private static def findModule(SchemaContext context,String argument) { - //checkPreconditions + private def findModule(SchemaContext context,String argument) { checkNotNull(argument); val startModule = argument.toModuleName(); return context.getLatestModule(startModule) } - - static def getLatestModule(SchemaContext schema,String moduleName) { - checkArgument(schema != null); + + private def getLatestModule(SchemaContext schema,String moduleName) { + checkArgument(schema !== null); checkArgument(moduleName !== null && !moduleName.empty) val modules = schema.modules.filter[m|m.name == moduleName] + return modules.filterLatestModule + } + + private def filterLatestModule(Iterable modules) { var latestModule = modules.head for (module : modules) { if (module.revision.after(latestModule.revision)) { @@ -107,6 +114,31 @@ class ControllerContext implements SchemaServiceListener { } return latestModule } + + def findModuleByName(String moduleName) { + checkPreconditions + checkArgument(moduleName !== null && !moduleName.empty) + return globalSchema.getLatestModule(moduleName) + } + + def findModuleByName(String moduleName, InstanceIdentifier partialPath) { + checkArgument(moduleName !== null && !moduleName.empty && partialPath !== null && !partialPath.path.empty) + val mountPointSchema = mountService?.getMountPoint(partialPath)?.schemaContext; + return mountPointSchema?.getLatestModule(moduleName); + } + + def findModuleByNamespace(URI namespace) { + checkPreconditions + val moduleSchemas = globalSchema.findModuleByNamespace(namespace) + return moduleSchemas?.filterLatestModule + } + + def findModuleByNamespace(URI namespace, InstanceIdentifier partialPath) { + checkArgument(namespace !== null && !namespace.toString.empty && partialPath !== null && !partialPath.path.empty) + val mountPointSchema = mountService?.getMountPoint(partialPath)?.schemaContext; + val moduleSchemas = mountPointSchema?.findModuleByNamespace(namespace) + return moduleSchemas?.filterLatestModule + } def String toFullRestconfIdentifier(InstanceIdentifier path) { checkPreconditions @@ -136,18 +168,13 @@ class ControllerContext implements SchemaServiceListener { throw new IllegalArgumentException("Conversion of generic path argument is not supported"); } - def findModuleByNamespace(URI namespace) { + def findModuleNameByNamespace(URI namespace) { checkPreconditions var module = uriToModuleName.get(namespace) if (module === null) { val moduleSchemas = globalSchema.findModuleByNamespace(namespace); if(moduleSchemas === null) return null - var latestModule = moduleSchemas.head - for (m : moduleSchemas) { - if (m.revision.after(latestModule.revision)) { - latestModule = m - } - } + var latestModule = moduleSchemas.filterLatestModule if(latestModule === null) return null uriToModuleName.put(namespace, latestModule.name) module = latestModule.name; @@ -155,16 +182,10 @@ class ControllerContext implements SchemaServiceListener { return module } - def findNamespaceByModule(String module) { + def findNamespaceByModuleName(String module) { var namespace = moduleNameToUri.get(module) if (namespace === null) { - val moduleSchemas = globalSchema.modules.filter[it|it.name.equals(module)] - var latestModule = moduleSchemas.head - for (m : moduleSchemas) { - if (m.revision.after(latestModule.revision)) { - latestModule = m - } - } + var latestModule = globalSchema.getLatestModule(module) if(latestModule === null) return null namespace = latestModule.namespace uriToModuleName.put(namespace, latestModule.name) @@ -235,7 +256,7 @@ class ControllerContext implements SchemaServiceListener { } private def DataSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List strings, - DataNodeContainer parentNode) { + DataNodeContainer parentNode, List mountPoints) { checkNotNull(strings) if (parentNode === null) { return null; @@ -255,8 +276,12 @@ class ControllerContext implements SchemaServiceListener { // Node is possibly in other mount point val partialPath = builder.toInstance; val mountPointSchema = mountService?.getMountPoint(partialPath)?.schemaContext; - if(mountPointSchema != null) { - return builder.collectPathArguments(strings, mountPointSchema.findModule(strings.head)); + if(mountPointSchema !== null) { + val module = mountPointSchema.findModule(strings.head) + if (module !== null) { + mountPoints.add(partialPath) + } + return builder.collectPathArguments(strings, module, mountPoints); } return null } @@ -294,7 +319,7 @@ class ControllerContext implements SchemaServiceListener { } if (targetNode instanceof DataNodeContainer) { val remaining = strings.subList(consumed, strings.length); - val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer); + val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoints); return result } @@ -310,7 +335,7 @@ class ControllerContext implements SchemaServiceListener { val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten for (caze : allCases) { potentialNode = caze.findInstanceDataChild(name); - if(potentialNode != null) { + if(potentialNode !== null) { return potentialNode; } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/EmptyNodeWrapper.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/EmptyNodeWrapper.java index c146954dce..cdb9599a46 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/EmptyNodeWrapper.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/EmptyNodeWrapper.java @@ -64,6 +64,11 @@ public final class EmptyNodeWrapper implements NodeWrapper>, Node this.namespace = namespace; } + @Override + public boolean isChangeAllowed() { + return unwrapped == null ? true : false; + } + @Override public Node unwrap() { if (unwrapped == null) { diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/InstanceIdWithSchemaNode.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/InstanceIdWithSchemaNode.java index ad0654af78..ba0e47ff92 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/InstanceIdWithSchemaNode.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/InstanceIdWithSchemaNode.java @@ -7,10 +7,12 @@ public class InstanceIdWithSchemaNode { private final InstanceIdentifier instanceIdentifier; private final DataSchemaNode schemaNode; + private final InstanceIdentifier mountPoint; - public InstanceIdWithSchemaNode(InstanceIdentifier instanceIdentifier, DataSchemaNode schemaNode) { + public InstanceIdWithSchemaNode(InstanceIdentifier instanceIdentifier, DataSchemaNode schemaNode, InstanceIdentifier mountPoint) { this.instanceIdentifier = instanceIdentifier; this.schemaNode = schemaNode; + this.mountPoint = mountPoint; } public InstanceIdentifier getInstanceIdentifier() { @@ -21,4 +23,8 @@ public class InstanceIdWithSchemaNode { return schemaNode; } + public InstanceIdentifier getMountPoint() { + return mountPoint; + } + } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NodeWrapper.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NodeWrapper.java index 675e119439..6b8665f765 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NodeWrapper.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NodeWrapper.java @@ -11,6 +11,8 @@ public interface NodeWrapper> { T unwrap(); + boolean isChangeAllowed(); + URI getNamespace(); void setNamespace(URI namespace); diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestCodec.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestCodec.java index 40fba88356..45f3f7f30b 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestCodec.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestCodec.java @@ -107,7 +107,7 @@ public class RestCodec { public QName deserialize(IdentityValuesDTO data) { IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0); String namespace = valueWithNamespace.getNamespace(); - URI validNamespace = ControllerContext.getInstance().findNamespaceByModule(namespace); + URI validNamespace = ControllerContext.getInstance().findNamespaceByModuleName(namespace); if (validNamespace == null) { validNamespace = URI.create(namespace); } 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 index 4645a411c1..a65c0ff97a 100644 --- 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 @@ -1,6 +1,7 @@ package org.opendaylight.controller.sal.restconf.impl import java.util.ArrayList +import java.util.HashMap import java.util.List import java.util.Set import javax.ws.rs.core.Response @@ -8,6 +9,8 @@ import org.opendaylight.controller.md.sal.common.api.TransactionStatus import org.opendaylight.controller.sal.rest.api.RestconfService import org.opendaylight.yangtools.yang.common.QName import org.opendaylight.yangtools.yang.data.api.CompositeNode +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder import org.opendaylight.yangtools.yang.data.api.Node import org.opendaylight.yangtools.yang.data.impl.NodeFactory import org.opendaylight.yangtools.yang.model.api.ChoiceNode @@ -17,10 +20,12 @@ import org.opendaylight.yangtools.yang.model.api.DataSchemaNode import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode import org.opendaylight.yangtools.yang.model.api.ListSchemaNode +import org.opendaylight.yangtools.yang.model.api.Module import org.opendaylight.yangtools.yang.model.api.TypeDefinition import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition import static javax.ws.rs.core.Response.Status.* +import org.opendaylight.yangtools.yang.model.api.RpcDefinition class RestconfImpl implements RestconfService { @@ -55,41 +60,27 @@ class RestconfImpl implements RestconfService { return null; } - override readData(String identifier) { - val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier - val data = broker.readOperationalData(instanceIdentifierWithSchemaNode.getInstanceIdentifier); - return new StructuredData(data, instanceIdentifierWithSchemaNode.schemaNode) - } - - override createConfigurationData(String identifier, CompositeNode payload) { - val identifierWithSchemaNode = identifier.resolveInstanceIdentifier - val value = normalizeNode(payload, identifierWithSchemaNode.schemaNode) - val status = broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier, value).get(); - switch status.result { - case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build - default: Response.status(INTERNAL_SERVER_ERROR).build - } + override invokeRpc(String identifier, CompositeNode payload) { + return callRpc(identifier.rpcDefinition, payload) } - - override updateConfigurationData(String identifier, CompositeNode payload) { - val identifierWithSchemaNode = identifier.resolveInstanceIdentifier - val value = normalizeNode(payload, identifierWithSchemaNode.schemaNode) - val status = broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier, value).get(); - switch status.result { - case TransactionStatus.COMMITED: Response.status(OK).build - default: Response.status(INTERNAL_SERVER_ERROR).build - } + + override invokeRpc(String identifier) { + return callRpc(identifier.rpcDefinition, null) } - - override invokeRpc(String identifier, CompositeNode payload) { - val rpc = identifier.rpcDefinition + + private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) { if (rpc === null) { throw new ResponseException(NOT_FOUND, "RPC does not exist."); } - val value = normalizeNode(payload, rpc.input) - val List> input = new ArrayList - input.add(value) - val rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null) + var CompositeNode rpcRequest; + if (payload === null) { + rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null) + } else { + val value = normalizeNode(payload, rpc.input, null) + val List> input = new ArrayList + input.add(value) + rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null) + } val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest); if (!rpcResult.successful) { throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed") @@ -100,6 +91,12 @@ class RestconfImpl implements RestconfService { return new StructuredData(rpcResult.result, rpc.output) } + override readData(String identifier) { + val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier + val data = broker.readOperationalData(instanceIdentifierWithSchemaNode.getInstanceIdentifier); + return new StructuredData(data, instanceIdentifierWithSchemaNode.schemaNode) + } + override readConfigurationData(String identifier) { val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier val data = broker.readConfigurationData(instanceIdentifierWithSchemaNode.getInstanceIdentifier); @@ -116,10 +113,61 @@ class RestconfImpl implements RestconfService { updateConfigurationData(identifier, payload); } + override updateConfigurationData(String identifier, CompositeNode payload) { + val identifierWithSchemaNode = identifier.resolveInstanceIdentifier + val value = normalizeNode(payload, identifierWithSchemaNode.schemaNode, identifierWithSchemaNode.mountPoint) + val status = broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier, value).get(); + switch status.result { + case TransactionStatus.COMMITED: Response.status(OK).build + default: Response.status(INTERNAL_SERVER_ERROR).build + } + } + override createConfigurationDataLegacy(String identifier, CompositeNode payload) { createConfigurationData(identifier, payload); } + override createConfigurationData(String identifier, CompositeNode payload) { + val uncompleteIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier + var schemaNode = (uncompleteIdentifierWithSchemaNode.schemaNode as DataNodeContainer).getSchemaChildNode(payload) + if (schemaNode === null) { + schemaNode = payload.findModule(uncompleteIdentifierWithSchemaNode.instanceIdentifier)?.getSchemaChildNode(payload) + } + val value = normalizeNode(payload, schemaNode, uncompleteIdentifierWithSchemaNode.instanceIdentifier) + val completeIdentifierWithSchemaNode = uncompleteIdentifierWithSchemaNode.addLastIdentifierFromData(value, schemaNode) + val status = broker.commitConfigurationDataPost(completeIdentifierWithSchemaNode.instanceIdentifier, value)?.get(); + if (status === null) { + return Response.status(ACCEPTED).build + } + switch status.result { + case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build + default: Response.status(INTERNAL_SERVER_ERROR).build + } + } + + override createConfigurationData(CompositeNode payload) { + val schemaNode = payload.findModule(null)?.getSchemaChildNode(payload) + val value = normalizeNode(payload, schemaNode, null) + val identifierWithSchemaNode = addLastIdentifierFromData(null, value, schemaNode) + val status = broker.commitConfigurationDataPost(identifierWithSchemaNode.instanceIdentifier, value)?.get(); + if (status === null) { + return Response.status(ACCEPTED).build + } + switch status.result { + case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build + default: Response.status(INTERNAL_SERVER_ERROR).build + } + } + + override deleteConfigurationData(String identifier) { + val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier + val status = broker.commitConfigurationDataDelete(instanceIdentifierWithSchemaNode.getInstanceIdentifier).get; + switch status.result { + case TransactionStatus.COMMITED: Response.status(OK).build + default: Response.status(INTERNAL_SERVER_ERROR).build + } + } + private def InstanceIdWithSchemaNode resolveInstanceIdentifier(String identifier) { val identifierWithSchemaNode = identifier.toInstanceIdentifier if (identifierWithSchemaNode === null) { @@ -128,18 +176,89 @@ class RestconfImpl implements RestconfService { return identifierWithSchemaNode } - private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema) { + private def dispatch Module findModule(CompositeNode data, InstanceIdentifier partialPath) { + if (partialPath !== null && !partialPath.path.empty) { + return data.nodeType.namespace.findModuleByNamespace(partialPath) + } else { + return data.nodeType.namespace.findModuleByNamespace + } + } + + private def dispatch Module findModule(CompositeNodeWrapper data, InstanceIdentifier partialPath) { + var Module module = null; + if (partialPath !== null && !partialPath.path.empty) { + module = data.namespace.findModuleByNamespace(partialPath) // namespace from XML + if (module === null) { + module = data.namespace.toString.findModuleByName(partialPath) // namespace (module name) from JSON + } + } else { + module = data.namespace.findModuleByNamespace // namespace from XML + if (module === null) { + module = data.namespace.toString.findModuleByName // namespace (module name) from JSON + } + } + return module + } + + private def dispatch DataSchemaNode getSchemaChildNode(DataNodeContainer parentSchemaNode, CompositeNode data) { + return parentSchemaNode?.getDataChildByName(data.nodeType.localName) + } + + private def dispatch DataSchemaNode getSchemaChildNode(DataNodeContainer parentSchemaNode, CompositeNodeWrapper data) { + return parentSchemaNode?.getDataChildByName(data.localName) + } + + private def InstanceIdWithSchemaNode addLastIdentifierFromData(InstanceIdWithSchemaNode identifierWithSchemaNode, CompositeNode data, DataSchemaNode schemaOfData) { + val iiOriginal = identifierWithSchemaNode?.instanceIdentifier + var InstanceIdentifierBuilder iiBuilder = null + if (iiOriginal === null) { + iiBuilder = InstanceIdentifier.builder + } else { + iiBuilder = InstanceIdentifier.builder(iiOriginal) + } + + if (schemaOfData instanceof ListSchemaNode) { + iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data)) + } else { + iiBuilder.node(schemaOfData.QName) + } + return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint) + } + + private def resolveKeysFromData(ListSchemaNode listNode, CompositeNode dataNode) { + val keyValues = new HashMap(); + for (key : listNode.keyDefinition) { + val dataNodeKeyValueObject = dataNode.getSimpleNodesByName(key.localName)?.head?.value + if (dataNodeKeyValueObject === null) { + throw new ResponseException(BAD_REQUEST, "Data contains list \"" + dataNode.nodeType.localName + "\" which does not contain key: \"" + key.localName + "\"") + } + keyValues.put(key, dataNodeKeyValueObject); + } + return keyValues + } + + private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema, InstanceIdentifier mountPoint) { + if (schema !== null && !schema.containerOrList) { + throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype."); + } if (node instanceof CompositeNodeWrapper) { - normalizeNode(node as CompositeNodeWrapper, schema, null) + if ((node as CompositeNodeWrapper).changeAllowed) { + normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint) + } return (node as CompositeNodeWrapper).unwrap() } return node } - private def void normalizeNode(NodeWrapper nodeBuilder, DataSchemaNode schema, QName previousAugment) { + private def isContainerOrList(DataSchemaNode schemaNode) { + return (schemaNode instanceof ContainerSchemaNode) || (schemaNode instanceof ListSchemaNode) + } + + private def void normalizeNode(NodeWrapper nodeBuilder, DataSchemaNode schema, QName previousAugment, + InstanceIdentifier mountPoint) { if (schema === null) { throw new ResponseException(BAD_REQUEST, - "Data has bad format\n" + nodeBuilder.localName + " does not exist in yang schema."); + "Data has bad format.\n\"" + nodeBuilder.localName + "\" does not exist in yang schema."); } var validQName = schema.QName var currentAugment = previousAugment; @@ -148,15 +267,18 @@ class RestconfImpl implements RestconfService { } else if (previousAugment !== null && schema.QName.namespace !== previousAugment.namespace) { validQName = QName.create(currentAugment, schema.QName.localName); } - val moduleName = controllerContext.findModuleByNamespace(validQName.namespace); + var moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace); + if (moduleName === null && mountPoint !== null && !mountPoint.path.empty) { + moduleName = controllerContext.findModuleByNamespace(validQName.namespace, mountPoint)?.name + } if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace || nodeBuilder.namespace.toString == moduleName) { nodeBuilder.qname = validQName } else { throw new ResponseException(BAD_REQUEST, - "Data has bad format\nIf data is in XML format then namespace for " + nodeBuilder.localName + - " should be " + schema.QName.namespace + ".\n If data is in Json format then module name for " + - nodeBuilder.localName + " should be " + moduleName + "."); + "Data has bad format.\nIf data is in XML format then namespace for \"" + nodeBuilder.localName + + "\" should be \"" + schema.QName.namespace + "\".\nIf data is in Json format then module name for \"" + + nodeBuilder.localName + "\" should be \"" + moduleName + "\"."); } if (nodeBuilder instanceof CompositeNodeWrapper) { @@ -164,7 +286,7 @@ class RestconfImpl implements RestconfService { for (child : children) { normalizeNode(child, findFirstSchemaByLocalName(child.localName, (schema as DataNodeContainer).childNodes), - currentAugment) + currentAugment, mountPoint) } if(schema instanceof ListSchemaNode) { val listKeys = (schema as ListSchemaNode).keyDefinition @@ -177,7 +299,7 @@ class RestconfImpl implements RestconfService { } if (!foundKey) { throw new ResponseException(BAD_REQUEST, - "Missing key \"" + listKey.localName + "\" of list \"" + schema.QName.localName + "\"") + "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName + "\"") } } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/SimpleNodeWrapper.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/SimpleNodeWrapper.java index 97f8102127..6aa8ada5ee 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/SimpleNodeWrapper.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/SimpleNodeWrapper.java @@ -58,6 +58,11 @@ public final class SimpleNodeWrapper implements NodeWrapper>, Simp this.namespace = namespace; } + @Override + public boolean isChangeAllowed() { + return simpleNode == null ? true : false; + } + @Override public SimpleNode unwrap() { if (simpleNode == null) { diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java index f2c0c29bc4..afe458d6e0 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java @@ -13,7 +13,6 @@ import javax.ws.rs.WebApplicationException; import org.junit.Test; import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider; import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper; -import org.opendaylight.controller.sal.restconf.impl.ResponseException; import org.opendaylight.controller.sal.restconf.impl.test.TestUtils; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.CompositeNode; @@ -207,15 +206,8 @@ public class JsonToCnSnTest { assertEquals("lst", compNode.getNodeType().getLocalName()); verifyCompositeNode(compNode, "simple:list:yang1"); - String exceptionMessage = ""; - try { - TestUtils.normalizeCompositeNode(compositeNode, modules2, "simple-list-yang2:lst"); - } catch (ResponseException e) { - exceptionMessage = String.valueOf(e.getResponse().getEntity()); - } - assertTrue(exceptionMessage - .contains("Data has bad format\nIf data is in XML format then namespace for lst should be simple:list:yang2.\n If data is in Json format then module name for lst should be simple-list-yang2.")); - + TestUtils.normalizeCompositeNode(compositeNode, modules2, "simple-list-yang2:lst"); + verifyCompositeNode(compNode, "simple:list:yang1"); } @Test diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java index 2370035861..c5f5a1eddc 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java @@ -60,7 +60,7 @@ public class InvokeRpcMethodTest { ControllerContext contContext = ControllerContext.getInstance(); contContext.onGlobalContextUpdated(TestUtils.loadSchemaContext(modules)); try { - contContext.findModuleByNamespace(new URI("invoke:rpc:module")); + contContext.findModuleNameByNamespace(new URI("invoke:rpc:module")); } catch (URISyntaxException e) { assertTrue("Uri wasn't created sucessfuly", false); } diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java new file mode 100644 index 0000000000..83e6ae51d0 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java @@ -0,0 +1,83 @@ +package org.opendaylight.controller.sal.restconf.impl.test; + +import static org.junit.Assert.*; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper; +import org.opendaylight.controller.sal.restconf.impl.ResponseException; +import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; + +public class NormalizeNodeTest extends YangAndXmlAndDataSchemaLoader { + + @BeforeClass + public static void initialization() { + dataLoad("/normalize-node/yang/"); + } + + @Test + public void namespaceNotNullAndInvalidNamespaceAndNoModuleNameTest() { + boolean exceptionReised = false; + try { + TestUtils.normalizeCompositeNode(prepareCnSn("wrongnamespace"), modules, schemaNodePath); + } catch (ResponseException e) { + exceptionReised = true; + } + assertTrue(exceptionReised); + } + + @Test + public void namespaceNullTest() { + String exceptionMessage = null; + try { + TestUtils.normalizeCompositeNode(prepareCnSn(null), modules, schemaNodePath); + } catch (ResponseException e) { + exceptionMessage = String.valueOf(e.getResponse().getEntity()); + } + assertNull(exceptionMessage); + } + + @Test + public void namespaceValidNamespaceTest() { + String exceptionMessage = null; + try { + TestUtils.normalizeCompositeNode(prepareCnSn("normalize:node:module"), modules, schemaNodePath); + } catch (ResponseException e) { + exceptionMessage = String.valueOf(e.getResponse().getEntity()); + } + assertNull(exceptionMessage); + } + + @Test + public void namespaceValidModuleNameTest() { + String exceptionMessage = null; + try { + TestUtils.normalizeCompositeNode(prepareCnSn("normalize-node-module"), modules, schemaNodePath); + } catch (ResponseException e) { + exceptionMessage = String.valueOf(e.getResponse().getEntity()); + } + assertNull(exceptionMessage); + } + + private CompositeNode prepareCnSn(String namespace) { + URI uri = null; + if (namespace != null) { + try { + uri = new URI(namespace); + } catch (URISyntaxException e) { + } + assertNotNull(uri); + } + + SimpleNodeWrapper lf1 = new SimpleNodeWrapper(uri, "lf1", 43); + CompositeNodeWrapper cont = new CompositeNodeWrapper(uri, "cont"); + cont.addValue(lf1); + + return cont; + } + +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestConfigDataTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestConfigDataTest.java new file mode 100644 index 0000000000..dccf0d3bae --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestConfigDataTest.java @@ -0,0 +1,210 @@ +package org.opendaylight.controller.sal.restconf.impl.test; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; +import static org.junit.Assert.assertEquals; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.text.ParseException; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.logging.Level; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider; +import org.opendaylight.controller.sal.rest.impl.XmlMapper; +import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider; +import org.opendaylight.controller.sal.restconf.impl.BrokerFacade; +import org.opendaylight.controller.sal.restconf.impl.ControllerContext; +import org.opendaylight.controller.sal.restconf.impl.RestconfImpl; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.controller.sal.core.api.mount.MountInstance; +import org.opendaylight.controller.sal.core.api.mount.MountService; + +import com.google.common.base.Charsets; + +public class RestConfigDataTest extends JerseyTest { + + private static ControllerContext controllerContext; + private static BrokerFacade brokerFacade; + private static RestconfImpl restconfImpl; + private static MountService mountService; + private static SchemaContext schemaContext; + + private static final MediaType MEDIA_TYPE_XML_DRAFT02 = new MediaType("application", "yang.data+xml"); + + @BeforeClass + public static void init() throws FileNotFoundException { + Set modules = TestUtils.loadModulesFrom("/test-config-data/yang1"); + schemaContext = TestUtils.loadSchemaContext(modules); + initMocking(); + } + + private static void initMocking() { + controllerContext = ControllerContext.getInstance(); + controllerContext.setSchemas(schemaContext); + mountService = mock(MountService.class); + controllerContext.setMountService(mountService); + brokerFacade = mock(BrokerFacade.class); + restconfImpl = RestconfImpl.getInstance(); + restconfImpl.setBroker(brokerFacade); + restconfImpl.setControllerContext(controllerContext); + } + + @Test + public void createConfigurationDataTest() throws UnsupportedEncodingException, ParseException { + initMocking(); + String URI_1 = createUri("/config", ""); + String URI_2 = createUri("/config/", ""); + String URI_3 = createUri("/config/", "test-interface:interfaces/"); + String URI_4 = createUri("/config/", "test-interface:interfaces/"); + String URI_5 = createUri("/config/", "test-interface:interfaces/test-interface2:class"); + + RpcResult rpcResult = new DummyRpcResult.Builder().result( + TransactionStatus.COMMITED).build(); + Future> dummyFuture = DummyFuture.builder().rpcResult(rpcResult).build(); + + when(brokerFacade.commitConfigurationDataPost(any(InstanceIdentifier.class), any(CompositeNode.class))) + .thenReturn(dummyFuture); + + ArgumentCaptor instanceIdCaptor = ArgumentCaptor.forClass(InstanceIdentifier.class); + ArgumentCaptor compNodeCaptor = ArgumentCaptor.forClass(CompositeNode.class); + + // Test URI_1 + Entity entity = createEntity("/test-config-data/xml/test-interface.xml"); + Response response = target(URI_1).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(204, response.getStatus()); + verify(brokerFacade).commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture()); + String identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces]"; + assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString()); + + // Test URI_2 + response = target(URI_2).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(204, response.getStatus()); + verify(brokerFacade, times(2)) + .commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture()); + assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString()); + + // Test URI_3 + entity = createEntity("/test-config-data/xml/test-interface2.xml"); + response = target(URI_3).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(204, response.getStatus()); + verify(brokerFacade, times(3)) + .commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture()); + + identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces, (urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interface[{(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)name=eth0}]]"; + assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString()); + + // Test URI_4 + Set modules2 = TestUtils.loadModulesFrom("/test-config-data/yang2"); + SchemaContext schemaContext2 = TestUtils.loadSchemaContext(modules2); + MountInstance mountInstance = mock(MountInstance.class); + when(mountInstance.getSchemaContext()).thenReturn(schemaContext2); + when(mountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance); + + entity = createEntity("/test-config-data/xml/test-interface3.xml"); + response = target(URI_4).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(204, response.getStatus()); + verify(brokerFacade, times(4)) + .commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture()); + identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces, (urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)class]"; + assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString()); + + // Test URI_5 + response = target(URI_5).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(204, response.getStatus()); + verify(brokerFacade, times(5)) + .commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture()); + identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces, (urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)class, (urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)class]"; + assertEquals("Bad format URI", identifier, instanceIdCaptor.getValue().getPath().toString()); + } + + @Test + public void testExistingData() throws UnsupportedEncodingException { + initMocking(); + String URI_1 = createUri("/config", ""); + String URI_2 = createUri("/config/", ""); + String URI_3 = createUri("/config/", "test-interface:interfaces/"); + String URI_4 = createUri("/config/", "test-interface:interfaces/"); + String URI_5 = createUri("/config/", "test-interface:interfaces/test-interface2:class"); + + when(brokerFacade.commitConfigurationDataPost(any(InstanceIdentifier.class), any(CompositeNode.class))) + .thenReturn(null); + + // Test URI_1 + Entity entity = createEntity("/test-config-data/xml/test-interface.xml"); + Response response = target(URI_1).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(202, response.getStatus()); + + // Test URI_2 + response = target(URI_2).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(202, response.getStatus()); + + // Test URI_3 + entity = createEntity("/test-config-data/xml/test-interface2.xml"); + response = target(URI_3).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(202, response.getStatus()); + + // Test URI_4 + Set modules2 = TestUtils.loadModulesFrom("/test-config-data/yang2"); + SchemaContext schemaContext2 = TestUtils.loadSchemaContext(modules2); + MountInstance mountInstance = mock(MountInstance.class); + when(mountInstance.getSchemaContext()).thenReturn(schemaContext2); + when(mountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance); + + entity = createEntity("/test-config-data/xml/test-interface3.xml"); + response = target(URI_4).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(202, response.getStatus()); + + // Test URI_5 + response = target(URI_5).request(MEDIA_TYPE_XML_DRAFT02).post(entity); + assertEquals(202, response.getStatus()); + } + + private String createUri(String prefix, String encodedPart) throws UnsupportedEncodingException { + return URI.create(prefix + URLEncoder.encode(encodedPart, Charsets.US_ASCII.name()).toString()).toASCIIString(); + } + + private Entity createEntity(final String relativePathToXml) { + InputStream inputStream = XmlMapper.class.getResourceAsStream(relativePathToXml); + String xml = TestUtils.getDocumentInPrintableForm(TestUtils.loadDocumentFrom(inputStream)); + Entity entity = Entity.entity(xml, MEDIA_TYPE_XML_DRAFT02); + + return entity; + } + + @Override + protected Application configure() { + enable(TestProperties.LOG_TRAFFIC); + enable(TestProperties.DUMP_ENTITY); + enable(TestProperties.RECORD_LOG_LEVEL); + set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue()); + + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE, + XmlToCompositeNodeProvider.INSTANCE); + return resourceConfig; + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java index 4295c29a22..3427fbde38 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java @@ -149,7 +149,7 @@ public final class TestUtils { ControllerContext.getInstance().setSchemas(TestUtils.loadSchemaContext(modules)); prepareMocksForRestconf(modules, restconf); - restconf.createConfigurationData(schemaNodePath, compositeNode); + restconf.updateConfigurationData(schemaNodePath, compositeNode); } /** diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java index b18f526a23..7cce34ffb6 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java @@ -12,7 +12,9 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -33,9 +35,15 @@ import org.opendaylight.controller.sal.rest.api.Draft01; import org.opendaylight.controller.sal.rest.api.RestconfService; import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider; import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider; -import org.opendaylight.controller.sal.restconf.impl.*; +import org.opendaylight.controller.sal.restconf.impl.BrokerFacade; +import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper; +import org.opendaylight.controller.sal.restconf.impl.ControllerContext; +import org.opendaylight.controller.sal.restconf.impl.RestconfImpl; +import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper; import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.data.api.*; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; @@ -140,26 +148,34 @@ public class XmlProvidersTest extends JerseyTest { RpcResult rpcResult = new DummyRpcResult.Builder().result( TransactionStatus.COMMITED).build(); Future> dummyFuture = DummyFuture.builder().rpcResult(rpcResult).build(); - when(brokerFacade.commitOperationalDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))) - .thenReturn(dummyFuture); when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))) .thenReturn(dummyFuture); + when(brokerFacade.commitConfigurationDataPost(any(InstanceIdentifier.class), any(CompositeNode.class))) + .thenReturn(dummyFuture); String uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0"); Response response = target(uri).request(MEDIA_TYPE_DRAFT02).put(entity); assertEquals(200, response.getStatus()); + + uri = createUri("/config/", "ietf-interfaces:interfaces"); response = target(uri).request(MEDIA_TYPE_DRAFT02).post(entity); assertEquals(204, response.getStatus()); uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0"); response = target(uri).request(MEDIA_TYPE_DRAFT02).put(entity); assertEquals(200, response.getStatus()); + + uri = createUri("/config/", "ietf-interfaces:interfaces"); response = target(uri).request(MEDIA_TYPE_DRAFT02).post(entity); assertEquals(204, response.getStatus()); uri = createUri("/datastore/", "ietf-interfaces:interfaces/interface/eth0"); + entity = Entity.entity(xml, MEDIA_TYPE); response = target(uri).request(MEDIA_TYPE).put(entity); assertEquals(200, response.getStatus()); + + uri = createUri("/datastore/", "ietf-interfaces:interfaces"); + entity = Entity.entity(xml, MEDIA_TYPE); response = target(uri).request(MEDIA_TYPE).post(entity); assertEquals(204, response.getStatus()); } @@ -172,8 +188,6 @@ public class XmlProvidersTest extends JerseyTest { RpcResult rpcResult = new DummyRpcResult.Builder().result( TransactionStatus.FAILED).build(); Future> dummyFuture = DummyFuture.builder().rpcResult(rpcResult).build(); - when(brokerFacade.commitOperationalDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))) - .thenReturn(dummyFuture); when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))) .thenReturn(dummyFuture); @@ -190,9 +204,10 @@ public class XmlProvidersTest extends JerseyTest { assertEquals(500, response.getStatus()); uri = createUri("/datastore/", "ietf-interfaces:interfaces/interface/eth0"); - response = target(uri).request(MEDIA_TYPE).put(entity); + entity = Entity.entity(xml, MEDIA_TYPE); + response = target(uri).request().put(entity); assertEquals(500, response.getStatus()); - response = target(uri).request(MEDIA_TYPE).post(entity); + response = target(uri).request().accept(MEDIA_TYPE).post(entity); assertEquals(500, response.getStatus()); } diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/normalize-node/yang/normalize-node-module b/opendaylight/md-sal/sal-rest-connector/src/test/resources/normalize-node/yang/normalize-node-module new file mode 100644 index 0000000000..15e68ef225 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/normalize-node/yang/normalize-node-module @@ -0,0 +1,14 @@ +module normalize-node-module { + namespace "normalize:node:module"; + + prefix "nonomo"; + revision 2014-01-09 { + } + + container cont { + leaf lf1 { + type int32; + } + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface.xml new file mode 100644 index 0000000000..755c8a9b0f --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface.xml @@ -0,0 +1,8 @@ + + + eth0 + ethernetCsmacd + false + + + diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface2.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface2.xml new file mode 100644 index 0000000000..05db4a5ccc --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface2.xml @@ -0,0 +1,5 @@ + + eth0 + ethernetCsmacd + false + diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface3.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface3.xml new file mode 100644 index 0000000000..e59ba178b7 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/xml/test-interface3.xml @@ -0,0 +1,6 @@ + + + Thomas + 23 + + diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang1/test-interface.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang1/test-interface.yang new file mode 100644 index 0000000000..f683a69444 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang1/test-interface.yang @@ -0,0 +1,30 @@ +module test-interface { + yang-version 1; + namespace "urn:ietf:params:xml:ns:yang:test-interface"; + prefix "sn"; + + description + "test file"; + + revision "2014-07-01" { + description + "Initial revision"; + reference "will be defined"; + } + + container interfaces { + list interface { + key "name"; + + leaf name { + type string; + } + leaf type { + type string; + } + leaf enabled { + type string; + } + } + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang2/test-interface2.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang2/test-interface2.yang new file mode 100644 index 0000000000..13bc0ebf9d --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/test-config-data/yang2/test-interface2.yang @@ -0,0 +1,27 @@ +module test-interface2 { + yang-version 1; + namespace "urn:ietf:params:xml:ns:yang:test-interface2"; + prefix "snn"; + + description + "test file"; + + revision "2014-08-01" { + description + "Initial revision"; + reference "will be defined"; + } + + container class { + list student { + key "name"; + + leaf name { + type string; + } + leaf age { + type string; + } + } + } +}