Add support for RESTCONF standard errors
Change-Id: I74c0997bc8e06bc10c97cd610ff70c7a6aa68c8b
diff --git a/apps/restconf/api/BUCK b/apps/restconf/api/BUCK
index b54f53c..39a3872 100644
--- a/apps/restconf/api/BUCK
+++ b/apps/restconf/api/BUCK
@@ -6,6 +6,11 @@
'//lib:jersey-server',
]
+TEST_DEPS = [
+ '//lib:TEST_REST',
+]
+
osgi_jar_with_tests (
deps = COMPILE_DEPS,
+ test_deps = TEST_DEPS,
)
diff --git a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfError.java b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfError.java
new file mode 100644
index 0000000..61c7144
--- /dev/null
+++ b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfError.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.restconf.api;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.List;
+
+/**
+ * Restconf error structured as in "ietf-restconf@2017-01-26.yang".
+ * An entry containing information about one specific error that occurred while
+ * processing a RESTCONF request.
+ */
+public final class RestconfError {
+ private final ErrorType errorType;
+ private final ErrorTag errorTag;
+ private String errorAppTag;
+ private String errorPath;
+ private String errorMessage;
+ private String errorInfo;
+
+ private RestconfError(ErrorType errorType, ErrorTag errorTag) {
+ this.errorType = errorType;
+ this.errorTag = errorTag;
+ }
+
+ /**
+ * The protocol layer where the error occurred.
+ * @return The error type
+ */
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+
+ /**
+ * The enumerated error-tag.
+ * @return The enumerated error-tag
+ */
+ public ErrorTag getErrorTag() {
+ return errorTag;
+ }
+
+ /**
+ * The application-specific error-tag.
+ * @return The application-specific error-tag
+ */
+ public String getErrorAppTag() {
+ return errorAppTag;
+ }
+
+ /**
+ * The YANG instance identifier associated with the error node.
+ * @return A yang instance represented in XPath
+ */
+ public String getErrorPath() {
+ return errorPath;
+ }
+
+ /**
+ * A message describing the error.
+ * @return A message describing the error
+ */
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ /**
+ * A container with zero or more data nodes representing additional error information.
+ * @return A string serialization of datanodes e.g. a stack trace
+ */
+ public String getErrorInfo() {
+ return errorInfo;
+ }
+
+ /**
+ * Convert the restconf error to Json - this is for one error - many may be included in a response.
+ * @return A JSON object containing the details of one error
+ */
+ public ObjectNode toJson() {
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode errorTags = (ObjectNode) mapper.createObjectNode();
+ errorTags.put("error-type", errorType.name().toLowerCase());
+ errorTags.put("error-tag", errorTag.text());
+
+ if (errorAppTag != null) {
+ errorTags.put("error-app-tag", errorAppTag);
+ }
+
+ if (errorPath != null) {
+ errorTags.put("error-path", errorPath);
+ }
+
+ if (errorMessage != null) {
+ errorTags.put("error-message", errorMessage);
+ }
+
+ if (errorInfo != null) {
+ errorTags.put("error-info", errorInfo);
+ }
+
+ ObjectNode errorNode = (ObjectNode) mapper.createObjectNode();
+ errorNode.put("error", errorTags);
+ return errorNode;
+ }
+
+ /**
+ * An enumerated set of the protocol layer involved in a RESTCONF request.
+ */
+ public enum ErrorType {
+ //The transport layer
+ TRANSPORT,
+ //The rpc or notification layer
+ RPC,
+ //The protocol operation layer
+ PROTOCOL,
+ //The server application layer
+ APPLICATION
+ }
+
+ /**
+ * An enumerated set of error 'tags' - consistent labels for error causes.
+ * See <a href="https://tools.ietf.org/html/rfc8040#section-7">Section 7 of RFC 8040</a>
+ * for a list of HTTP status codes associated with these
+ * error tags. Developers should ensure that suitable HTTP status codes are used
+ * when raising RESTCONF errors
+ */
+ public enum ErrorTag {
+ /**
+ * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+ */
+ IN_USE("in-use"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST},
+ * 404 {@link javax.ws.rs.core.Response.Status#NOT_FOUND} or
+ * 406 {@link javax.ws.rs.core.Response.Status#NOT_ACCEPTABLE}.
+ */
+ INVALID_VALUE("invalid-value"),
+ /**
+ * Use with Response 413 {@link javax.ws.rs.core.Response.Status#REQUEST_ENTITY_TOO_LARGE}.
+ */
+ REQUEST_TOO_BIG("too-big"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+ */
+ RESPONSE_TOO_BIG("too-big"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+ */
+ MISSING_ATTRIBUTE("missing-attribute"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+ */
+ BAD_ATTRIBUTE("bad-attribute"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+ */
+ UNKNOWN_ATTRIBUTE("unknown-attribute"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+ */
+ BAD_ELEMENT("bad-element"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+ */
+ UNKNOWN_ELEMENT("unknown-element"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+ */
+ UNKNOWN_NAMESPACE("unknown-namespace"),
+ /**
+ * Use with Response 401 {@link javax.ws.rs.core.Response.Status#UNAUTHORIZED},
+ * or 403 {@link javax.ws.rs.core.Response.Status#FORBIDDEN}.
+ */
+ ACCESS_DENIED("access-denied"),
+ /**
+ * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+ */
+ LOCK_DENIED("lock-denied"),
+ /**
+ * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+ */
+ RESOURCE_DENIED("resource-denied"),
+ /**
+ * Use with Response 500 {@link javax.ws.rs.core.Response.Status#INTERNAL_SERVER_ERROR}.
+ */
+ ROLLBACK_FAILED("rollback-failed"),
+ /**
+ * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+ */
+ DATA_EXISTS("data-exists"),
+ /**
+ * Use with Response 409 {@link javax.ws.rs.core.Response.Status#CONFLICT}.
+ */
+ DATA_MISSING("data-missing"),
+ /**
+ * Use with Response 405 {@link javax.ws.rs.core.Response.Status#METHOD_NOT_ALLOWED},
+ * or 501 {@link javax.ws.rs.core.Response.Status#NOT_IMPLEMENTED}.
+ */
+ OPERATION_NOT_SUPPORTED("operation-not-supported"),
+ /**
+ * Use with Response 412 {@link javax.ws.rs.core.Response.Status#PRECONDITION_FAILED},
+ * or 500 {@link javax.ws.rs.core.Response.Status#INTERNAL_SERVER_ERROR}.
+ */
+ OPERATION_FAILED("operation-failed"),
+ /**
+ * Use with Response 500 {@link javax.ws.rs.core.Response.Status#INTERNAL_SERVER_ERROR}.
+ */
+ PARTIAL_OPERATION("partial-operation"),
+ /**
+ * Use with Response 400 {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}.
+ */
+ MALFORMED_MESSAGE("malformed-message");
+
+ private String text;
+
+ ErrorTag(String text) {
+ this.text = text;
+ }
+
+ /**
+ * Lowercase version of the error tag compliant with the standard.
+ * @return the associated lowercase version
+ */
+ public String text() {
+ return text;
+ }
+ }
+
+ /**
+ * Build a new RestconfError. ErrorTag and ErrorType are mandatory parameters.
+ * @param errorType The error type
+ * @param errorTag The error Tag
+ * @return A build which an be used to create the RestconfError
+ */
+ public static RestconfError.Builder builder(ErrorType errorType, ErrorTag errorTag) {
+ return new Builder(errorType, errorTag);
+ }
+
+ /**
+ * Create the complete JSON representation of the errors for the Response.
+ * Note this can contain many individual RestconfErrors
+ * @param restconfErrors A list of {@link RestconfError}s
+ * @return A JSON node which can be used in a response
+ */
+ public static ObjectNode wrapErrorAsJson(List<RestconfError> restconfErrors) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode errorArray = mapper.createArrayNode();
+ restconfErrors.forEach(error -> errorArray.add(error.toJson()));
+ ObjectNode errorsNode = (ObjectNode) mapper.createObjectNode();
+ errorsNode.put("ietf-restconf:errors", errorArray);
+ return errorsNode;
+ }
+
+ /**
+ * A builder for the Restconf Error. ErrorTag and ErrorType are mandatory parameters.
+ */
+ public static final class Builder {
+ private RestconfError restconfError;
+
+ /**
+ * ErrorTag and ErrorType are mandatory parameters of the error.
+ * @param errorType The error-type
+ * @param errorTag The error-tag
+ */
+ private Builder(ErrorType errorType, ErrorTag errorTag) {
+ restconfError = new RestconfError(errorType, errorTag);
+ }
+
+ /**
+ * Set an error-app-tag on the error.
+ * @param errorAppTag a tag relevant to the error in the application
+ * @return The builder
+ */
+ public Builder errorAppTag(String errorAppTag) {
+ this.restconfError.errorAppTag = errorAppTag;
+ return this;
+ }
+
+ /**
+ * Set an error-path on the error.
+ * @param errorPath A path to the resource that caused the error
+ * @return The builder
+ */
+ public Builder errorPath(String errorPath) {
+ this.restconfError.errorPath = errorPath;
+ return this;
+ }
+
+ /**
+ * Set an error-message on the error.
+ * @param errorMessage an explaination of the error
+ * @return The builder
+ */
+ public Builder errorMessage(String errorMessage) {
+ this.restconfError.errorMessage = errorMessage;
+ return this;
+ }
+
+ /**
+ * Set an error-info on the error.
+ * @param errorInfo Any additional infor about the error
+ * @return The builder
+ */
+ public Builder errorInfo(String errorInfo) {
+ this.restconfError.errorInfo = errorInfo;
+ return this;
+ }
+
+ /**
+ * Build the contents of the builder in to a {@link RestconfError}.
+ * @return A RestconfError
+ */
+ public RestconfError build() {
+ return restconfError;
+ }
+ }
+}
diff --git a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfException.java b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfException.java
index 8f12b7f..459531f 100644
--- a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfException.java
+++ b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfException.java
@@ -15,53 +15,123 @@
*/
package org.onosproject.restconf.api;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
+import java.util.ArrayList;
+import java.util.Optional;
+
import static javax.ws.rs.core.Response.Status;
/**
- * Exceptions raised during RESTCONF operations. This class extends
- * WebApplicationException. The design intention is to create a place holder
- * for RESTCONF specific errors and to be able to add more functions as the
- * subsystem grows.
+ * Exceptions raised during RESTCONF operations. This class extends WebApplicationException.
+ * To comply with the RESTCONF specification on error handling, the parameters given
+ * when creating this exception will be used to create one
+ * {@link org.onosproject.restconf.api.RestconfError}. Additional
+ * {@link org.onosproject.restconf.api.RestconfError}s may be added subsequently.
+ * This exception should be converted to a Json object using the toRestconfError()
+ * method before being passed back to the caller in a response
*/
public class RestconfException extends WebApplicationException {
// This is a randomly generated value. A WebApplicationException class is required to define it.
- private static final long SERIAL_VERSION_UID = 3275970397584007046L;
+ private static final long SERIAL_VERSION_UID = 1275970654684007046L;
+
+ private ArrayList<RestconfError> restconfErrors = new ArrayList<>();
/**
- * Constructs a new RESTCONF server error exception. The caller raising this
- * exception may pass in a HTTP error status code and an error message. The
- * error code will be displayed to the RESTCONF client as part of the
- * response from the RESTCONF server. The error message is a string which
- * may be saved in a log file and may be later retrieved by the
- * getMessage() method.
+ * Constructs a new RESTCONF server error exception based of an existing exception.
+ * The caller raising this exception may pass in a HTTP error status code
+ * and an error message. The error code will be displayed to the RESTCONF
+ * client as part of the response from the RESTCONF server. The error message
+ * is a string which may be saved in a log file and may be later retrieved by
+ * the getMessage() method. The parameters given will be formed in to a
+ * {@link org.onosproject.restconf.api.RestconfError}. Additional errors may be
+ * added after construction.
*
* @param message the detailed error message
- * @param status HTTP error status
+ * @param throwable The existing exception that caused this response.
+ * The message from this exception will be used as error-info
+ * @param errorTag A Restconf Error tag
+ * @param status HTTP error status. Developers are asked to ensure that the correct one for the error-tag is used
+ * @param errorPath An optional path that gives the item that caused the exception
* @throws IllegalArgumentException in case the status code is null or is not from
* javax.ws.rs.core.Response.Status.Family
* status code family
*/
- public RestconfException(String message, Status status) {
- super(message, null, Response.status(status).build());
+ public RestconfException(String message, Throwable throwable,
+ RestconfError.ErrorTag errorTag, Status status,
+ Optional<String> errorPath) {
+ super(message, throwable, Response.status(status).build());
+ RestconfError.Builder bldr = RestconfError
+ .builder(RestconfError.ErrorType.APPLICATION, errorTag)
+ .errorMessage(message);
+ if (throwable != null) {
+ bldr.errorInfo(throwable.getMessage());
+ }
+ if (errorPath.isPresent()) {
+ bldr.errorPath(errorPath.get());
+ }
+ addToErrors(bldr.build());
}
/**
- * Constructs a new RESTCONF server error exception. The caller raising
- * this exception may pass in the numerical value of a HTTP error
- * status code, The error code will be displayed to the RESTCONF client
- * as a response from the RESTCONF server.
+ * Constructs a new RESTCONF server error exception.
+ * The caller raising this exception may pass in a HTTP error status code
+ * and an error message. The error code will be displayed to the RESTCONF
+ * client as part of the response from the RESTCONF server. The error message
+ * is a string which may be saved in a log file and may be later retrieved by
+ * the getMessage() method. The parameters given will be formed in to a
+ * {@link org.onosproject.restconf.api.RestconfError}. Additional errors may be
+ * added after construction.
*
- * @param status HTTP error status
- * @throws IllegalArgumentException in case the status code is not a valid
- * HTTP status code or if it is not from the
+ * @param message the detailed error message
+ * @param errorTag A Restconf Error tag
+ * @param status HTTP error status. Developers are asked to ensure that the correct one for the error-tag is used
+ * @param errorPath An optional path that gives the item that caused the exception
+ * @param errorInfo An optional string with more info about the error
+ * @throws IllegalArgumentException in case the status code is null or is not from
* javax.ws.rs.core.Response.Status.Family
* status code family
*/
- public RestconfException(int status) {
- super((Throwable) null, Response.status(status).build());
+ public RestconfException(String message, RestconfError.ErrorTag errorTag,
+ Status status, Optional<String> errorPath,
+ Optional<String> errorInfo) {
+ super(message, null, Response.status(status).build());
+ RestconfError.Builder bldr = RestconfError
+ .builder(RestconfError.ErrorType.APPLICATION, errorTag)
+ .errorMessage(message);
+ if (errorInfo.isPresent()) {
+ bldr.errorInfo(errorInfo.get());
+ }
+ if (errorPath.isPresent()) {
+ bldr.errorPath(errorPath.get());
+ }
+ addToErrors(bldr.build());
+ }
+
+ /**
+ * Allows additional RestconfErrors to be added to the exception.
+ * @param restconfError An additional RestconfError to be added to the response
+ */
+ public void addToErrors(RestconfError restconfError) {
+ restconfErrors.add(restconfError);
+ }
+
+ /**
+ * Convert the RestconfException and all of its RestconfErrors in to a Json object.
+ * @return A json node generated from the RestconfException
+ */
+ public ObjectNode toRestconfErrorJson() {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode errorArray = mapper.createArrayNode();
+ restconfErrors.forEach(error -> errorArray.add(error.toJson()));
+ ObjectNode errorsNode = (ObjectNode) mapper.createObjectNode();
+ errorsNode.put("ietf-restconf:errors", errorArray);
+ return errorsNode;
}
}
diff --git a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfService.java b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfService.java
index 9f3ba53..9a12bd6 100644
--- a/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfService.java
+++ b/apps/restconf/api/src/main/java/org/onosproject/restconf/api/RestconfService.java
@@ -23,7 +23,7 @@
/**
* Abstraction of RESTCONF Server functionality according to the
- * RESTCONF RFC (no official RFC number yet).
+ * RESTCONF RFC 8040.
*/
public interface RestconfService {
/**
diff --git a/apps/restconf/api/src/test/java/org/onosproject/restconf/api/RestconfExceptionTest.java b/apps/restconf/api/src/test/java/org/onosproject/restconf/api/RestconfExceptionTest.java
new file mode 100644
index 0000000..c9a1d04
--- /dev/null
+++ b/apps/restconf/api/src/test/java/org/onosproject/restconf/api/RestconfExceptionTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.restconf.api;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Test;
+
+import javax.ws.rs.core.Response;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the conversion of RestconfErrors in to JSON.
+ */
+public class RestconfExceptionTest {
+
+ private RestconfError error1 = RestconfError
+ .builder(RestconfError.ErrorType.TRANSPORT, RestconfError.ErrorTag.ACCESS_DENIED)
+ .build();
+
+ private RestconfError error2 = RestconfError
+ .builder(RestconfError.ErrorType.TRANSPORT, RestconfError.ErrorTag.BAD_ATTRIBUTE)
+ .build();
+
+ private RestconfError error3 = RestconfError
+ .builder(RestconfError.ErrorType.RPC, RestconfError.ErrorTag.BAD_ELEMENT)
+ .errorAppTag("my-app-tag")
+ .errorMessage("a message about the error")
+ .errorPath("/a/b/c")
+ .errorInfo("info about the error")
+ .build();
+
+ /**
+ * Test a Restconf Exception with many RestconfErrors converted to Json.
+ */
+ @Test
+ public void testToRestconfErrorJson() {
+ IllegalArgumentException ie = new IllegalArgumentException("This is a test");
+ RestconfException e = new RestconfException("Error in system", ie,
+ RestconfError.ErrorTag.DATA_EXISTS, Response.Status.BAD_REQUEST,
+ Optional.of("/some/path"));
+ e.addToErrors(error1);
+ e.addToErrors(error2);
+ e.addToErrors(error3);
+
+ assertEquals("{\"ietf-restconf:errors\":[" +
+ "{\"error\":{" +
+ "\"error-type\":\"application\"," +
+ "\"error-tag\":\"data-exists\"," +
+ "\"error-path\":\"/some/path\"," +
+ "\"error-message\":\"Error in system\"," +
+ "\"error-info\":\"This is a test\"}}," +
+ "{\"error\":{" +
+ "\"error-type\":\"transport\"," +
+ "\"error-tag\":\"access-denied\"}}," +
+ "{\"error\":{" +
+ "\"error-type\":\"transport\"," +
+ "\"error-tag\":\"bad-attribute\"}}," +
+ "{\"error\":{" +
+ "\"error-type\":\"rpc\"," +
+ "\"error-tag\":\"bad-element\"," +
+ "\"error-app-tag\":\"my-app-tag\"," +
+ "\"error-path\":\"/a/b/c\"," +
+ "\"error-message\":\"a message about the error\"," +
+ "\"error-info\":\"info about the error\"}}]}",
+ e.toRestconfErrorJson().toString());
+ }
+
+ @Test
+ public void testWrappingErrorsNotInException() {
+ RestconfError error4 = RestconfError
+ .builder(RestconfError.ErrorType.TRANSPORT, RestconfError.ErrorTag.UNKNOWN_ELEMENT)
+ .build();
+
+ ObjectNode json = RestconfError.wrapErrorAsJson(Arrays.asList(error4, error1, error2, error3));
+
+ assertEquals("{\"ietf-restconf:errors\":[" +
+ "{\"error\":{" +
+ "\"error-type\":\"transport\"," +
+ "\"error-tag\":\"unknown-element\"}}," +
+ "{\"error\":{" +
+ "\"error-type\":\"transport\"," +
+ "\"error-tag\":\"access-denied\"}}," +
+ "{\"error\":{" +
+ "\"error-type\":\"transport\"," +
+ "\"error-tag\":\"bad-attribute\"}}," +
+ "{\"error\":{" +
+ "\"error-type\":\"rpc\"," +
+ "\"error-tag\":\"bad-element\"," +
+ "\"error-app-tag\":\"my-app-tag\"," +
+ "\"error-path\":\"/a/b/c\"," +
+ "\"error-message\":\"a message about the error\"," +
+ "\"error-info\":\"info about the error\"}}]}",
+ json.toString());
+
+ }
+}
diff --git a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
index 1b6a589..0ad6b25 100644
--- a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
+++ b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
@@ -28,6 +28,7 @@
import org.onosproject.config.DynamicConfigService;
import org.onosproject.config.FailedException;
import org.onosproject.config.Filter;
+import org.onosproject.restconf.api.RestconfError;
import org.onosproject.restconf.api.RestconfException;
import org.onosproject.restconf.api.RestconfRpcOutput;
import org.onosproject.restconf.api.RestconfService;
@@ -46,13 +47,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.ws.rs.core.Response;
import java.net.URI;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import static javax.ws.rs.core.Response.Status.CONFLICT;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
import static org.onosproject.d.config.ResourceIds.parentOf;
import static org.onosproject.restconf.utils.RestconfUtils.convertDataNodeToJson;
@@ -119,8 +124,9 @@
dataNode = dynamicConfigService.readNode(rl.ridForDynConfig(), filter);
} catch (FailedException e) {
log.error("ERROR: DynamicConfigService: ", e);
- throw new RestconfException("ERROR: DynamicConfigService",
- INTERNAL_SERVER_ERROR);
+ throw new RestconfException("ERROR: DynamicConfigService", e,
+ RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+ Optional.of(uri.getPath()));
}
ObjectNode rootNode = convertDataNodeToJson(rl.ridForYangRuntime(), dataNode);
return rootNode;
@@ -146,9 +152,16 @@
try {
dynamicConfigService.createNode(rl.ridForDynConfig(), dataNode);
} catch (FailedException e) {
- log.error("ERROR: DynamicConfigService: ", e);
- throw new RestconfException("ERROR: DynamicConfigService",
- INTERNAL_SERVER_ERROR);
+ if (e.getMessage().startsWith("Requested node already present")) {
+ throw new RestconfException("Already exists", e,
+ RestconfError.ErrorTag.DATA_EXISTS, CONFLICT,
+ Optional.of(uri.getPath()));
+ } else {
+ log.error("ERROR: DynamicConfigService: ", e);
+ throw new RestconfException("ERROR: DynamicConfigService", e,
+ RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+ Optional.of(uri.getPath()));
+ }
}
}
@@ -176,8 +189,9 @@
} catch (FailedException e) {
log.error("ERROR: DynamicConfigService: ", e);
- throw new RestconfException("ERROR: DynamicConfigService",
- INTERNAL_SERVER_ERROR);
+ throw new RestconfException("ERROR: DynamicConfigService", e,
+ RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+ Optional.of(uri.getPath()));
}
}
@@ -191,8 +205,9 @@
}
} catch (FailedException e) {
log.error("ERROR: DynamicConfigService: ", e);
- throw new RestconfException("ERROR: DynamicConfigService",
- INTERNAL_SERVER_ERROR);
+ throw new RestconfException("ERROR: DynamicConfigService", e,
+ RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+ Optional.of(uri.getPath()));
}
}
@@ -217,8 +232,9 @@
dynamicConfigService.updateNode(parentOf(rl.ridForDynConfig()), dataNode);
} catch (FailedException e) {
log.error("ERROR: DynamicConfigService: ", e);
- throw new RestconfException("ERROR: DynamicConfigService",
- INTERNAL_SERVER_ERROR);
+ throw new RestconfException("ERROR: DynamicConfigService", e,
+ RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+ Optional.of(uri.getPath()));
}
}
@@ -240,6 +256,10 @@
ChunkedOutput<String> output)
throws RestconfException {
//TODO: to be completed
+ throw new RestconfException("Not implemented",
+ RestconfError.ErrorTag.OPERATION_NOT_SUPPORTED,
+ Response.Status.NOT_IMPLEMENTED,
+ Optional.empty(), Optional.of("subscribeEventStream not yet implemented"));
}
@Override
@@ -267,12 +287,26 @@
} catch (InterruptedException e) {
log.error("ERROR: computeResultQ.take() has been interrupted.");
log.debug("executeRpc Exception:", e);
- restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR, null);
+ RestconfError error =
+ RestconfError.builder(RestconfError.ErrorType.RPC,
+ RestconfError.ErrorTag.OPERATION_FAILED)
+ .errorMessage("RPC execution has been interrupted")
+ .errorPath(uri.getPath())
+ .build();
+ restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR,
+ RestconfError.wrapErrorAsJson(Arrays.asList(error)));
restconfOutput.reason("RPC execution has been interrupted");
} catch (Exception e) {
log.error("ERROR: executeRpc: {}", e.getMessage());
log.debug("executeRpc Exception:", e);
- restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR, null);
+ RestconfError error =
+ RestconfError.builder(RestconfError.ErrorType.RPC,
+ RestconfError.ErrorTag.OPERATION_FAILED)
+ .errorMessage(e.getMessage())
+ .errorPath(uri.getPath())
+ .build();
+ restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR,
+ RestconfError.wrapErrorAsJson(Arrays.asList(error)));
restconfOutput.reason(e.getMessage());
}
diff --git a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
index cd0b428..41fcf39 100644
--- a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
+++ b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
@@ -20,9 +20,9 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.io.IOUtils;
import org.onlab.osgi.DefaultServiceDirectory;
+import org.onosproject.restconf.api.RestconfError;
import org.onosproject.restconf.api.RestconfException;
import org.onosproject.restconf.api.RestconfRpcOutput;
-import org.onosproject.restconf.utils.exceptions.RestconfUtilsException;
import org.onosproject.yang.model.DataNode;
import org.onosproject.yang.model.DefaultResourceData;
import org.onosproject.yang.model.ResourceData;
@@ -43,13 +43,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
+import java.util.Optional;
-import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
-import static javax.ws.rs.core.Response.Status.EXPECTATION_FAILED;
-import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
-import static javax.ws.rs.core.Response.Status.NO_CONTENT;
-import static javax.ws.rs.core.Response.Status.OK;
-import static javax.ws.rs.core.Response.Status.REQUEST_TIMEOUT;
+import static javax.ws.rs.core.Response.Status.*;
/**
* Utilities used by the RESTCONF app.
@@ -84,7 +80,9 @@
try {
rootNode = (ObjectNode) mapper.readTree(inputStream);
} catch (IOException e) {
- throw new RestconfUtilsException("ERROR: InputStream failed to parse");
+ throw new RestconfException("ERROR: InputStream failed to parse",
+ e, RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+ Optional.empty());
}
return rootNode;
}
@@ -101,7 +99,9 @@
try {
inputStream = IOUtils.toInputStream(json);
} catch (Exception e) {
- throw new RestconfUtilsException("ERROR: Json Node failed to parse");
+ throw new RestconfException("ERROR: Json Node failed to parse", e,
+ RestconfError.ErrorTag.MALFORMED_MESSAGE, BAD_REQUEST,
+ Optional.empty());
}
return inputStream;
}
@@ -142,13 +142,19 @@
// CompositeStream --- YangRuntimeService ---> CompositeData.
CompositeData compositeData = YANG_RUNTIME.decode(compositeStream, context);
resourceData = compositeData.resourceData();
+ } catch (RestconfException ex) {
+ throw ex;
} catch (Exception ex) {
log.error("convertJsonToDataNode failure: {}", ex.getMessage());
log.debug("convertJsonToDataNode failure", ex);
+ throw new RestconfException("ERROR: JSON cannot be converted to DataNode",
+ ex, RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
+ Optional.of(uri.getPath()));
}
if (resourceData == null) {
throw new RestconfException("ERROR: JSON cannot be converted to DataNode",
- INTERNAL_SERVER_ERROR);
+ RestconfError.ErrorTag.DATA_MISSING, CONFLICT,
+ Optional.of(uri.getPath()), Optional.empty());
}
return resourceData;
}
@@ -193,7 +199,8 @@
}
if (rootNode == null) {
throw new RestconfException("ERROR: InputStream can not be convert to ObjectNode",
- INTERNAL_SERVER_ERROR);
+ null, RestconfError.ErrorTag.DATA_MISSING, CONFLICT,
+ Optional.empty());
}
return rootNode;
}
diff --git a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/RestconfUtilsException.java b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/RestconfUtilsException.java
deleted file mode 100644
index b0db098..0000000
--- a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/RestconfUtilsException.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.onosproject.restconf.utils.exceptions;
-
-/**
- * Represents class of errors related to RESTCONF utilities.
- */
-public class RestconfUtilsException extends RuntimeException {
-
- /**
- * Constructs an exception with the specified message.
- *
- * @param message the message describing the specific nature of the error
- */
- public RestconfUtilsException(String message) {
- super(message);
- }
-
- /**
- * Constructs an exception with the specified message and the underlying
- * cause.
- *
- * @param message the message describing the specific nature of the error
- * @param cause the underlying cause of this error
- */
- public RestconfUtilsException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/package-info.java b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/package-info.java
deleted file mode 100644
index a0fe88e..0000000
--- a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/exceptions/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Parse utils custom exceptions.
- */
-package org.onosproject.restconf.utils.exceptions;
\ No newline at end of file