<classifier>config</classifier>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.netconf</groupId>
+ <artifactId>sal-rest-connector-config</artifactId>
+ <version>${restconf.version}</version>
+ <type>xml</type>
+ <classifier>configrestconfservice</classifier>
+ </dependency>
+
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<bundle>mvn:io.netty/netty-handler/${netty.version}</bundle>
<bundle>mvn:io.netty/netty-transport/${netty.version}</bundle>
<configfile finalname="${config.configfile.directory}/${config.restconf.configfile}">mvn:org.opendaylight.netconf/sal-rest-connector-config/${project.version}/xml/config</configfile>
+ <configfile finalname="${config.configfile.directory}/${config.restconf.service.configfile}">mvn:org.opendaylight.netconf/sal-rest-connector-config/${project.version}/xml/configrestconfservice</configfile>
</feature>
<feature name ='odl-mdsal-apidocs' version='${project.version}' description="OpenDaylight :: MDSAL :: APIDOCS">
<feature version='${project.version}'>odl-restconf</feature>
<type>xml</type>
<classifier>config</classifier>
</artifact>
+ <artifact>
+ <file>${project.build.directory}/classes/initial/10-restconf-service.xml</file>
+ <type>xml</type>
+ <classifier>configrestconfservice</classifier>
+ </artifact>
</artifacts>
</configuration>
</execution>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2015 Brocade Communications 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
+-->
+<snapshot>
+ <configuration>
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+ <module>
+ <type xmlns:rest="urn:opendaylight:params:xml:ns:yang:controller:sal:restconf:service">rest:json-restconf-service-impl</type>
+ <name>json-restconf-service-impl</name>
+ </module>
+ </modules>
+
+ <services xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+ <service>
+ <type xmlns:rest="urn:opendaylight:params:xml:ns:yang:controller:sal:restconf:service">rest:json-restconf-service</type>
+ <instance>
+ <name>json-restconf-service</name>
+ <provider>
+ /modules/module[type='json-restconf-service-impl'][name='json-restconf-service-impl']
+ </provider>
+ </instance>
+ </service>
+ </services>
+ </data>
+ </configuration>
+ <required-capabilities>
+ <capability>urn:opendaylight:params:xml:ns:yang:controller:sal:restconf:service?module=sal-restconf-service&revision=2015-07-08</capability>
+ </required-capabilities>
+</snapshot>
</dependency>
<!-- Testing Dependencies -->
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
--- /dev/null
+package org.opendaylight.controller.config.yang.sal.restconf.service;
+
+import org.opendaylight.netconf.sal.restconf.impl.JSONRestconfServiceImpl;
+
+public class JSONRestconfServiceModule extends org.opendaylight.controller.config.yang.sal.restconf.service.AbstractJSONRestconfServiceModule {
+ public JSONRestconfServiceModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ }
+
+ public JSONRestconfServiceModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.yang.sal.restconf.service.JSONRestconfServiceModule oldModule, java.lang.AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void customValidation() {
+ // add custom validation form module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ return new JSONRestconfServiceImpl();
+ }
+}
--- /dev/null
+/*
+* Generated file
+*
+* Generated from: yang module name: sal-restconf-service yang module local name: json-restconf-service-impl
+* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+* Generated at: Tue Sep 22 07:17:38 EDT 2015
+*
+* Do not modify this file unless it is present under src/main directory
+*/
+package org.opendaylight.controller.config.yang.sal.restconf.service;
+public class JSONRestconfServiceModuleFactory extends org.opendaylight.controller.config.yang.sal.restconf.service.AbstractJSONRestconfServiceModuleFactory {
+
+}
import javax.ws.rs.ext.Provider;
import org.opendaylight.netconf.sal.rest.api.Draft02;
import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws IOException,
WebApplicationException {
try {
- final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
- if (entityStream.available() < 1) {
- return new NormalizedNodeContext(path, null);
- }
- final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
- final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
-
- final SchemaNode parentSchema;
- if(isPost()) {
- // FIXME: We need dispatch for RPC.
- parentSchema = path.getSchemaNode();
- } else if(path.getSchemaNode() instanceof SchemaContext) {
+ return readFrom(getInstanceIdentifierContext(), entityStream, isPost());
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ private static void propagateExceptionAs(Exception e) throws RestconfDocumentedException {
+ if(e instanceof RestconfDocumentedException) {
+ throw (RestconfDocumentedException)e;
+ }
+
+ if(e instanceof ResultAlreadySetException) {
+ LOG.debug("Error parsing json input:", e);
+
+ throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. " +
+ "Are you creating multiple resources/subresources in POST request?");
+ }
+
+ LOG.debug("Error parsing json input", e);
+
+ throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
+ }
+
+ public static NormalizedNodeContext readFrom(final String uriPath, final InputStream entityStream,
+ final boolean isPost) throws RestconfDocumentedException {
+
+ try {
+ return readFrom(ControllerContext.getInstance().toInstanceIdentifier(uriPath), entityStream, isPost);
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ private static NormalizedNodeContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream,
+ final boolean isPost) throws IOException {
+ if (entityStream.available() < 1) {
+ return new NormalizedNodeContext(path, null);
+ }
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+ final SchemaNode parentSchema;
+ if(isPost) {
+ // FIXME: We need dispatch for RPC.
+ parentSchema = path.getSchemaNode();
+ } else if(path.getSchemaNode() instanceof SchemaContext) {
+ parentSchema = path.getSchemaContext();
+ } else {
+ if (SchemaPath.ROOT.equals(path.getSchemaNode().getPath().getParent())) {
parentSchema = path.getSchemaContext();
} else {
- if (SchemaPath.ROOT.equals(path.getSchemaNode().getPath().getParent())) {
- parentSchema = path.getSchemaContext();
- } else {
- parentSchema = SchemaContextUtil.findDataSchemaNode(path.getSchemaContext(), path.getSchemaNode().getPath().getParent());
- }
+ parentSchema = SchemaContextUtil.findDataSchemaNode(path.getSchemaContext(), path.getSchemaNode().getPath().getParent());
}
+ }
- final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(), parentSchema);
- final JsonReader reader = new JsonReader(new InputStreamReader(entityStream));
- jsonParser.parse(reader);
+ final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(), parentSchema);
+ final JsonReader reader = new JsonReader(new InputStreamReader(entityStream));
+ jsonParser.parse(reader);
- NormalizedNode<?, ?> result = resultHolder.getResult();
- final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
- InstanceIdentifierContext<? extends SchemaNode> newIIContext;
+ NormalizedNode<?, ?> result = resultHolder.getResult();
+ final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
+ InstanceIdentifierContext<? extends SchemaNode> newIIContext;
- while (result instanceof AugmentationNode || result instanceof ChoiceNode) {
- final Object childNode = ((DataContainerNode) result).getValue().iterator().next();
- if (isPost()) {
- iiToDataList.add(result.getIdentifier());
- }
- result = (NormalizedNode<?, ?>) childNode;
+ while (result instanceof AugmentationNode || result instanceof ChoiceNode) {
+ final Object childNode = ((DataContainerNode) result).getValue().iterator().next();
+ if (isPost) {
+ iiToDataList.add(result.getIdentifier());
}
+ result = (NormalizedNode<?, ?>) childNode;
+ }
- if (isPost()) {
- if (result instanceof MapEntryNode) {
- iiToDataList.add(new YangInstanceIdentifier.NodeIdentifier(result.getNodeType()));
- iiToDataList.add(result.getIdentifier());
- } else {
- iiToDataList.add(result.getIdentifier());
- }
+ if (isPost) {
+ if (result instanceof MapEntryNode) {
+ iiToDataList.add(new YangInstanceIdentifier.NodeIdentifier(result.getNodeType()));
+ iiToDataList.add(result.getIdentifier());
} else {
- if (result instanceof MapNode) {
- result = Iterables.getOnlyElement(((MapNode) result).getValue());
- }
+ iiToDataList.add(result.getIdentifier());
}
+ } else {
+ if (result instanceof MapNode) {
+ result = Iterables.getOnlyElement(((MapNode) result).getValue());
+ }
+ }
- final YangInstanceIdentifier fullIIToData = YangInstanceIdentifier.create(Iterables.concat(
- path.getInstanceIdentifier().getPathArguments(), iiToDataList));
-
- newIIContext = new InstanceIdentifierContext<>(fullIIToData, path.getSchemaNode(), path.getMountPoint(),
- path.getSchemaContext());
-
- return new NormalizedNodeContext(newIIContext, result);
- } catch (final RestconfDocumentedException e) {
- throw e;
- } catch (final ResultAlreadySetException e) {
- LOG.debug("Error parsing json input:", e);
+ final YangInstanceIdentifier fullIIToData = YangInstanceIdentifier.create(Iterables.concat(
+ path.getInstanceIdentifier().getPathArguments(), iiToDataList));
- throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. " +
- "Are you creating multiple resources/subresources in POST request?");
- } catch (final Exception e) {
- LOG.debug("Error parsing json input", e);
+ newIIContext = new InstanceIdentifierContext<>(fullIIToData, path.getSchemaNode(), path.getMountPoint(),
+ path.getSchemaContext());
- throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
- ErrorTag.MALFORMED_MESSAGE);
- }
+ return new NormalizedNodeContext(newIIContext, result);
}
}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications 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.restconf.api;
+
+import com.google.common.base.Optional;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
+
+/**
+ * @author Thomas Pantelis
+ */
+public interface JSONRestconfService {
+ /**
+ * The data tree root path.
+ */
+ String ROOT_PATH = null;
+
+ /**
+ * Issues a restconf PUT request to the configuration data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @param payload the payload data in JSON format.
+ * @throws OperationFailedException if the request fails.
+ */
+ void put(String uriPath, @Nonnull String payload) throws OperationFailedException;
+
+ /**
+ * Issues a restconf POST request to the configuration data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @param payload the payload data in JSON format.
+ * @throws OperationFailedException if the request fails.
+ */
+ void post(String uriPath, @Nonnull String payload) throws OperationFailedException;
+
+ /**
+ * Issues a restconf DELETE request to the configuration data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @throws OperationFailedException if the request fails.
+ */
+ void delete(String uriPath) throws OperationFailedException;
+
+ /**
+ * Issues a restconf GET request to the given data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @param datastoreType the data store type to read from.
+ * @return an Optional containing the data in JSON format if present.
+ * @throws OperationFailedException if the request fails.
+ */
+ Optional<String> get(String uriPath, LogicalDatastoreType datastoreType) throws OperationFailedException;
+
+ /**
+ * Invokes a yang-defined RPC.
+ *
+ * @param uriPath the path representing the RPC to invoke, eg "toaster:make-toast".
+ * @param input the input in JSON format if the RPC takes input.
+ * @return an Optional containing the output in JSON format if the RPC returns output.
+ * @throws OperationFailedException if the request fails.
+ */
+ Optional<String> invokeRpc(@Nonnull String uriPath, Optional<String> input) throws OperationFailedException;
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications 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.restconf.impl;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import javax.ws.rs.core.MediaType;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter;
+import org.opendaylight.netconf.sal.restconf.api.JSONRestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the JSONRestconfService interface.
+ *
+ * @author Thomas Pantelis
+ */
+public class JSONRestconfServiceImpl implements JSONRestconfService, AutoCloseable {
+ private final static Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceImpl.class);
+
+ private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
+
+ @Override
+ public void put(String uriPath, String payload) throws OperationFailedException {
+ Preconditions.checkNotNull(payload, "payload can't be null");
+
+ LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
+
+ InputStream entityStream = new ByteArrayInputStream(payload.getBytes(Charsets.UTF_8));
+ NormalizedNodeContext context = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, false);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", context.getData());
+
+ try {
+ RestconfImpl.getInstance().updateConfigurationData(uriPath, context);
+ } catch (Exception e) {
+ propagateExceptionAs(uriPath, e, "PUT");
+ }
+ }
+
+ @Override
+ public void post(String uriPath, String payload) throws OperationFailedException {
+ Preconditions.checkNotNull(payload, "payload can't be null");
+
+ LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
+
+ InputStream entityStream = new ByteArrayInputStream(payload.getBytes(Charsets.UTF_8));
+ NormalizedNodeContext context = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, true);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", context.getData());
+
+ try {
+ RestconfImpl.getInstance().createConfigurationData(uriPath, context, null);
+ } catch (Exception e) {
+ propagateExceptionAs(uriPath, e, "POST");
+ }
+ }
+
+ @Override
+ public void delete(String uriPath) throws OperationFailedException {
+ LOG.debug("delete: uriPath: {}", uriPath);
+
+ try {
+ RestconfImpl.getInstance().deleteConfigurationData(uriPath);
+ } catch (Exception e) {
+ propagateExceptionAs(uriPath, e, "DELETE");
+ }
+ }
+
+ @Override
+ public Optional<String> get(String uriPath, LogicalDatastoreType datastoreType) throws OperationFailedException {
+ LOG.debug("get: uriPath: {}", uriPath);
+
+ try {
+ NormalizedNodeContext readData;
+ if(datastoreType == LogicalDatastoreType.CONFIGURATION) {
+ readData = RestconfImpl.getInstance().readConfigurationData(uriPath, null);
+ } else {
+ readData = RestconfImpl.getInstance().readOperationalData(uriPath, null);
+ }
+
+ Optional<String> result = Optional.of(toJson(readData));
+
+ LOG.debug("get returning: {}", result.get());
+
+ return result;
+ } catch (Exception e) {
+ if(!isDataMissing(e)) {
+ propagateExceptionAs(uriPath, e, "GET");
+ }
+
+ LOG.debug("Data missing - returning absent");
+ return Optional.absent();
+ }
+ }
+
+ @Override
+ public Optional<String> invokeRpc(String uriPath, Optional<String> input) throws OperationFailedException {
+ Preconditions.checkNotNull(uriPath, "uriPath can't be null");
+
+ String actualInput = input.isPresent() ? input.get() : null;
+
+ LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
+
+ String output = null;
+ try {
+ NormalizedNodeContext outputContext;
+ if(actualInput != null) {
+ InputStream entityStream = new ByteArrayInputStream(actualInput.getBytes(Charsets.UTF_8));
+ NormalizedNodeContext inputContext = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, true);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
+ .getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
+
+ outputContext = RestconfImpl.getInstance().invokeRpc(uriPath, inputContext, null);
+ } else {
+ outputContext = RestconfImpl.getInstance().invokeRpc(uriPath, "", null);
+ }
+
+ if(outputContext.getData() != null) {
+ output = toJson(outputContext);
+ }
+ } catch (Exception e) {
+ propagateExceptionAs(uriPath, e, "RPC");
+ }
+
+ return Optional.fromNullable(output);
+ }
+
+ @Override
+ public void close() {
+ }
+
+ private String toJson(NormalizedNodeContext readData) throws IOException {
+ NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
+ MediaType.APPLICATION_JSON_TYPE, null, outputStream );
+ return outputStream.toString(Charsets.UTF_8.name());
+ }
+
+ private boolean isDataMissing(Exception e) {
+ boolean dataMissing = false;
+ if(e instanceof RestconfDocumentedException) {
+ RestconfDocumentedException rde = (RestconfDocumentedException)e;
+ if(!rde.getErrors().isEmpty()) {
+ if(rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING) {
+ dataMissing = true;
+ }
+ }
+ }
+
+ return dataMissing;
+ }
+
+ private static void propagateExceptionAs(String uriPath, Exception e, String operation) throws OperationFailedException {
+ LOG.debug("Error for uriPath: {}", uriPath, e);
+
+ if(e instanceof RestconfDocumentedException) {
+ throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), e.getCause(),
+ toRpcErrors(((RestconfDocumentedException)e).getErrors()));
+ }
+
+ throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), e);
+ }
+
+ private static RpcError[] toRpcErrors(List<RestconfError> from) {
+ RpcError[] to = new RpcError[from.size()];
+ int i = 0;
+ for(RestconfError e: from) {
+ to[i++] = RpcResultBuilder.newError(toRpcErrorType(e.getErrorType()), e.getErrorTag().getTagValue(),
+ e.getErrorMessage());
+ }
+
+ return to;
+ }
+
+ private static ErrorType toRpcErrorType(RestconfError.ErrorType errorType) {
+ switch(errorType) {
+ case TRANSPORT: {
+ return ErrorType.TRANSPORT;
+ }
+ case RPC: {
+ return ErrorType.RPC;
+ }
+ case PROTOCOL: {
+ return ErrorType.PROTOCOL;
+ }
+ default: {
+ return ErrorType.APPLICATION;
+ }
+ }
+ }
+}
PRETTY_PRINT("prettyPrint"),
DEPTH("depth");
- private String uriParameterName;
+ private final String uriParameterName;
UriParameters(final String uriParameterName) {
this.uriParameterName = uriParameterName;
public static WriterParameters parseWriterParameters(final UriInfo info) {
WriterParameters.WriterParametersBuilder wpBuilder = new WriterParameters.WriterParametersBuilder();
+ if(info == null) {
+ return wpBuilder.build();
+ }
+
String param = info.getQueryParameters(false).getFirst(UriParameters.DEPTH.toString());
if (!Strings.isNullOrEmpty(param) && !"unbounded".equals(param)) {
try {
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
-
import java.util.Collection;
import java.util.List;
-
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
-
import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcError;
this(message, RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED);
}
+ /**
+ * Constructs an instance with an error message, error type, error tag and exception cause.
+ *
+ * @param message
+ * A string which provides a plain text string describing the error.
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ * @param cause
+ * The underlying exception cause.
+ */
+ public RestconfDocumentedException(String message, ErrorType errorType, ErrorTag errorTag, Throwable cause) {
+ this(cause, new RestconfError(errorType, errorTag, message, null, RestconfError.toErrorInfo(cause)));
+ }
+
/**
* Constructs an instance with an error message, error type, and error tag.
*
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
import org.opendaylight.yangtools.yang.model.util.EmptyType;
-import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
import org.opendaylight.yangtools.yang.parser.builder.api.GroupingBuilder;
import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder;
import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder;
final DOMRpcResult result = checkRpcResponse(response);
- DataSchemaNode resultNodeSchema = null;
- NormalizedNode<?, ?> resultData = null;
- if (result != null && result.getResult() != null) {
- resultData = result.getResult();
- final ContainerSchemaNode rpcDataSchemaNode =
- SchemaContextUtil.getRpcDataSchema(schemaContext, rpc.getOutput().getPath());
- resultNodeSchema = rpcDataSchemaNode.getDataChildByName(result.getResult().getNodeType());
- }
-
- return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, resultNodeSchema, mountPoint,
- schemaContext), resultData, QueryParametersParser.parseWriterParameters(uriInfo));
+ return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, rpc, mountPoint, schemaContext),
+ result.getResult(), QueryParametersParser.parseWriterParameters(uriInfo));
}
private RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) {
}
private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint, final YangInstanceIdentifier normalizedII) {
+ if(uriInfo == null) {
+ // This is null if invoked internally
+ return null;
+ }
+
final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("config");
try {
--- /dev/null
+module sal-restconf-service {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:sal:restconf:service";
+ prefix "sal-restconf-service";
+
+ import config { prefix config; revision-date 2013-04-05; }
+
+ description "Definition for the internal restconf service";
+
+ revision "2015-07-08" {
+ description "Initial revision";
+ }
+
+ identity json-restconf-service {
+ base "config:service-type";
+ config:java-class "org.opendaylight.netconf.sal.restconf.api.JSONRestconfService";
+ }
+
+ identity json-restconf-service-impl {
+ base config:module-type;
+ config:provided-service json-restconf-service;
+ config:java-name-prefix JSONRestconfService;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case json-restconf-service-impl {
+ when "/config:modules/config:module/config:type = 'json-restconf-service-impl'";
+ }
+ }
+}
\ No newline at end of file
import org.junit.BeforeClass;
import org.junit.Test;
import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
-import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter;
import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter;
import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
public void leafrefFromLeafListToLeafTest() throws Exception {
final String json = toJson("/nn-to-json/leafref/xml/data_relativ_ref_from_leaflist_to_existing_leaf.xml");
validateJson(
- ".*\"cont-augment-module\\p{Blank}*:\\p{Blank}*lflst1\":\\p{Blank}*.*\"346\",*\"347\",*\"345\".*",
+ ".*\"cont-augment-module\\p{Blank}*:\\p{Blank}*lflst1\":\\p{Blank}*.*\"34[5|6|7]\",*\"34[5|6|7]\",*\"34[5|6|7]\".*",
json);
}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications 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.test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Matchers.notNull;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.Futures;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.JSONRestconfServiceImpl;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+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.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+/**
+ * Unit tests for JSONRestconfServiceImpl.
+ *
+ * @author Thomas Pantelis
+ */
+public class JSONRestconfServiceImplTest {
+ static final String IETF_INTERFACES_NS = "urn:ietf:params:xml:ns:yang:ietf-interfaces";
+ static final String IETF_INTERFACES_VERSION = "2013-07-04";
+ static final QName INTERFACES_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "interfaces");
+ static final QName INTERFACE_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "interface");
+ static final QName NAME_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "name");
+ static final QName TYPE_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "type");
+ static final QName ENABLED_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "enabled");
+ static final QName DESC_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "description");
+
+ static final String TEST_MODULE_NS = "test:module";
+ static final String TEST_MODULE_VERSION = "2014-01-09";
+ static final QName TEST_CONT_QNAME = QName.create(TEST_MODULE_NS, TEST_MODULE_VERSION, "cont");
+ static final QName TEST_CONT1_QNAME = QName.create(TEST_MODULE_NS, TEST_MODULE_VERSION, "cont1");
+ static final QName TEST_LF11_QNAME = QName.create(TEST_MODULE_NS, TEST_MODULE_VERSION, "lf11");
+ static final QName TEST_LF12_QNAME = QName.create(TEST_MODULE_NS, TEST_MODULE_VERSION, "lf12");
+
+ static final String TOASTER_MODULE_NS = "http://netconfcentral.org/ns/toaster";
+ static final String TOASTER_MODULE_VERSION = "2009-11-20";
+ static final QName TOASTER_DONENESS_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "toasterDoneness");
+ static final QName TOASTER_TYPE_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "toasterToastType");
+ static final QName WHEAT_BREAD_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "wheat-bread");
+ static final QName MAKE_TOAST_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "make-toast");
+ static final QName CANCEL_TOAST_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "cancel-toast");
+ static final QName TEST_OUTPUT_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "testOutput");
+ static final QName TEXT_OUT_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "textOut");
+
+ private static BrokerFacade brokerFacade;
+
+ private final JSONRestconfServiceImpl service = new JSONRestconfServiceImpl();
+
+ @BeforeClass
+ public static void init() throws IOException {
+ ControllerContext.getInstance().setSchemas(TestUtils.loadSchemaContext("/full-versions/yangs"));
+ brokerFacade = mock(BrokerFacade.class);
+ RestconfImpl.getInstance().setBroker(brokerFacade);
+ RestconfImpl.getInstance().setControllerContext(ControllerContext.getInstance());
+ }
+
+ @Before
+ public void setup() {
+ reset(brokerFacade);
+ }
+
+ private String loadData(String path) throws IOException {
+ InputStream stream = JSONRestconfServiceImplTest.class.getResourceAsStream(path);
+ return IOUtils.toString(stream, "UTF-8");
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void testPut() throws Exception {
+ doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataPut(
+ notNull(SchemaContext.class), notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class));
+
+ String uriPath = "ietf-interfaces:interfaces/interface/eth0";
+ String payload = loadData("/parts/ietf-interfaces_interfaces.json");
+
+ service.put(uriPath, payload);
+
+ ArgumentCaptor<YangInstanceIdentifier> capturedPath = ArgumentCaptor.forClass(YangInstanceIdentifier.class);
+ ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class);
+ verify(brokerFacade).commitConfigurationDataPut(notNull(SchemaContext.class), capturedPath.capture(),
+ capturedNode.capture());
+
+ verifyPath(capturedPath.getValue(), INTERFACES_QNAME, INTERFACE_QNAME,
+ new Object[]{INTERFACE_QNAME, NAME_QNAME, "eth0"});
+
+ assertTrue("Expected MapEntryNode. Actual " + capturedNode.getValue().getClass(),
+ capturedNode.getValue() instanceof MapEntryNode);
+ MapEntryNode actualNode = (MapEntryNode) capturedNode.getValue();
+ assertEquals("MapEntryNode node type", INTERFACE_QNAME, actualNode.getNodeType());
+ verifyLeafNode(actualNode, NAME_QNAME, "eth0");
+ verifyLeafNode(actualNode, TYPE_QNAME, "ethernetCsmacd");
+ verifyLeafNode(actualNode, ENABLED_QNAME, Boolean.FALSE);
+ verifyLeafNode(actualNode, DESC_QNAME, "some interface");
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void testPutBehindMountPoint() throws Exception {
+ DOMMountPoint mockMountPoint = setupTestMountPoint();
+
+ doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataPut(
+ notNull(DOMMountPoint.class), notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class));
+
+ String uriPath = "ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1";
+ String payload = loadData("/full-versions/testCont1Data.json");
+
+ service.put(uriPath, payload);
+
+ ArgumentCaptor<YangInstanceIdentifier> capturedPath = ArgumentCaptor.forClass(YangInstanceIdentifier.class);
+ ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class);
+ verify(brokerFacade).commitConfigurationDataPut(same(mockMountPoint), capturedPath.capture(),
+ capturedNode.capture());
+
+ verifyPath(capturedPath.getValue(), TEST_CONT_QNAME, TEST_CONT1_QNAME);
+
+ assertTrue("Expected ContainerNode", capturedNode.getValue() instanceof ContainerNode);
+ ContainerNode actualNode = (ContainerNode) capturedNode.getValue();
+ assertEquals("ContainerNode node type", TEST_CONT1_QNAME, actualNode.getNodeType());
+ verifyLeafNode(actualNode, TEST_LF11_QNAME, "lf11 data");
+ verifyLeafNode(actualNode, TEST_LF12_QNAME, "lf12 data");
+ }
+
+ @Test(expected=TransactionCommitFailedException.class)
+ public void testPutFailure() throws Throwable {
+ doReturn(Futures.immediateFailedCheckedFuture(new TransactionCommitFailedException("mock")))
+ .when(brokerFacade).commitConfigurationDataPut(notNull(SchemaContext.class),
+ notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class));
+
+ String uriPath = "ietf-interfaces:interfaces/interface/eth0";
+ String payload = loadData("/parts/ietf-interfaces_interfaces.json");
+
+ try {
+ service.put(uriPath, payload);
+ } catch (OperationFailedException e) {
+ assertNotNull(e.getCause());
+ throw e.getCause();
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void testPost() throws Exception {
+ doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataPost(
+ any(SchemaContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class));
+
+ String uriPath = null;
+ String payload = loadData("/parts/ietf-interfaces_interfaces_absolute_path.json");
+
+ service.post(uriPath, payload);
+
+ ArgumentCaptor<YangInstanceIdentifier> capturedPath = ArgumentCaptor.forClass(YangInstanceIdentifier.class);
+ ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class);
+ verify(brokerFacade).commitConfigurationDataPost(notNull(SchemaContext.class), capturedPath.capture(),
+ capturedNode.capture());
+
+ verifyPath(capturedPath.getValue(), INTERFACES_QNAME);
+
+ assertTrue("Expected ContainerNode", capturedNode.getValue() instanceof ContainerNode);
+ ContainerNode actualNode = (ContainerNode) capturedNode.getValue();
+ assertEquals("ContainerNode node type", INTERFACES_QNAME, actualNode.getNodeType());
+
+ Optional<DataContainerChild<?, ?>> mapChild = actualNode.getChild(new NodeIdentifier(INTERFACE_QNAME));
+ assertEquals(INTERFACE_QNAME.toString() + " present", true, mapChild.isPresent());
+ assertTrue("Expected MapNode. Actual " + mapChild.get().getClass(), mapChild.get() instanceof MapNode);
+ MapNode mapNode = (MapNode)mapChild.get();
+
+ NodeIdentifierWithPredicates entryNodeID = new NodeIdentifierWithPredicates(
+ INTERFACE_QNAME, NAME_QNAME, "eth0");
+ Optional<MapEntryNode> entryChild = mapNode.getChild(entryNodeID);
+ assertEquals(entryNodeID.toString() + " present", true, entryChild.isPresent());
+ MapEntryNode entryNode = entryChild.get();
+ verifyLeafNode(entryNode, NAME_QNAME, "eth0");
+ verifyLeafNode(entryNode, TYPE_QNAME, "ethernetCsmacd");
+ verifyLeafNode(entryNode, ENABLED_QNAME, Boolean.FALSE);
+ verifyLeafNode(entryNode, DESC_QNAME, "some interface");
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void testPostBehindMountPoint() throws Exception {
+ DOMMountPoint mockMountPoint = setupTestMountPoint();
+
+ doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataPost(
+ notNull(DOMMountPoint.class), notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class));
+
+ String uriPath = "ietf-interfaces:interfaces/yang-ext:mount/test-module:cont";
+ String payload = loadData("/full-versions/testCont1Data.json");
+
+ service.post(uriPath, payload);
+
+ ArgumentCaptor<YangInstanceIdentifier> capturedPath = ArgumentCaptor.forClass(YangInstanceIdentifier.class);
+ ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class);
+ verify(brokerFacade).commitConfigurationDataPost(same(mockMountPoint), capturedPath.capture(),
+ capturedNode.capture());
+
+ verifyPath(capturedPath.getValue(), TEST_CONT_QNAME, TEST_CONT1_QNAME);
+
+ assertTrue("Expected ContainerNode", capturedNode.getValue() instanceof ContainerNode);
+ ContainerNode actualNode = (ContainerNode) capturedNode.getValue();
+ assertEquals("ContainerNode node type", TEST_CONT1_QNAME, actualNode.getNodeType());
+ verifyLeafNode(actualNode, TEST_LF11_QNAME, "lf11 data");
+ verifyLeafNode(actualNode, TEST_LF12_QNAME, "lf12 data");
+ }
+
+ @Test(expected=TransactionCommitFailedException.class)
+ public void testPostFailure() throws Throwable {
+ doReturn(Futures.immediateFailedCheckedFuture(new TransactionCommitFailedException("mock")))
+ .when(brokerFacade).commitConfigurationDataPost(any(SchemaContext.class),
+ any(YangInstanceIdentifier.class), any(NormalizedNode.class));
+
+ String uriPath = null;
+ String payload = loadData("/parts/ietf-interfaces_interfaces_absolute_path.json");
+
+ try {
+ service.post(uriPath, payload);
+ } catch (OperationFailedException e) {
+ assertNotNull(e.getCause());
+ throw e.getCause();
+ }
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataDelete(
+ notNull(YangInstanceIdentifier.class));
+
+ String uriPath = "ietf-interfaces:interfaces/interface/eth0";
+
+ service.delete(uriPath);
+
+ ArgumentCaptor<YangInstanceIdentifier> capturedPath = ArgumentCaptor.forClass(YangInstanceIdentifier.class);
+ verify(brokerFacade).commitConfigurationDataDelete(capturedPath.capture());
+
+ verifyPath(capturedPath.getValue(), INTERFACES_QNAME, INTERFACE_QNAME,
+ new Object[]{INTERFACE_QNAME, NAME_QNAME, "eth0"});
+ }
+
+ @Test(expected=OperationFailedException.class)
+ public void testDeleteFailure() throws Exception {
+ String invalidUriPath = "ietf-interfaces:interfaces/invalid";
+
+ service.delete(invalidUriPath);
+ }
+
+ @Test
+ public void testGetConfig() throws Exception {
+ testGet(LogicalDatastoreType.CONFIGURATION);
+ }
+
+ @Test
+ public void testGetOperational() throws Exception {
+ testGet(LogicalDatastoreType.OPERATIONAL);
+ }
+
+ @Test
+ public void testGetWithNoData() throws Exception {
+ doReturn(null).when(brokerFacade).readConfigurationData(notNull(YangInstanceIdentifier.class));
+
+ String uriPath = "ietf-interfaces:interfaces";
+
+ Optional<String> optionalResp = service.get(uriPath, LogicalDatastoreType.CONFIGURATION);
+
+ assertEquals("Response present", false, optionalResp.isPresent());
+ }
+
+ @Test(expected=OperationFailedException.class)
+ public void testGetFailure() throws Exception {
+ String invalidUriPath = "/ietf-interfaces:interfaces/invalid";
+
+ service.get(invalidUriPath, LogicalDatastoreType.CONFIGURATION);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void testInvokeRpcWithInput() throws Exception {
+ SchemaPath path = SchemaPath.create(true, MAKE_TOAST_QNAME);
+
+ DOMRpcResult expResult = new DefaultDOMRpcResult((NormalizedNode<?, ?>)null);
+ doReturn(Futures.immediateCheckedFuture(expResult)).when(brokerFacade).invokeRpc(eq(path),
+ any(NormalizedNode.class));
+
+ String uriPath = "toaster:make-toast";
+ String input = loadData("/full-versions/make-toast-rpc-input.json");
+
+ Optional<String> output = service.invokeRpc(uriPath, Optional.of(input));
+
+ assertEquals("Output present", false, output.isPresent());
+
+ ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class);
+ verify(brokerFacade).invokeRpc(eq(path), capturedNode.capture());
+
+ assertTrue("Expected ContainerNode. Actual " + capturedNode.getValue().getClass(),
+ capturedNode.getValue() instanceof ContainerNode);
+ ContainerNode actualNode = (ContainerNode) capturedNode.getValue();
+ verifyLeafNode(actualNode, TOASTER_DONENESS_QNAME, Long.valueOf(10));
+ verifyLeafNode(actualNode, TOASTER_TYPE_QNAME, WHEAT_BREAD_QNAME);
+ }
+
+ @Test
+ public void testInvokeRpcWithNoInput() throws Exception {
+ SchemaPath path = SchemaPath.create(true, CANCEL_TOAST_QNAME);
+
+ DOMRpcResult expResult = new DefaultDOMRpcResult((NormalizedNode<?, ?>)null);
+ doReturn(Futures.immediateCheckedFuture(expResult)).when(brokerFacade).invokeRpc(any(SchemaPath.class),
+ any(NormalizedNode.class));
+
+ String uriPath = "toaster:cancel-toast";
+
+ Optional<String> output = service.invokeRpc(uriPath, Optional.<String>absent());
+
+ assertEquals("Output present", false, output.isPresent());
+
+ verify(brokerFacade).invokeRpc(eq(path), isNull(NormalizedNode.class));
+ }
+
+ @Test
+ public void testInvokeRpcWithOutput() throws Exception {
+ SchemaPath path = SchemaPath.create(true, TEST_OUTPUT_QNAME);
+
+ NormalizedNode<?, ?> outputNode = ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TEST_OUTPUT_QNAME))
+ .withChild(ImmutableNodes.leafNode(TEXT_OUT_QNAME, "foo")).build();
+ DOMRpcResult expResult = new DefaultDOMRpcResult(outputNode);
+ doReturn(Futures.immediateCheckedFuture(expResult)).when(brokerFacade).invokeRpc(any(SchemaPath.class),
+ any(NormalizedNode.class));
+
+ String uriPath = "toaster:testOutput";
+
+ Optional<String> output = service.invokeRpc(uriPath, Optional.<String>absent());
+
+ assertEquals("Output present", true, output.isPresent());
+ assertNotNull("Returned null response", output.get());
+ assertThat("Missing \"textOut\"", output.get(), containsString("\"textOut\":\"foo\""));
+
+ verify(brokerFacade).invokeRpc(eq(path), isNull(NormalizedNode.class));
+ }
+
+ @Test(expected=OperationFailedException.class)
+ public void testInvokeRpcFailure() throws Exception {
+ DOMRpcException exception = new DOMRpcImplementationNotAvailableException("testExeption");
+ doReturn(Futures.immediateFailedCheckedFuture(exception)).when(brokerFacade).invokeRpc(any(SchemaPath.class),
+ any(NormalizedNode.class));
+
+ String uriPath = "toaster:cancel-toast";
+
+ service.invokeRpc(uriPath, Optional.<String>absent());
+ }
+
+ void testGet(LogicalDatastoreType datastoreType) throws OperationFailedException {
+ MapEntryNode entryNode = ImmutableNodes.mapEntryBuilder(INTERFACE_QNAME, NAME_QNAME, "eth0")
+ .withChild(ImmutableNodes.leafNode(NAME_QNAME, "eth0"))
+ .withChild(ImmutableNodes.leafNode(TYPE_QNAME, "ethernetCsmacd"))
+ .withChild(ImmutableNodes.leafNode(ENABLED_QNAME, Boolean.TRUE))
+ .withChild(ImmutableNodes.leafNode(DESC_QNAME, "eth interface"))
+ .build();
+
+ if(datastoreType == LogicalDatastoreType.CONFIGURATION) {
+ doReturn(entryNode).when(brokerFacade).readConfigurationData(notNull(YangInstanceIdentifier.class));
+ } else {
+ doReturn(entryNode).when(brokerFacade).readOperationalData(notNull(YangInstanceIdentifier.class));
+ }
+
+ String uriPath = "/ietf-interfaces:interfaces/interface/eth0";
+
+ Optional<String> optionalResp = service.get(uriPath, datastoreType);
+ assertEquals("Response present", true, optionalResp.isPresent());
+ String jsonResp = optionalResp.get();
+
+ assertNotNull("Returned null response", jsonResp);
+ assertThat("Missing \"name\"", jsonResp, containsString("\"name\":\"eth0\""));
+ assertThat("Missing \"type\"", jsonResp, containsString("\"type\":\"ethernetCsmacd\""));
+ assertThat("Missing \"enabled\"", jsonResp, containsString("\"enabled\":true"));
+ assertThat("Missing \"description\"", jsonResp, containsString("\"description\":\"eth interface\""));
+
+ ArgumentCaptor<YangInstanceIdentifier> capturedPath = ArgumentCaptor.forClass(YangInstanceIdentifier.class);
+ if (datastoreType == LogicalDatastoreType.CONFIGURATION) {
+ verify(brokerFacade).readConfigurationData(capturedPath.capture());
+ } else {
+ verify(brokerFacade).readOperationalData(capturedPath.capture());
+ }
+
+ verifyPath(capturedPath.getValue(), INTERFACES_QNAME, INTERFACE_QNAME,
+ new Object[]{INTERFACE_QNAME, NAME_QNAME, "eth0"});
+ }
+
+ DOMMountPoint setupTestMountPoint() throws FileNotFoundException {
+ SchemaContext schemaContextTestModule = TestUtils.loadSchemaContext("/full-versions/test-module");
+ DOMMountPoint mockMountPoint = mock(DOMMountPoint.class);
+ doReturn(schemaContextTestModule).when(mockMountPoint).getSchemaContext();
+
+ DOMMountPointService mockMountService = mock(DOMMountPointService.class);
+ doReturn(Optional.of(mockMountPoint)).when(mockMountService).getMountPoint(notNull(YangInstanceIdentifier.class));
+
+ ControllerContext.getInstance().setMountService(mockMountService);
+ return mockMountPoint;
+ }
+
+ void verifyLeafNode(DataContainerNode<?> parent, QName leafType, Object leafValue) {
+ Optional<DataContainerChild<?, ?>> leafChild = parent.getChild(new NodeIdentifier(leafType));
+ assertEquals(leafType.toString() + " present", true, leafChild.isPresent());
+ assertEquals(leafType.toString() + " value", leafValue, leafChild.get().getValue());
+ }
+
+ void verifyPath(YangInstanceIdentifier path, Object... expArgs) {
+ List<PathArgument> pathArgs = path.getPathArguments();
+ assertEquals("Arg count for actual path " + path, expArgs.length, pathArgs.size());
+ int i = 0;
+ for(PathArgument actual: pathArgs) {
+ QName expNodeType;
+ if(expArgs[i] instanceof Object[]) {
+ Object[] listEntry = (Object[]) expArgs[i];
+ expNodeType = (QName) listEntry[0];
+
+ assertTrue(actual instanceof NodeIdentifierWithPredicates);
+ Map<QName, Object> keyValues = ((NodeIdentifierWithPredicates)actual).getKeyValues();
+ assertEquals(String.format("Path arg %d keyValues size", i + 1), 1, keyValues.size());
+ QName expKey = (QName) listEntry[1];
+ assertEquals(String.format("Path arg %d keyValue for %s", i + 1, expKey), listEntry[2],
+ keyValues.get(expKey));
+ } else {
+ expNodeType = (QName) expArgs[i];
+ }
+
+ assertEquals(String.format("Path arg %d node type", i + 1), expNodeType, actual.getNodeType());
+ i++;
+ }
+
+ }
+}
--- /dev/null
+{
+ "input" :
+ {
+ "toaster:toasterDoneness" : "10",
+ "toaster:toasterToastType": "wheat-bread"
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "cont1": {
+ "lf11": "lf11 data",
+ "lf12": "lf12 data"
+ }
+}
\ No newline at end of file
<config.netconf.topology.configfile>02-netconf-topology.xml</config.netconf.topology.configfile>
<config.netconf.mdsal.configfile>08-mdsal-netconf.xml</config.netconf.mdsal.configfile>
<config.restconf.configfile>10-rest-connector.xml</config.restconf.configfile>
+ <config.restconf.service.configfile>10-restconf-service.xml</config.restconf.service.configfile>
<config.netconf.connector.configfile>99-netconf-connector.xml</config.netconf.connector.configfile>
<aaa.version>0.3.0-SNAPSHOT</aaa.version>