Add Instantiation CL Rest Controller 03/118903/5
authorFrancescoFioraEst <francesco.fiora@est.tech>
Fri, 5 Mar 2021 14:40:57 +0000 (14:40 +0000)
committerFrancesco Fiora <francesco.fiora@est.tech>
Wed, 10 Mar 2021 09:56:53 +0000 (09:56 +0000)
Issue-ID: POLICY-2980
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
Change-Id: Ic4c1fd8291dcdcdfd3a5145c545766a9dd6afbb3

tosca-controlloop/common/src/main/java/org/onap/policy/clamp/controlloop/common/handler/ControlLoopHandler.java
tosca-controlloop/common/src/test/java/org/onap/policy/clamp/controlloop/common/handler/DummyControlLoopHandler.java
tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/InstantiationHandler.java [new file with mode: 0644]
tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationController.java [new file with mode: 0644]
tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/ControlLoopAafFilter.java [new file with mode: 0644]
tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestController.java [new file with mode: 0644]
tosca-controlloop/runtime/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivator.java
tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/instantiation/rest/InstantiationControllerTest.java [new file with mode: 0644]
tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/rest/RestControllerTest.java [new file with mode: 0644]
tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/main/startstop/ClRuntimeActivatorTest.java
tosca-controlloop/runtime/src/test/java/org/onap/policy/clamp/controlloop/runtime/util/rest/CommonRestController.java [new file with mode: 0644]

index 2751f24..dff4c6b 100644 (file)
@@ -78,6 +78,11 @@ public abstract class ControlLoopHandler {
      */
     public abstract void startAndRegisterPublishers(List<TopicSink> 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();
 }
index fb26333..1602fb6 100644 (file)
@@ -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 (file)
index 0000000..c4b0955
--- /dev/null
@@ -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.
+ *
+ * </p>
+ * 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<Class<?>> getProviderClasses() {
+        Set<Class<?>> 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<TopicSink> 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 (file)
index 0000000..807da5d
--- /dev/null
@@ -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 (file)
index 0000000..f166de5
--- /dev/null
@@ -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 (file)
index 0000000..dd3fa30
--- /dev/null
@@ -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);
+    }
+}
index 8ac4d68..d3a5c58 100644 (file)
 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<TopicSink> topicSinks;
     private List<TopicSource> 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> instantiationHandler = new AtomicReference<>();
+        final AtomicReference<RestServer> 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<Class<?>> 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 (file)
index 0000000..9244c7a
--- /dev/null
@@ -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 (file)
index 0000000..4f68b4f
--- /dev/null
@@ -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));
+    }
+
+}
index 7928124..da71c23 100644 (file)
@@ -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 (file)
index 0000000..83cfe9b
--- /dev/null
@@ -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());
+    }
+}