From c4db69b9b00b15a7ecc797bee5da1d64067220f9 Mon Sep 17 00:00:00 2001 From: FrancescoFioraEst Date: Fri, 5 Mar 2021 14:40:57 +0000 Subject: Add Instantiation CL Rest Controller Issue-ID: POLICY-2980 Signed-off-by: FrancescoFioraEst Change-Id: Ic4c1fd8291dcdcdfd3a5145c545766a9dd6afbb3 --- .../common/handler/ControlLoopHandler.java | 10 + .../common/handler/DummyControlLoopHandler.java | 10 + .../instantiation/InstantiationHandler.java | 109 ++++++ .../rest/InstantiationController.java | 416 +++++++++++++++++++++ .../runtime/main/rest/ControlLoopAafFilter.java | 38 ++ .../runtime/main/rest/RestController.java | 115 ++++++ .../runtime/main/startstop/ClRuntimeActivator.java | 42 +++ .../rest/InstantiationControllerTest.java | 316 ++++++++++++++++ .../runtime/main/rest/RestControllerTest.java | 71 ++++ .../main/startstop/ClRuntimeActivatorTest.java | 3 +- .../runtime/util/rest/CommonRestController.java | 263 +++++++++++++ 11 files changed, 1392 insertions(+), 1 deletion(-) create mode 100644 tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/InstantiationHandler.java create mode 100644 tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationController.java create mode 100644 tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/ControlLoopAafFilter.java create mode 100644 tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestController.java create mode 100644 tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationControllerTest.java create mode 100644 tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestControllerTest.java create mode 100644 tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/util/rest/CommonRestController.java diff --git a/tosca-controlloop/common/src/main/java/org/onap/policy/clamp/controlloop/common/handler/ControlLoopHandler.java b/tosca-controlloop/common/src/main/java/org/onap/policy/clamp/controlloop/common/handler/ControlLoopHandler.java index 2751f2441..dff4c6b58 100644 --- a/tosca-controlloop/common/src/main/java/org/onap/policy/clamp/controlloop/common/handler/ControlLoopHandler.java +++ b/tosca-controlloop/common/src/main/java/org/onap/policy/clamp/controlloop/common/handler/ControlLoopHandler.java @@ -78,6 +78,11 @@ public abstract class ControlLoopHandler { */ public abstract void startAndRegisterPublishers(List topicSinks); + /** + * Start any providers for this handler. + */ + public abstract void startProviders(); + /** * Stop any topic message publishers for this handler. */ @@ -89,4 +94,9 @@ public abstract class ControlLoopHandler { * @param msgDispatcher the message dispatcher from which to unregister the listener */ public abstract void stopAndUnregisterListeners(MessageTypeDispatcher msgDispatcher); + + /** + * Stop any providers for this handler. + */ + public abstract void stopProviders(); } diff --git a/tosca-controlloop/common/src/test/java/org/onap/policy/clamp/controlloop/common/handler/DummyControlLoopHandler.java b/tosca-controlloop/common/src/test/java/org/onap/policy/clamp/controlloop/common/handler/DummyControlLoopHandler.java index fb26333b4..1602fb6eb 100644 --- a/tosca-controlloop/common/src/test/java/org/onap/policy/clamp/controlloop/common/handler/DummyControlLoopHandler.java +++ b/tosca-controlloop/common/src/test/java/org/onap/policy/clamp/controlloop/common/handler/DummyControlLoopHandler.java @@ -50,4 +50,14 @@ public class DummyControlLoopHandler extends ControlLoopHandler { public void stopAndUnregisterListeners(MessageTypeDispatcher msgDispatcher) { // Do nothing on this dummy class } + + @Override + public void startProviders() { + // Do nothing on this dummy class + } + + @Override + public void stopProviders() { + // Do nothing on this dummy class + } } diff --git a/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/InstantiationHandler.java b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/InstantiationHandler.java new file mode 100644 index 000000000..c4b0955f9 --- /dev/null +++ b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/InstantiationHandler.java @@ -0,0 +1,109 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.controlloop.runtime.instantiation; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.ws.rs.core.Response; +import lombok.Getter; +import org.onap.policy.clamp.controlloop.common.handler.ControlLoopHandler; +import org.onap.policy.clamp.controlloop.runtime.instantiation.rest.InstantiationController; +import org.onap.policy.clamp.controlloop.runtime.main.parameters.ClRuntimeParameterGroup; +import org.onap.policy.common.endpoints.event.comm.TopicSink; +import org.onap.policy.common.endpoints.listeners.MessageTypeDispatcher; +import org.onap.policy.common.utils.services.Registry; +import org.onap.policy.models.base.PfModelRuntimeException; + +/** + * This class handles instantiation of control loop instances, + * so only one object of this type should be built at a time. + * + *

+ * It is effectively a singleton that is started at system start + */ +public final class InstantiationHandler extends ControlLoopHandler { + + @Getter + private ControlLoopInstantiationProvider controlLoopInstantiationProvider; + + /** + * Gets the InstantiationHandler. + * + * @return InstantiationHandler + */ + public static InstantiationHandler getInstance() { + return Registry.get(InstantiationHandler.class.getName()); + } + + /** + * Create a handler. + * + * @param controlLoopParameters the parameters for access to the database + */ + public InstantiationHandler(ClRuntimeParameterGroup controlLoopParameters) { + super(controlLoopParameters.getDatabaseProviderParameters()); + } + + @Override + public Set> getProviderClasses() { + Set> providerClasses = new HashSet<>(); + + providerClasses.add(InstantiationController.class); + + return providerClasses; + } + + @Override + public void startAndRegisterListeners(MessageTypeDispatcher msgDispatcher) { + // No topic communication on this handler + } + + @Override + public void startAndRegisterPublishers(List topicSinks) { + // No topic communication on this handler + } + + @Override + public void stopAndUnregisterPublishers() { + // No topic communication on this handler + } + + @Override + public void stopAndUnregisterListeners(MessageTypeDispatcher msgDispatcher) { + // No topic communication on this handler + } + + @Override + public void startProviders() { + controlLoopInstantiationProvider = new ControlLoopInstantiationProvider(getDatabaseProviderParameters()); + } + + @Override + public void stopProviders() { + try { + controlLoopInstantiationProvider.close(); + } catch (IOException e) { + throw new PfModelRuntimeException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()); + } + } +} diff --git a/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationController.java b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationController.java new file mode 100644 index 000000000..807da5d68 --- /dev/null +++ b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationController.java @@ -0,0 +1,416 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.controlloop.runtime.instantiation.rest; + +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; +import io.swagger.annotations.Extension; +import io.swagger.annotations.ExtensionProperty; +import io.swagger.annotations.ResponseHeader; +import java.util.UUID; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import org.onap.policy.clamp.controlloop.common.exception.ControlLoopException; +import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoops; +import org.onap.policy.clamp.controlloop.models.messages.rest.instantiation.InstantiationCommand; +import org.onap.policy.clamp.controlloop.models.messages.rest.instantiation.InstantiationResponse; +import org.onap.policy.clamp.controlloop.runtime.instantiation.ControlLoopInstantiationProvider; +import org.onap.policy.clamp.controlloop.runtime.instantiation.InstantiationHandler; +import org.onap.policy.clamp.controlloop.runtime.main.rest.RestController; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.base.PfModelRuntimeException; +import org.onap.policy.models.errors.concepts.ErrorResponseInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to provide REST end points for creating, deleting, query and commanding a control loop definition. + */ +public class InstantiationController extends RestController { + + private static final Logger LOGGER = LoggerFactory.getLogger(InstantiationController.class); + + // The CL provider for instantiation requests + private final ControlLoopInstantiationProvider provider; + + /** + * create Instantiation Controller. + */ + public InstantiationController() { + this.provider = InstantiationHandler.getInstance().getControlLoopInstantiationProvider(); + } + + /** + * Creates a control loop. + * + * @param requestId request ID used in ONAP logging + * @param controlLoops the control loops + * @return a response + */ + // @formatter:off + @POST + @Path("/instantiation") + @ApiOperation( + value = "Commissions control loop definitions", + notes = "Commissions control loop definitions, returning the control loop IDs", + response = InstantiationResponse.class, + tags = { + "Control Loop Instantiation API" + }, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class) + }, + extensions = { + @Extension( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public Response create( + @HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Entity Body of Control Loop", required = true) ControlLoops controlLoops) { + + try { + InstantiationResponse response = provider.createControlLoops(controlLoops); + return addLoggingHeaders(addVersionControlHeaders(Response.status(Status.OK)), requestId).entity(response) + .build(); + + } catch (PfModelRuntimeException | PfModelException e) { + LOGGER.warn("creation of control loop failed", e); + return createInstantiationErrorResponse(e, requestId); + } + } + + /** + * Queries details of all control loops. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the control loop to get, null for all control loops + * @param version the version of the control loop to get, null for all control loops + * @return the control loops + */ + // @formatter:off + @GET + @Path("/instantiation") + @ApiOperation(value = "Query details of the requested control loops", + notes = "Queries details of the requested control loops, returning all control loop details", + response = ControlLoops.class, + tags = { + "Clamp control loop Instantiation API" + }, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public Response query( + @HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Control Loop definition name", required = true) @QueryParam("name") String name, + @ApiParam(value = "Control Loop definition version", + required = true) @QueryParam("version") String version) { + + try { + ControlLoops response = provider.getControlLoops(name, version); + return addLoggingHeaders(addVersionControlHeaders(Response.status(Status.OK)), requestId).entity(response) + .build(); + + } catch (PfModelRuntimeException | PfModelException e) { + LOGGER.warn("commisssioning of control loop failed", e); + return createInstantiationErrorResponse(e, requestId); + } + + } + + /** + * Updates a control loop. + * + * @param requestId request ID used in ONAP logging + * @param controlLoops the control loops + * @return a response + */ + // @formatter:off + @PUT + @Path("/instantiation") + @ApiOperation( + value = "Updates control loop definitions", + notes = "Updates control loop definitions, returning the updated control loop definition IDs", + response = InstantiationResponse.class, + tags = { + "Control Loop Instantiation API" + }, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class) + }, + extensions = { + @Extension( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public Response update( + @HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Entity Body of Control Loop", required = true) ControlLoops controlLoops) { + + try { + InstantiationResponse response = provider.updateControlLoops(controlLoops); + return addLoggingHeaders(addVersionControlHeaders(Response.status(Status.OK)), requestId).entity(response) + .build(); + + } catch (PfModelRuntimeException | PfModelException e) { + LOGGER.warn("update of control loops failed", e); + return createInstantiationErrorResponse(e, requestId); + } + } + + /** + * Deletes a control loop definition. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the control loop to delete + * @param version the version of the control loop to delete + * @return a response + */ + // @formatter:off + @DELETE + @Path("/instantiation") + @ApiOperation(value = "Delete a control loop", + notes = "Deletes a control loop, returning optional error details", + response = InstantiationResponse.class, + tags = { + "Clamp Control Loop Instantiation API" + }, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses(value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + + public Response delete( + @HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Control Loop definition name", required = true) @QueryParam("name") String name, + @ApiParam(value = "Control Loop definition version", required = true) + @QueryParam("version") String version) { + + try { + InstantiationResponse response = provider.deleteControlLoop(name, version); + return addLoggingHeaders(addVersionControlHeaders(Response.status(Status.OK)), requestId).entity(response) + .build(); + + } catch (PfModelRuntimeException | PfModelException e) { + LOGGER.warn("delete of control loop failed", e); + return createInstantiationErrorResponse(e, requestId); + } + } + + /** + * Issues control loop commands to control loops. + * + * @param requestId request ID used in ONAP logging + * @param command the command to issue to control loops + * @return the control loop definitions + */ + // @formatter:off + @PUT + @Path("/instantiation/command") + @ApiOperation(value = "Issue a command to the requested control loops", + notes = "Issues a command to a control loop, ordering a state change on the control loop", + response = InstantiationResponse.class, + tags = { + "Clamp Control Loop Instantiation API" + }, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public Response issueControlLoopCommand( + @HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Entity Body of control loop command", required = true) InstantiationCommand command) { + + try { + InstantiationResponse response = provider.issueControlLoopCommand(command); + return addLoggingHeaders(addVersionControlHeaders(Response.status(Status.ACCEPTED)), requestId) + .entity(response).build(); + + } catch (PfModelRuntimeException | PfModelException | ControlLoopException e) { + LOGGER.warn("creation of control loop failed", e); + return createInstantiationErrorResponse(e, requestId); + } + } + + /** + * create a Instantiation Response from an exception. + * @param e the error + * @param requestId request ID used in ONAP logging + * @return the Instantiation Response + */ + private Response createInstantiationErrorResponse(ErrorResponseInfo e, UUID requestId) { + InstantiationResponse resp = new InstantiationResponse(); + resp.setErrorDetails(e.getErrorResponse().getErrorMessage()); + return addLoggingHeaders(addVersionControlHeaders(Response.status(e.getErrorResponse().getResponseCode())), + requestId).entity(resp).build(); + } + +} diff --git a/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/ControlLoopAafFilter.java b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/ControlLoopAafFilter.java new file mode 100644 index 000000000..f166de5d6 --- /dev/null +++ b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/ControlLoopAafFilter.java @@ -0,0 +1,38 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.controlloop.runtime.main.rest; + +import org.onap.policy.common.endpoints.http.server.aaf.AafGranularAuthFilter; +import org.onap.policy.common.utils.resources.MessageConstants; + +/** + * Class to manage AAF filters for the control loop runtime component. + */ +public class ControlLoopAafFilter extends AafGranularAuthFilter { + + public static final String AAF_NODETYPE = MessageConstants.POLICY_CLAMP; + public static final String AAF_ROOT_PERMISSION = DEFAULT_NAMESPACE + "." + AAF_NODETYPE; + + @Override + public String getPermissionTypeRoot() { + return AAF_ROOT_PERMISSION; + } +} diff --git a/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestController.java b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestController.java new file mode 100644 index 000000000..dd3fa30fc --- /dev/null +++ b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestController.java @@ -0,0 +1,115 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.controlloop.runtime.main.rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.BasicAuthDefinition; +import io.swagger.annotations.Info; +import io.swagger.annotations.SecurityDefinition; +import io.swagger.annotations.SwaggerDefinition; +import io.swagger.annotations.Tag; +import java.net.HttpURLConnection; +import java.util.UUID; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.ResponseBuilder; + +/** + * Common superclass to provide REST endpoints for the control loop service. + */ +// @formatter:off +@Path("/onap/controlloop/v2") +@Api(value = "Control Loop API") +@Produces({MediaType.APPLICATION_JSON, RestController.APPLICATION_YAML}) +@SwaggerDefinition( + info = @Info(description = + "Control Loop Service", version = "v1.0", + title = "Control Loop"), + consumes = {MediaType.APPLICATION_JSON, RestController.APPLICATION_YAML}, + produces = {MediaType.APPLICATION_JSON, RestController.APPLICATION_YAML}, + schemes = {SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS}, + tags = {@Tag(name = "controlloop", description = "Control Loop Service")}, + securityDefinition = @SecurityDefinition(basicAuthDefinitions = {@BasicAuthDefinition(key = "basicAuth")})) +// @formatter:on +public class RestController { + public static final String APPLICATION_YAML = "application/yaml"; + + public static final String EXTENSION_NAME = "interface info"; + + public static final String API_VERSION_NAME = "api-version"; + public static final String API_VERSION = "1.0.0"; + + public static final String LAST_MOD_NAME = "last-mod-release"; + public static final String LAST_MOD_RELEASE = "Dublin"; + + public static final String VERSION_MINOR_NAME = "X-MinorVersion"; + public static final String VERSION_MINOR_DESCRIPTION = + "Used to request or communicate a MINOR version back from the client" + + " to the server, and from the server back to the client"; + + public static final String VERSION_PATCH_NAME = "X-PatchVersion"; + public static final String VERSION_PATCH_DESCRIPTION = "Used only to communicate a PATCH version in a response for" + + " troubleshooting purposes only, and will not be provided by" + " the client on request"; + + public static final String VERSION_LATEST_NAME = "X-LatestVersion"; + public static final String VERSION_LATEST_DESCRIPTION = "Used only to communicate an API's latest version"; + + public static final String REQUEST_ID_NAME = "X-ONAP-RequestID"; + public static final String REQUEST_ID_HDR_DESCRIPTION = "Used to track REST transactions for logging purpose"; + public static final String REQUEST_ID_PARAM_DESCRIPTION = "RequestID for http transaction"; + + public static final String AUTHORIZATION_TYPE = "basicAuth"; + + public static final int AUTHENTICATION_ERROR_CODE = HttpURLConnection.HTTP_UNAUTHORIZED; + public static final int AUTHORIZATION_ERROR_CODE = HttpURLConnection.HTTP_FORBIDDEN; + public static final int SERVER_ERROR_CODE = HttpURLConnection.HTTP_INTERNAL_ERROR; + + public static final String AUTHENTICATION_ERROR_MESSAGE = "Authentication Error"; + public static final String AUTHORIZATION_ERROR_MESSAGE = "Authorization Error"; + public static final String SERVER_ERROR_MESSAGE = "Internal Server Error"; + + /** + * Adds version headers to the response. + * + * @param respBuilder response builder + * @return the response builder, with version headers + */ + public ResponseBuilder addVersionControlHeaders(ResponseBuilder respBuilder) { + return respBuilder.header(VERSION_MINOR_NAME, "0").header(VERSION_PATCH_NAME, "0").header(VERSION_LATEST_NAME, + API_VERSION); + } + + /** + * Adds logging headers to the response. + * + * @param respBuilder response builder + * @return the response builder, with version logging + */ + public ResponseBuilder addLoggingHeaders(ResponseBuilder respBuilder, UUID requestId) { + if (requestId == null) { + // Generate a random uuid if client does not embed requestId in rest request + return respBuilder.header(REQUEST_ID_NAME, UUID.randomUUID()); + } + + return respBuilder.header(REQUEST_ID_NAME, requestId); + } +} diff --git a/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivator.java b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivator.java index 8ac4d684d..d3a5c5810 100644 --- a/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivator.java +++ b/tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivator.java @@ -21,11 +21,17 @@ package org.onap.policy.clamp.controlloop.runtime.main.startstop; import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.Response.Status; import org.onap.policy.clamp.controlloop.common.exception.ControlLoopRuntimeException; +import org.onap.policy.clamp.controlloop.runtime.instantiation.InstantiationHandler; import org.onap.policy.clamp.controlloop.runtime.main.parameters.ClRuntimeParameterGroup; +import org.onap.policy.clamp.controlloop.runtime.main.rest.ControlLoopAafFilter; import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.common.endpoints.event.comm.TopicSink; import org.onap.policy.common.endpoints.event.comm.TopicSource; +import org.onap.policy.common.endpoints.http.server.RestServer; import org.onap.policy.common.endpoints.listeners.MessageTypeDispatcher; import org.onap.policy.common.parameters.ParameterService; import org.onap.policy.common.utils.services.ServiceManagerContainer; @@ -41,6 +47,7 @@ public class ClRuntimeActivator extends ServiceManagerContainer { private final ClRuntimeParameterGroup clRuntimeParameterGroup; // Topics from which the application receives and to which the application sends messages + private List topicSinks; private List topicSources; /** @@ -61,6 +68,9 @@ public class ClRuntimeActivator extends ServiceManagerContainer { this.clRuntimeParameterGroup = clRuntimeParameterGroup; + topicSinks = TopicEndpointManager.getManager() + .addTopicSinks(clRuntimeParameterGroup.getTopicParameterGroup().getTopicSinks()); + topicSources = TopicEndpointManager.getManager() .addTopicSources(clRuntimeParameterGroup.getTopicParameterGroup().getTopicSources()); @@ -71,6 +81,9 @@ public class ClRuntimeActivator extends ServiceManagerContainer { "topic message dispatcher failed to start", e); } + final AtomicReference instantiationHandler = new AtomicReference<>(); + final AtomicReference restServer = new AtomicReference<>(); + // @formatter:off addAction("Control loop runtime parameters", () -> ParameterService.register(clRuntimeParameterGroup), @@ -80,9 +93,38 @@ public class ClRuntimeActivator extends ServiceManagerContainer { () -> TopicEndpointManager.getManager().start(), () -> TopicEndpointManager.getManager().shutdown()); + addAction("Instantiation Handler", + () -> instantiationHandler.set(new InstantiationHandler(clRuntimeParameterGroup)), + () -> instantiationHandler.get().close()); + + addAction("Providers", + () -> instantiationHandler.get().startProviders(), + () -> instantiationHandler.get().stopProviders()); + + addAction("Listeners", + () -> instantiationHandler.get().startAndRegisterListeners(msgDispatcher), + () -> instantiationHandler.get().stopAndUnregisterListeners(msgDispatcher)); + + addAction("Publishers", + () -> instantiationHandler.get().startAndRegisterPublishers(topicSinks), + () -> instantiationHandler.get().stopAndUnregisterPublishers()); + addAction("Topic Message Dispatcher", this::registerMsgDispatcher, this::unregisterMsgDispatcher); clRuntimeParameterGroup.getRestServerParameters().setName(clRuntimeParameterGroup.getName()); + + addAction("REST server", + () -> { + Set> providerClasses = instantiationHandler.get().getProviderClasses(); + + RestServer server = new RestServer(clRuntimeParameterGroup.getRestServerParameters(), + ControlLoopAafFilter.class, + providerClasses.toArray(new Class[providerClasses.size()])); + + restServer.set(server); + restServer.get().start(); + }, + () -> restServer.get().stop()); // @formatter:on } diff --git a/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationControllerTest.java b/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationControllerTest.java new file mode 100644 index 000000000..9244c7ad7 --- /dev/null +++ b/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationControllerTest.java @@ -0,0 +1,316 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.controlloop.runtime.instantiation.rest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.core.Response; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoop; +import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoops; +import org.onap.policy.clamp.controlloop.models.messages.rest.instantiation.InstantiationCommand; +import org.onap.policy.clamp.controlloop.models.messages.rest.instantiation.InstantiationResponse; +import org.onap.policy.clamp.controlloop.runtime.instantiation.ControlLoopInstantiationProvider; +import org.onap.policy.clamp.controlloop.runtime.instantiation.InstantiationUtils; +import org.onap.policy.clamp.controlloop.runtime.util.rest.CommonRestController; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; + +/** + * Class to perform unit test of {@link InstantiationController}}. + * + */ +public class InstantiationControllerTest extends CommonRestController { + + private static final String CL_INSTANTIATION_CREATE_JSON = "src/test/resources/rest/controlloops/ControlLoops.json"; + + private static final String CL_INSTANTIATION_UPDATE_JSON = + "src/test/resources/rest/controlloops/ControlLoopsUpdate.json"; + + private static final String CL_INSTANTIATION_CHANGE_STATE_JSON = + "src/test/resources/rest/controlloops/PassiveCommand.json"; + + private static final String INSTANTIATION_ENDPOINT = "instantiation"; + + private static final String INSTANTIATION_COMMAND_ENDPOINT = "instantiation/command"; + + /** + * starts Main and inserts a commissioning template. + * + * @throws Exception if an error occurs + */ + @BeforeClass + public static void setUpBeforeClass() throws Exception { + CommonRestController.setUpBeforeClass("InstApi"); + + ControlLoops controlLoops = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_CREATE_JSON, "Command"); + try (ControlLoopInstantiationProvider instantiationProvider = + new ControlLoopInstantiationProvider(getParameters())) { + instantiationProvider.createControlLoops(controlLoops); + } + } + + @AfterClass + public static void teardownAfterClass() { + CommonRestController.teardownAfterClass(); + } + + @Test + public void testSwagger() throws Exception { + super.testSwagger(INSTANTIATION_ENDPOINT); + } + + @Test + public void testCreate_Unauthorized() throws Exception { + ControlLoops controlLoops = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_CREATE_JSON, "Unauthorized"); + + assertUnauthorizedPost(INSTANTIATION_ENDPOINT, Entity.json(controlLoops)); + } + + @Test + public void testQuery_Unauthorized() throws Exception { + assertUnauthorizedGet(INSTANTIATION_ENDPOINT); + } + + @Test + public void testUpdate_Unauthorized() throws Exception { + ControlLoops controlLoops = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_UPDATE_JSON, "Unauthorized"); + + assertUnauthorizedPut(INSTANTIATION_ENDPOINT, Entity.json(controlLoops)); + } + + @Test + public void testDelete_Unauthorized() throws Exception { + assertUnauthorizedDelete(INSTANTIATION_ENDPOINT); + } + + @Test + public void testComand_Unauthorized() throws Exception { + InstantiationCommand instantiationCommand = InstantiationUtils + .getInstantiationCommandFromResource(CL_INSTANTIATION_CHANGE_STATE_JSON, "Unauthorized"); + + assertUnauthorizedPut(INSTANTIATION_COMMAND_ENDPOINT, Entity.json(instantiationCommand)); + } + + @Test + public void testCreate() throws Exception { + ControlLoops controlLoopsFromRsc = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_CREATE_JSON, "Create"); + + Invocation.Builder invocationBuilder = super.sendRequest(INSTANTIATION_ENDPOINT); + Response resp = invocationBuilder.post(Entity.json(controlLoopsFromRsc)); + assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus()); + InstantiationResponse instResponse = resp.readEntity(InstantiationResponse.class); + InstantiationUtils.assertInstantiationResponse(instResponse, controlLoopsFromRsc); + + try (ControlLoopInstantiationProvider instantiationProvider = + new ControlLoopInstantiationProvider(getParameters())) { + for (ControlLoop controlLoopFromRsc : controlLoopsFromRsc.getControlLoopList()) { + ControlLoops controlLoopsFromDb = instantiationProvider.getControlLoops( + controlLoopFromRsc.getKey().getName(), controlLoopFromRsc.getKey().getVersion()); + + assertNotNull(controlLoopsFromDb); + assertThat(controlLoopsFromDb.getControlLoopList()).hasSize(1); + assertEquals(controlLoopFromRsc, controlLoopsFromDb.getControlLoopList().get(0)); + } + } + } + + @Test + public void testCreateBadRequest() throws Exception { + ControlLoops controlLoopsFromRsc = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_CREATE_JSON, "CreateBadRequest"); + + Invocation.Builder invocationBuilder = super.sendRequest(INSTANTIATION_ENDPOINT); + Response resp = invocationBuilder.post(Entity.json(controlLoopsFromRsc)); + assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus()); + + // testing Bad Request: CL already defined + resp = invocationBuilder.post(Entity.json(controlLoopsFromRsc)); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), resp.getStatus()); + InstantiationResponse instResponse = resp.readEntity(InstantiationResponse.class); + assertNotNull(instResponse.getErrorDetails()); + assertNull(instResponse.getAffectedControlLoops()); + } + + @Test + public void testQuery_NoResultWithThisName() throws Exception { + Invocation.Builder invocationBuilder = super.sendRequest(INSTANTIATION_ENDPOINT + "?name=noResultWithThisName"); + Response rawresp = invocationBuilder.buildGet().invoke(); + assertEquals(Response.Status.OK.getStatusCode(), rawresp.getStatus()); + ControlLoops resp = rawresp.readEntity(ControlLoops.class); + assertThat(resp.getControlLoopList()).isEmpty(); + } + + @Test + public void testQuery() throws Exception { + // inserts a ControlLoops to DB + ControlLoops controlLoops = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_CREATE_JSON, "Query"); + try (ControlLoopInstantiationProvider instantiationProvider = + new ControlLoopInstantiationProvider(getParameters())) { + instantiationProvider.createControlLoops(controlLoops); + } + + for (ControlLoop controlLoopFromRsc : controlLoops.getControlLoopList()) { + Invocation.Builder invocationBuilder = + super.sendRequest(INSTANTIATION_ENDPOINT + "?name=" + controlLoopFromRsc.getKey().getName()); + Response rawresp = invocationBuilder.buildGet().invoke(); + assertEquals(Response.Status.OK.getStatusCode(), rawresp.getStatus()); + ControlLoops controlLoopsQuery = rawresp.readEntity(ControlLoops.class); + assertNotNull(controlLoopsQuery); + assertThat(controlLoopsQuery.getControlLoopList()).hasSize(1); + assertEquals(controlLoopFromRsc, controlLoopsQuery.getControlLoopList().get(0)); + } + } + + @Test + public void testUpdate() throws Exception { + ControlLoops controlLoopsCreate = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_CREATE_JSON, "Update"); + + ControlLoops controlLoops = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_UPDATE_JSON, "Update"); + + try (ControlLoopInstantiationProvider instantiationProvider = + new ControlLoopInstantiationProvider(getParameters())) { + instantiationProvider.createControlLoops(controlLoopsCreate); + + Invocation.Builder invocationBuilder = super.sendRequest(INSTANTIATION_ENDPOINT); + Response resp = invocationBuilder.put(Entity.json(controlLoops)); + assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus()); + + InstantiationResponse instResponse = resp.readEntity(InstantiationResponse.class); + InstantiationUtils.assertInstantiationResponse(instResponse, controlLoops); + + for (ControlLoop controlLoopUpdate : controlLoops.getControlLoopList()) { + ControlLoops controlLoopsFromDb = instantiationProvider + .getControlLoops(controlLoopUpdate.getKey().getName(), controlLoopUpdate.getKey().getVersion()); + + assertNotNull(controlLoopsFromDb); + assertThat(controlLoopsFromDb.getControlLoopList()).hasSize(1); + assertEquals(controlLoopUpdate, controlLoopsFromDb.getControlLoopList().get(0)); + } + } + } + + @Test + public void testDelete_NoResultWithThisName() throws Exception { + Invocation.Builder invocationBuilder = super.sendRequest(INSTANTIATION_ENDPOINT + "?name=noResultWithThisName"); + Response resp = invocationBuilder.delete(); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resp.getStatus()); + InstantiationResponse instResponse = resp.readEntity(InstantiationResponse.class); + assertNotNull(instResponse.getErrorDetails()); + assertNull(instResponse.getAffectedControlLoops()); + } + + @Test + public void testDelete() throws Exception { + ControlLoops controlLoopsFromRsc = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_CREATE_JSON, "Delete"); + try (ControlLoopInstantiationProvider instantiationProvider = + new ControlLoopInstantiationProvider(getParameters())) { + instantiationProvider.createControlLoops(controlLoopsFromRsc); + + for (ControlLoop controlLoopFromRsc : controlLoopsFromRsc.getControlLoopList()) { + Invocation.Builder invocationBuilder = + super.sendRequest(INSTANTIATION_ENDPOINT + "?name=" + controlLoopFromRsc.getKey().getName() + + "&version=" + controlLoopFromRsc.getKey().getVersion()); + Response resp = invocationBuilder.delete(); + assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus()); + InstantiationResponse instResponse = resp.readEntity(InstantiationResponse.class); + InstantiationUtils.assertInstantiationResponse(instResponse, controlLoopFromRsc); + + ControlLoops controlLoopsFromDb = instantiationProvider.getControlLoops( + controlLoopFromRsc.getKey().getName(), controlLoopFromRsc.getKey().getVersion()); + assertThat(controlLoopsFromDb.getControlLoopList()).isEmpty(); + } + } + } + + @Test + public void testDeleteBadRequest() throws Exception { + ControlLoops controlLoopsFromRsc = + InstantiationUtils.getControlLoopsFromResource(CL_INSTANTIATION_CREATE_JSON, "DelBadRequest"); + try (ControlLoopInstantiationProvider instantiationProvider = + new ControlLoopInstantiationProvider(getParameters())) { + instantiationProvider.createControlLoops(controlLoopsFromRsc); + + for (ControlLoop controlLoopFromRsc : controlLoopsFromRsc.getControlLoopList()) { + Invocation.Builder invocationBuilder = + super.sendRequest(INSTANTIATION_ENDPOINT + "?name=" + controlLoopFromRsc.getKey().getName()); + Response resp = invocationBuilder.delete(); + // should be BAD_REQUEST + assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), resp.getStatus()); + } + } + } + + @Test + public void testComand_NotFound1() throws Exception { + Invocation.Builder invocationBuilder = super.sendRequest(INSTANTIATION_COMMAND_ENDPOINT); + Response resp = invocationBuilder.put(Entity.json(new InstantiationCommand())); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), resp.getStatus()); + } + + @Test + public void testComand_NotFound2() throws Exception { + InstantiationCommand command = + InstantiationUtils.getInstantiationCommandFromResource(CL_INSTANTIATION_CHANGE_STATE_JSON, "Command"); + command.setOrderedState(null); + + Invocation.Builder invocationBuilder = super.sendRequest(INSTANTIATION_COMMAND_ENDPOINT); + Response resp = invocationBuilder.put(Entity.json(command)); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), resp.getStatus()); + } + + @Test + public void testCommand() throws Exception { + InstantiationCommand command = + InstantiationUtils.getInstantiationCommandFromResource(CL_INSTANTIATION_CHANGE_STATE_JSON, "Command"); + + Invocation.Builder invocationBuilder = super.sendRequest(INSTANTIATION_COMMAND_ENDPOINT); + Response resp = invocationBuilder.put(Entity.json(command)); + assertEquals(Response.Status.ACCEPTED.getStatusCode(), resp.getStatus()); + InstantiationResponse instResponse = resp.readEntity(InstantiationResponse.class); + InstantiationUtils.assertInstantiationResponse(instResponse, command); + + // check passive state on DB + try (ControlLoopInstantiationProvider instantiationProvider = + new ControlLoopInstantiationProvider(getParameters())) { + for (ToscaConceptIdentifier toscaConceptIdentifier : command.getControlLoopIdentifierList()) { + ControlLoops controlLoopsGet = instantiationProvider.getControlLoops(toscaConceptIdentifier.getName(), + toscaConceptIdentifier.getVersion()); + assertThat(controlLoopsGet.getControlLoopList()).hasSize(1); + assertEquals(command.getOrderedState(), controlLoopsGet.getControlLoopList().get(0).getOrderedState()); + } + } + } +} diff --git a/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestControllerTest.java b/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestControllerTest.java new file mode 100644 index 000000000..4f68b4f8c --- /dev/null +++ b/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestControllerTest.java @@ -0,0 +1,71 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.controlloop.runtime.main.rest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.UUID; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.junit.Test; + +/** + * Class to perform unit test of {@link RestController}}. + * + */ +public class RestControllerTest { + + @Test + public void testProduces() { + Produces annotation = RestController.class.getAnnotation(Produces.class); + assertNotNull(annotation); + assertThat(annotation.value()).contains(MediaType.APPLICATION_JSON) + .contains(RestController.APPLICATION_YAML); + } + + @Test + public void testAddVersionControlHeaders() { + RestController ctlr = new RestController(); + Response resp = ctlr.addVersionControlHeaders(Response.status(Response.Status.OK)).build(); + assertEquals("0", resp.getHeaderString(RestController.VERSION_MINOR_NAME)); + assertEquals("0", resp.getHeaderString(RestController.VERSION_PATCH_NAME)); + assertEquals("1.0.0", resp.getHeaderString(RestController.VERSION_LATEST_NAME)); + } + + @Test + public void testAddLoggingHeaders_Null() { + RestController ctlr = new RestController(); + Response resp = ctlr.addLoggingHeaders(Response.status(Response.Status.OK), null).build(); + assertNotNull(resp.getHeaderString(RestController.REQUEST_ID_NAME)); + } + + @Test + public void testAddLoggingHeaders_NonNull() { + UUID uuid = UUID.randomUUID(); + RestController ctlr = new RestController(); + Response resp = ctlr.addLoggingHeaders(Response.status(Response.Status.OK), uuid).build(); + assertEquals(uuid.toString(), resp.getHeaderString(RestController.REQUEST_ID_NAME)); + } + +} diff --git a/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivatorTest.java b/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivatorTest.java index 7928124c8..da71c239d 100644 --- a/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivatorTest.java +++ b/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivatorTest.java @@ -75,7 +75,8 @@ public class ClRuntimeActivatorTest { @Test public void testNotValid() { + ClRuntimeParameterGroup parameterGroup = new ClRuntimeParameterGroup("name"); assertThatExceptionOfType(ControlLoopRuntimeException.class) - .isThrownBy(() -> new ClRuntimeActivator(new ClRuntimeParameterGroup("name"))); + .isThrownBy(() -> new ClRuntimeActivator(parameterGroup)); } } diff --git a/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/util/rest/CommonRestController.java b/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/util/rest/CommonRestController.java new file mode 100644 index 000000000..83cfe9b52 --- /dev/null +++ b/tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/util/rest/CommonRestController.java @@ -0,0 +1,263 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.controlloop.runtime.util.rest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.onap.policy.clamp.controlloop.common.exception.ControlLoopException; +import org.onap.policy.clamp.controlloop.runtime.main.startstop.Main; +import org.onap.policy.clamp.controlloop.runtime.util.CommonTestData; +import org.onap.policy.common.gson.GsonMessageBodyHandler; +import org.onap.policy.common.utils.network.NetworkUtil; +import org.onap.policy.common.utils.services.Registry; +import org.onap.policy.models.provider.PolicyModelsProviderParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to perform Rest unit tests. + * + */ +public class CommonRestController { + + private static final String CONFIG_FILE = "src/test/resources/parameters/RuntimeConfigParameters%d.json"; + + private static final Logger LOGGER = LoggerFactory.getLogger(CommonRestController.class); + + public static final String SELF = NetworkUtil.getHostname(); + public static final String ENDPOINT_PREFIX = "onap/controlloop/v2/"; + + private static int port; + private static String httpPrefix; + private static Main main; + + /** + * Allocates a port for the server, writes a config file, and then starts Main. + * + * @param dbName database name + * @throws Exception if an error occurs + */ + public static void setUpBeforeClass(final String dbName) throws Exception { + port = NetworkUtil.allocPort(); + + httpPrefix = "http://" + SELF + ":" + port + "/"; + + makeConfigFile(dbName); + startMain(); + } + + /** + * Stops Main. + */ + public static void teardownAfterClass() { + try { + stopMain(); + } catch (Exception ex) { + LOGGER.error("cannot stop main", ex); + } + } + + protected static PolicyModelsProviderParameters getParameters() { + return main.getParameters().getDatabaseProviderParameters(); + } + + /** + * Verifies that an endpoint appears within the swagger response. + * + * @param endpoint the endpoint of interest + * @throws Exception if an error occurs + */ + protected void testSwagger(final String endpoint) throws Exception { + final Invocation.Builder invocationBuilder = sendFqeRequest(httpPrefix + "swagger.yaml", true); + final String resp = invocationBuilder.get(String.class); + + assertTrue(resp.contains(ENDPOINT_PREFIX + endpoint + ":")); + } + + /** + * Makes a parameter configuration file. + * + * @param dbName database name + * @throws IOException if an error occurs writing the configuration file + * @throws FileNotFoundException if an error occurs writing the configuration file + */ + private static void makeConfigFile(final String dbName) throws FileNotFoundException, IOException { + String json = CommonTestData.getParameterGroupAsString(port, dbName); + + File file = new File(String.format(CONFIG_FILE, port)); + file.deleteOnExit(); + + try (FileOutputStream output = new FileOutputStream(file)) { + output.write(json.getBytes(StandardCharsets.UTF_8)); + } + } + + /** + * Starts the "Main". + * + * @throws InterruptedException + * + * @throws Exception if an error occurs + */ + protected static void startMain() throws InterruptedException { + Registry.newRegistry(); + + // make sure port is available + if (NetworkUtil.isTcpPortOpen(SELF, port, 1, 1L)) { + throw new IllegalStateException("port " + port + " is not available"); + } + + final String[] configParameters = {"-c", String.format(CONFIG_FILE, port)}; + + main = new Main(configParameters); + + if (!NetworkUtil.isTcpPortOpen(SELF, port, 40, 250L)) { + throw new IllegalStateException("server is not listening on port " + port); + } + } + + /** + * Stops the "Main". + * + * @throws ControlLoopException + * + * @throws Exception if an error occurs + */ + private static void stopMain() throws Exception { + if (main != null) { + Main main2 = main; + main = null; + + main2.shutdown(); + } + // make sure port is close + if (NetworkUtil.isTcpPortOpen(SELF, port, 1, 1L)) { + throw new IllegalStateException("port " + port + " is still in use"); + } + } + + /** + * Sends a request to an endpoint. + * + * @param endpoint the target endpoint + * @return a request builder + * @throws Exception if an error occurs + */ + protected Invocation.Builder sendRequest(final String endpoint) throws Exception { + return sendFqeRequest(httpPrefix + ENDPOINT_PREFIX + endpoint, true); + } + + /** + * Sends a request to an endpoint, without any authorization header. + * + * @param endpoint the target endpoint + * @return a request builder + * @throws Exception if an error occurs + */ + protected Invocation.Builder sendNoAuthRequest(final String endpoint) throws Exception { + return sendFqeRequest(httpPrefix + ENDPOINT_PREFIX + endpoint, false); + } + + /** + * Sends a request to a fully qualified endpoint. + * + * @param fullyQualifiedEndpoint the fully qualified target endpoint + * @param includeAuth if authorization header should be included + * @return a request builder + * @throws Exception if an error occurs + */ + protected Invocation.Builder sendFqeRequest(final String fullyQualifiedEndpoint, boolean includeAuth) + throws Exception { + final Client client = ClientBuilder.newBuilder().build(); + + client.property(ClientProperties.METAINF_SERVICES_LOOKUP_DISABLE, "true"); + client.register(GsonMessageBodyHandler.class); + + if (includeAuth) { + client.register(HttpAuthenticationFeature.basic("healthcheck", "zb!XztG34")); + } + + final WebTarget webTarget = client.target(fullyQualifiedEndpoint); + + return webTarget.request(MediaType.APPLICATION_JSON); + } + + /** + * Assert that POST call is Unauthorized. + * + * @param endPoint the endpoint + * @param entity the entity ofthe body + * @throws Exception if an error occurs + */ + protected void assertUnauthorizedPost(final String endPoint, final Entity entity) throws Exception { + Response rawresp = sendNoAuthRequest(endPoint).post(entity); + assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), rawresp.getStatus()); + } + + /** + * Assert that PUT call is Unauthorized. + * + * @param endPoint the endpoint + * @param entity the entity ofthe body + * @throws Exception if an error occurs + */ + protected void assertUnauthorizedPut(final String endPoint, final Entity entity) throws Exception { + Response rawresp = sendNoAuthRequest(endPoint).put(entity); + assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), rawresp.getStatus()); + } + + /** + * Assert that GET call is Unauthorized. + * + * @param endPoint the endpoint + * @throws Exception if an error occurs + */ + protected void assertUnauthorizedGet(final String endPoint) throws Exception { + Response rawresp = sendNoAuthRequest(endPoint).buildGet().invoke(); + assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), rawresp.getStatus()); + } + + /** + * Assert that DELETE call is Unauthorized. + * + * @param endPoint the endpoint + * @throws Exception if an error occurs + */ + protected void assertUnauthorizedDelete(final String endPoint) throws Exception { + Response rawresp = sendNoAuthRequest(endPoint).delete(); + assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), rawresp.getStatus()); + } +} -- cgit 1.2.3-korg