Add RFC 8040 compliant error handler
Add a new error handler that formats error responses consistent
with RFC 8040 (RESTCONF) standards.
Change-Id: I67a6ab626d943115570f2e74d0a8132933726bc8
Issue-ID: CCSDK-2482
Signed-off-by: Dan Timoney <dtimoney@att.com>
diff --git a/ms/sliboot/pom.xml b/ms/sliboot/pom.xml
index 2d15080..213f02c 100644
--- a/ms/sliboot/pom.xml
+++ b/ms/sliboot/pom.xml
@@ -210,6 +210,7 @@
<generateApiTests>true</generateApiTests>
<ignoreFileOverride>${project.basedir}/.swagger-codegen-ignore</ignoreFileOverride>
<withXml>true</withXml>
+ <templateDirectory>${project.basedir}/src/main/templates</templateDirectory>
<configOptions>
<java8>true</java8>
<springBootVersion>2.2.4-RELEASE</springBootVersion>
diff --git a/ms/sliboot/src/main/dc/docker-compose.yaml b/ms/sliboot/src/main/dc/docker-compose.yaml
index f48af3a..b1f2529 100755
--- a/ms/sliboot/src/main/dc/docker-compose.yaml
+++ b/ms/sliboot/src/main/dc/docker-compose.yaml
@@ -2,13 +2,16 @@
services:
db:
- image: mariadb:10.3
+ image: mariadb:10.5
container_name: sliboot_db_container
ports:
- "13306:3306"
environment:
- MYSQL_ROOT_PASSWORD=openECOMP1.0
- MYSQL_ROOT_HOST=%
+ - MYSQL_USER=sli
+ - MYSQL_PASSWORD=abc123
+ - MYSQL_DATABASE=sdnctl
logging:
driver: "json-file"
options:
@@ -28,7 +31,6 @@
- db:dbhost
environment:
- MYSQL_DB_HOST=dbhost
- - MYSQL_ROOT_PASSWORD=openECOMP1.0
- MYSQL_DB_USER=sli
- MYSQL_DB_PASSWD=abc123
- MYSQL_DB_DATABASE=sdnctl
diff --git a/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/App.java b/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/App.java
index d2ef5e7..b93c2f9 100644
--- a/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/App.java
+++ b/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/App.java
@@ -35,8 +35,7 @@
@SpringBootApplication
@EnableSwagger2
-@ComponentScan(basePackages = { "org.onap.ccsdk.apps.ms.sliboot.*" })
-
+@ComponentScan(basePackages = { "org.onap.ccsdk.apps.ms.sliboot.*", "org.onap.ccsdk.apps.services" })
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
diff --git a/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/WebConfig.java b/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/WebConfig.java
index 3b0b2a2..1cef316 100644
--- a/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/WebConfig.java
+++ b/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/WebConfig.java
@@ -28,10 +28,9 @@
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-@EnableWebMvc
@Configuration
@EnableJpaRepositories("org.onap.ccsdk.apps.ms.sliboot.*")
-@ComponentScan(basePackages = {"org.onap.ccsdk.apps.ms.sliboot.*"})
+@ComponentScan(basePackages = {"org.onap.ccsdk.apps.ms.sliboot.*", "org.onap.ccsdk.apps.services"})
@EntityScan("org.onap.ccsdk.apps.ms.sliboot.*")
@EnableTransactionManagement
public class WebConfig implements WebMvcConfigurer {
diff --git a/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/controllers/RestconfApiController.java b/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/controllers/RestconfApiController.java
index 4b78b2d..f37fe13 100644
--- a/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/controllers/RestconfApiController.java
+++ b/ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/controllers/RestconfApiController.java
@@ -29,6 +29,9 @@
import org.onap.ccsdk.apps.ms.sliboot.swagger.OperationsApi;
import org.onap.ccsdk.apps.ms.sliboot.data.TestResultsOperationalRepository;
import org.onap.ccsdk.apps.ms.sliboot.swagger.model.*;
+import org.onap.ccsdk.apps.services.RestApplicationException;
+import org.onap.ccsdk.apps.services.RestException;
+import org.onap.ccsdk.apps.services.RestProtocolException;
import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
import org.onap.ccsdk.sli.core.sli.SvcLogicException;
import org.onap.ccsdk.sli.core.sli.provider.base.SvcLogicServiceBase;
@@ -42,6 +45,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
@@ -49,7 +53,7 @@
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2020-02-20T12:50:11.207-05:00")
-@Controller
+@RestController
@ComponentScan(basePackages = {"org.onap.ccsdk.apps.ms.sliboot.*", "org.onap.ccsdk.apps.services"})
@EntityScan("org.onap.ccsdk.apps.ms.sliboot.*")
public class RestconfApiController implements ConfigApi, OperationalApi, OperationsApi {
@@ -273,7 +277,7 @@
}
@Override
- public ResponseEntity<SliApiTestResults> configSLIAPItestResultsGet() {
+ public ResponseEntity<SliApiTestResults> configSLIAPItestResultsGet() throws RestException {
if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
} else {
@@ -282,6 +286,10 @@
SliApiTestResults results = new SliApiTestResults();
+ if (testResultsConfigRepository.count() == 0) {
+ throw new RestApplicationException("data-missing", "Request could not be completed because the relevant data model content does not exist", 404);
+ }
+
testResultsConfigRepository.findAll().forEach(testResult -> {
SliApiTestresultsTestResult item = null;
try {
diff --git a/ms/sliboot/src/main/resources/startSliboot.sh b/ms/sliboot/src/main/resources/startSliboot.sh
index 3ca2ad6..a4cbd25 100644
--- a/ms/sliboot/src/main/resources/startSliboot.sh
+++ b/ms/sliboot/src/main/resources/startSliboot.sh
@@ -42,17 +42,17 @@
# Create tablespace and user account
-mysql -h ${MYSQL_DB_HOST} -u root -p${MYSQL_ROOT_PASSWORD} mysql <<-END
-CREATE DATABASE ${MYSQL_DB_DATABASE};
-CREATE USER '${MYSQL_DB_USER}'@'localhost' IDENTIFIED BY '${MYSQL_DB_PASSWD}';
-CREATE USER '${MYSQL_DB_USER}'@'%' IDENTIFIED BY '${MYSQL_DB_PASSWD}';
-GRANT ALL PRIVILEGES ON ${MYSQL_DB_DATABASE}.* TO '${MYSQL_DB_USER}'@'localhost' WITH GRANT OPTION;
-GRANT ALL PRIVILEGES ON ${MYSQL_DB_DATABASE}.* TO '${MYSQL_DB_USER}'@'%' WITH GRANT OPTION;
-commit;
-END
+#mysql -h ${MYSQL_DB_HOST} -u root -p${MYSQL_ROOT_PASSWORD} mysql <<-END
+#CREATE DATABASE ${MYSQL_DB_DATABASE} IF NOT EXISTS;
+#CREATE USER '${MYSQL_DB_USER}'@'localhost' IDENTIFIED BY '${MYSQL_DB_PASSWD}';
+#CREATE USER '${MYSQL_DB_USER}'@'%' IDENTIFIED BY '${MYSQL_DB_PASSWD}';
+#GRANT ALL PRIVILEGES ON ${MYSQL_DB_DATABASE}.* TO '${MYSQL_DB_USER}'@'localhost' WITH GRANT OPTION;
+#GRANT ALL PRIVILEGES ON ${MYSQL_DB_DATABASE}.* TO '${MYSQL_DB_USER}'@'%' WITH GRANT OPTION;
+#commit;
+#END
# Initialize schema
-mysql -h ${MYSQL_DB_HOST} -u ${MYSQL_DB_USER} -p${MYSQL_DB_PASSWD} ${MYSQL_DB_DATABASE} < ${CCSDK_HOME}/config/schema.sql
+# mysql -h ${MYSQL_DB_HOST} -u ${MYSQL_DB_USER} -p${MYSQL_DB_PASSWD} ${MYSQL_DB_DATABASE} < ${CCSDK_HOME}/config/schema.sql
echo -e "\nCerts ready"
diff --git a/ms/sliboot/src/main/templates/api.mustache b/ms/sliboot/src/main/templates/api.mustache
new file mode 100644
index 0000000..c28642c
--- /dev/null
+++ b/ms/sliboot/src/main/templates/api.mustache
@@ -0,0 +1,136 @@
+/**
+* NOTE: This class is auto generated by the swagger code generator program ({{{generatorVersion}}}).
+* https://github.com/swagger-api/swagger-codegen
+* Do not edit the class manually.
+*/
+package {{package}};
+
+{{#imports}}import {{import}};
+{{/imports}}
+{{#jdk8-no-delegate}}
+ import com.fasterxml.jackson.databind.ObjectMapper;
+{{/jdk8-no-delegate}}
+import io.swagger.annotations.*;
+{{#jdk8-no-delegate}}
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.springframework.http.HttpStatus;
+{{/jdk8-no-delegate}}
+import org.springframework.http.ResponseEntity;
+{{#useBeanValidation}}
+ import org.springframework.validation.annotation.Validated;
+{{/useBeanValidation}}
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.multipart.MultipartFile;
+import org.onap.ccsdk.apps.services.RestException;
+
+{{#jdk8-no-delegate}}
+ import javax.servlet.http.HttpServletRequest;
+{{/jdk8-no-delegate}}
+{{#useBeanValidation}}
+ import javax.validation.Valid;
+ import javax.validation.constraints.*;
+{{/useBeanValidation}}
+{{#jdk8-no-delegate}}
+ import java.io.IOException;
+{{/jdk8-no-delegate}}
+import java.util.List;
+{{#jdk8-no-delegate}}
+ import java.util.Optional;
+{{/jdk8-no-delegate}}
+{{^jdk8-no-delegate}}
+ {{#useOptional}}
+ import java.util.Optional;
+ {{/useOptional}}
+{{/jdk8-no-delegate}}
+{{#async}}
+ import java.util.concurrent.{{^jdk8}}Callable{{/jdk8}}{{#jdk8}}CompletableFuture{{/jdk8}};
+{{/async}}
+{{>generatedAnnotation}}
+@Api(value = "{{{baseName}}}", description = "the {{{baseName}}} API")
+{{#operations}}
+ public interface {{classname}} {
+ {{#jdk8}}
+
+ {{^isDelegate}}
+ Logger log = LoggerFactory.getLogger({{classname}}.class);
+
+ default Optional<ObjectMapper> getObjectMapper() {
+ return Optional.empty();
+ }
+
+ default Optional<HttpServletRequest> getRequest() {
+ return Optional.empty();
+ }
+
+ default Optional<String> getAcceptHeader() {
+ return getRequest().map(r -> r.getHeader("Accept"));
+ }
+ {{/isDelegate}}
+ {{#isDelegate}}
+ {{classname}}Delegate getDelegate();
+ {{/isDelegate}}
+ {{/jdk8}}
+ {{#operation}}
+
+ @ApiOperation(value = "{{{summary}}}", nickname = "{{{operationId}}}", notes = "{{{notes}}}"{{#returnBaseType}}, response = {{{returnBaseType}}}.class{{/returnBaseType}}{{#returnContainer}}, responseContainer = "{{{returnContainer}}}"{{/returnContainer}}{{#hasAuthMethods}}, authorizations = {
+ {{#authMethods}}@Authorization(value = "{{name}}"{{#isOAuth}}, scopes = {
+ {{#scopes}}@AuthorizationScope(scope = "{{scope}}", description = "{{description}}"){{#hasMore}},
+ {{/hasMore}}{{/scopes}}
+ }{{/isOAuth}}){{#hasMore}},
+ {{/hasMore}}{{/authMethods}}
+ }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}"{{tag}}",{{/vendorExtensions.x-tags}} })
+ @ApiResponses(value = { {{#responses}}
+ @ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{baseType}}}.class{{/baseType}}{{#containerType}}, responseContainer = "{{{containerType}}}"{{/containerType}}){{#hasMore}},{{/hasMore}}{{/responses}} })
+ {{#implicitHeaders}}
+ @ApiImplicitParams({
+ {{#headerParams}}
+ {{>implicitHeader}}
+ {{/headerParams}}
+ })
+ {{/implicitHeaders}}
+ @RequestMapping(value = "{{{path}}}",{{#singleContentTypes}}
+ produces = "{{{vendorExtensions.x-accepts}}}",
+ consumes = "{{{vendorExtensions.x-contentType}}}",{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}}
+ produces = { {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} }, {{/hasProduces}}{{#hasConsumes}}
+ consumes = { {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} },{{/hasConsumes}}{{/singleContentTypes}}
+ method = RequestMethod.{{httpMethod}})
+ {{#jdk8}}default {{/jdk8}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{#delegate-method}}_{{/delegate-method}}{{operationId}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{#hasMore}},{{/hasMore}}{{/allParams}}){{^jdk8}};{{/jdk8}}{{#jdk8}} throws RestException {
+ {{#delegate-method}}
+ return {{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});
+ }
+
+ // Override this method
+ default {{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}({{#allParams}}{{^isFile}}{{{dataType}}}{{/isFile}}{{#isFile}}MultipartFile{{/isFile}} {{paramName}}{{#hasMore}},{{/hasMore}}{{/allParams}}) throws RestException {
+ {{/delegate-method}}
+ {{^isDelegate}}
+ if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
+ {{#examples}}
+ if (getAcceptHeader().get().contains("{{{contentType}}}")) {
+ try {
+ return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(getObjectMapper().get().readValue("{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{example}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}", {{>exampleReturnTypes}}.class), HttpStatus.NOT_IMPLEMENTED){{#async}}){{/async}};
+ } catch (IOException e) {
+ log.error("Couldn't serialize response for content type {{{contentType}}}", e);
+ return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR){{#async}}){{/async}};
+ }
+ }
+ {{/examples}}
+ } else {
+ log.warn("ObjectMapper or HttpServletRequest not configured in default {{classname}} interface so no example is generated");
+ }
+ return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED){{#async}}){{/async}};
+ {{/isDelegate}}
+ {{#isDelegate}}
+ return getDelegate().{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});
+ {{/isDelegate}}
+ }{{/jdk8}}
+
+ {{/operation}}
+ }
+{{/operations}}
\ No newline at end of file
diff --git a/ms/sliboot/src/test/java/org/onap/ccsdk/apps/ms/sliboot/RestconfApiControllerTest.java b/ms/sliboot/src/test/java/org/onap/ccsdk/apps/ms/sliboot/RestconfApiControllerTest.java
index 4a7a7ec..7d38088 100644
--- a/ms/sliboot/src/test/java/org/onap/ccsdk/apps/ms/sliboot/RestconfApiControllerTest.java
+++ b/ms/sliboot/src/test/java/org/onap/ccsdk/apps/ms/sliboot/RestconfApiControllerTest.java
@@ -123,14 +123,15 @@
public void testTestResultAdd() throws Exception {
String url = "/config/SLI-API:test-results/";
- MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(url).contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
-
- assertEquals(200, mvcResult.getResponse().getStatus());
-
// Delete any existing content before testing insert
- mvcResult = mvc.perform(MockMvcRequestBuilders.delete(url).contentType(MediaType.APPLICATION_JSON)).andReturn();
+ MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.delete(url).contentType(MediaType.APPLICATION_JSON)).andReturn();
assertEquals(200, mvcResult.getResponse().getStatus());
+ mvcResult = mvc.perform(MockMvcRequestBuilders.get(url).contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+
+ assertEquals(404, mvcResult.getResponse().getStatus());
+ log.info("Empty test-results returns error - {}", mvcResult.getResponse().getContentAsString());
+
String jsonString = "{\n" +
" \"test-result\" : [\n" +
" {\n" +
@@ -146,6 +147,7 @@
mvcResult = mvc.perform(MockMvcRequestBuilders.get(url).contentType(MediaType.APPLICATION_JSON)).andReturn();
assertEquals(200, mvcResult.getResponse().getStatus());
+
ObjectMapper objectMapper = new ObjectMapper();
SliApiTestResults testResults = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), SliApiTestResults.class);
assertNotNull(testResults);