diff options
32 files changed, 1361 insertions, 176 deletions
diff --git a/participant/participant-impl/participant-impl-kubernetes/pom.xml b/participant/participant-impl/participant-impl-kubernetes/pom.xml index 15e8e90ec..ebaa7a198 100644 --- a/participant/participant-impl/participant-impl-kubernetes/pom.xml +++ b/participant/participant-impl/participant-impl-kubernetes/pom.xml @@ -32,6 +32,16 @@ <name>${project.artifactId}</name> <description>Kubernetes participant, that allows k8s pods to partake in control loops</description> + <!-- Dependency added here to take precedence over Mockito-core in parent pom--> + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-inline</artifactId> + <version>3.8.0</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> <resources> <!-- Output the version of the control loop system --> diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/Application.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/Application.java index 37ecf4e6f..5d9d203fe 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/Application.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/Application.java @@ -23,12 +23,15 @@ package org.onap.policy.clamp.controlloop.participant.kubernetes; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.context.annotation.ComponentScan; /** * Starter. * */ @SpringBootApplication +@ComponentScan({"org.onap.policy.clamp.controlloop.participant.kubernetes", + "org.onap.policy.clamp.controlloop.participant.intermediary"}) @ConfigurationPropertiesScan("org.onap.policy.clamp.controlloop.participant.kubernetes.parameters") public class Application { /** diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/BeanFactory.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/ParticipantConfig.java index 3199d0cd9..94789a74f 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/BeanFactory.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/ParticipantConfig.java @@ -31,7 +31,7 @@ import org.springframework.web.multipart.commons.CommonsMultipartResolver; * Bean Factory class for helm client. */ @Configuration -public class BeanFactory { +public class ParticipantConfig { @Value("${server.http-port}") private int httpPort = 0; diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/controller/ChartController.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/controller/ChartController.java index 5560e47a8..23605e641 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/controller/ChartController.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/controller/ChartController.java @@ -149,13 +149,12 @@ public class ChartController { * @param name name of the chart * @param version version of the chart * @return Status of operation - * @throws ServiceException in case of error. */ @DeleteMapping(path = "/charts/{name}/{version}") @ApiOperation(value = "Delete the chart") @ApiResponses(value = {@ApiResponse(code = 204, message = "Chart Deleted")}) public ResponseEntity<Object> deleteChart(@PathVariable("name") String name, - @PathVariable("version") String version) throws ServiceException { + @PathVariable("version") String version) { ChartInfo chart = chartService.getChart(name, version); if (chart == null) { diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/handler/ControlLoopElementHandler.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/handler/ControlLoopElementHandler.java index 6257c3d19..4f654832d 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/handler/ControlLoopElementHandler.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/handler/ControlLoopElementHandler.java @@ -26,6 +26,8 @@ import java.lang.invoke.MethodHandles; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import lombok.AccessLevel; +import lombok.Getter; import lombok.Setter; import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElement; import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopOrderedState; @@ -57,6 +59,7 @@ public class ControlLoopElementHandler implements ControlLoopElementListener { private ParticipantIntermediaryApi intermediaryApi; // Map of CLElement Id and installed Helm charts + @Getter(AccessLevel.PACKAGE) private final Map<UUID, ChartInfo> chartMap = new HashMap<>(); /** diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/helm/HelmClient.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/helm/HelmClient.java index 343a44617..90d7218da 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/helm/HelmClient.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/helm/HelmClient.java @@ -75,12 +75,33 @@ public class HelmClient { */ public String findChartRepository(ChartInfo chart) throws ServiceException, IOException { updateHelmRepo(); - logger.info("Looking for helm chart {} in all the configured helm repositories", chart.getChartName()); - String repository = null; + String repository = verifyConfiguredRepo(chart); + if (repository != null) { + return repository; + } + var localHelmChartDir = chartStore.getAppPath(chart.getChartName(), chart.getVersion()).toString(); + logger.info("Chart not found in helm repositories, verifying local repo {} ", localHelmChartDir); + if (verifyLocalHelmRepo(localHelmChartDir + "/" + chart.getChartName())) { + repository = localHelmChartDir; + } - var process = helmRepoVerifyCommand(chart.getChartName()).start(); + return repository; + } - try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + /** + * Verify helm chart in configured repositories. + * @param chart chartInfo + * @return repo name + * @throws IOException incase of error + * @throws ServiceException incase of error + */ + public String verifyConfiguredRepo(ChartInfo chart) throws IOException, ServiceException { + logger.info("Looking for helm chart {} in all the configured helm repositories", chart.getChartName()); + String repository = null; + var builder = helmRepoVerifyCommand(chart.getChartName()); + String output = executeCommand(builder); + try (var reader = new BufferedReader(new InputStreamReader(IOUtils.toInputStream(output, + StandardCharsets.UTF_8)))) { String line = reader.readLine(); while (line != null) { if (line.contains(chart.getChartName())) { @@ -91,13 +112,6 @@ public class HelmClient { line = reader.readLine(); } } - - var localHelmChartDir = chartStore.getAppPath(chart.getChartName(), chart.getVersion()).toString(); - logger.info("Chart not found in helm repositories, verifying local repo {} ", localHelmChartDir); - if (verifyLocalHelmRepo(localHelmChartDir + "/" + chart.getChartName())) { - repository = localHelmChartDir; - } - return repository; } @@ -111,7 +125,13 @@ public class HelmClient { executeCommand(prepareUnInstallCommand(chart)); } - static String executeCommand(ProcessBuilder processBuilder) throws ServiceException { + /** + * Execute helm cli bash commands . + * @param processBuilder processbuilder + * @return string output + * @throws ServiceException incase of error. + */ + public static String executeCommand(ProcessBuilder processBuilder) throws ServiceException { var commandStr = toString(processBuilder); try { @@ -123,13 +143,15 @@ public class HelmClient { var error = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8); throw new ServiceException("Command execution failed: " + commandStr + " " + error); } + var output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); logger.debug("Command <{}> execution, output: {}", commandStr, output); return output; + } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new ServiceException("Failed to execute the Command: " + commandStr + ", the command was interrupted", - ie); + ie); } catch (Exception exc) { throw new ServiceException("Failed to execute the Command: " + commandStr, exc); } @@ -140,12 +162,12 @@ public class HelmClient { // @formatter:off List<String> helmArguments = new ArrayList<>( Arrays.asList( - "helm", - "install", chart.getReleaseName(), chart.getRepository() + "/" + chart.getChartName(), - "--version", chart.getVersion(), - "--namespace", chart.getNamespace() + "helm", + "install", chart.getReleaseName(), chart.getRepository() + "/" + chart.getChartName(), + "--version", chart.getVersion(), + "--namespace", chart.getNamespace() ) - ); + ); // @formatter:on // Verify if values.yaml available for the chart @@ -176,9 +198,7 @@ public class HelmClient { private void updateHelmRepo() throws ServiceException { logger.info("Updating local helm repositories before verifying the chart"); - List<String> helmArguments = Arrays.asList("helm", "repo", "update"); - - executeCommand(new ProcessBuilder().command(helmArguments)); + executeCommand(new ProcessBuilder().command("helm", "repo", "update")); logger.debug("Helm repositories updated successfully"); } diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartList.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartList.java index c86bff58a..7f46bbde5 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartList.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartList.java @@ -18,7 +18,7 @@ package org.onap.policy.clamp.controlloop.participant.kubernetes.models; -import java.util.Collection; +import java.util.List; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -27,5 +27,5 @@ import lombok.Setter; @Setter @Builder public class ChartList { - private Collection<ChartInfo> charts; + private List<ChartInfo> charts; } diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/parameters/ParticipantK8sParameters.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/parameters/ParticipantK8sParameters.java index a5731da74..3b2b3732b 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/parameters/ParticipantK8sParameters.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/parameters/ParticipantK8sParameters.java @@ -20,13 +20,13 @@ package org.onap.policy.clamp.controlloop.participant.kubernetes.parameters; +import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; import org.onap.policy.clamp.controlloop.participant.intermediary.parameters.ParticipantIntermediaryParameters; import org.onap.policy.clamp.controlloop.participant.intermediary.parameters.ParticipantParameters; -import org.onap.policy.common.parameters.annotations.Valid; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartService.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartService.java index 29a49a9ff..adb6cf0d1 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartService.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartService.java @@ -53,9 +53,8 @@ public class ChartService { * @param name name of the app * @param version version of the app * @return chart - * @throws ServiceException in case of error. */ - public ChartInfo getChart(String name, String version) throws ServiceException { + public ChartInfo getChart(String name, String version) { return chartStore.getChart(name, version); } diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartStore.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartStore.java index dcdff62f5..03b35161d 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartStore.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartStore.java @@ -33,6 +33,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import lombok.AccessLevel; +import lombok.Getter; import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException; import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo; import org.onap.policy.clamp.controlloop.participant.kubernetes.parameters.ParticipantK8sParameters; @@ -52,9 +54,8 @@ public class ChartStore { private final ParticipantK8sParameters participantK8sParameters; - /** - * The chartStore map contains chart name as key & ChartInfo as value. - */ + // ChartStore map contains chart name as key & ChartInfo as value. + @Getter(AccessLevel.PACKAGE) private Map<String, ChartInfo> localChartMap = new ConcurrentHashMap<>(); /** diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/handler/ControlLoopElementHandlerTest.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/handler/ControlLoopElementHandlerTest.java new file mode 100644 index 000000000..f3d27a63c --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/handler/ControlLoopElementHandlerTest.java @@ -0,0 +1,130 @@ +/*- + * ============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.participant.kubernetes.handler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElement; +import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopOrderedState; +import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopState; +import org.onap.policy.clamp.controlloop.participant.intermediary.api.ParticipantIntermediaryApi; +import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartList; +import org.onap.policy.clamp.controlloop.participant.kubernetes.service.ChartService; +import org.onap.policy.clamp.controlloop.participant.kubernetes.utils.TestUtils; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.springframework.test.context.junit.jupiter.SpringExtension; + + +@ExtendWith(SpringExtension.class) +class ControlLoopElementHandlerTest { + + private static final Coder CODER = new StandardCoder(); + private static final String CHART_INFO_YAML = "src/test/resources/ChartList.json"; + private static final String KEY_NAME = "org.onap.domain.database.HelloWorld_K8SMicroserviceControlLoopElement"; + private static List<ChartInfo> charts; + private static ToscaServiceTemplate toscaServiceTemplate; + + + @InjectMocks + private ControlLoopElementHandler controlLoopElementHandler = new ControlLoopElementHandler(); + + @Mock + private ChartService chartService; + + @Mock + private ParticipantIntermediaryApi participantIntermediaryApi; + + @BeforeAll + static void init() throws CoderException { + charts = CODER.decode(new File(CHART_INFO_YAML), ChartList.class).getCharts(); + toscaServiceTemplate = TestUtils.testControlLoopRead(); + } + + + @Test + void test_ControlLoopElementStateChange() throws ServiceException { + UUID controlLoopElementId1 = UUID.randomUUID(); + UUID controlLoopElementId2 = UUID.randomUUID(); + + controlLoopElementHandler.getChartMap().put(controlLoopElementId1, charts.get(0)); + controlLoopElementHandler.getChartMap().put(controlLoopElementId2, charts.get(1)); + + doNothing().when(chartService).uninstallChart(charts.get(0)); + + controlLoopElementHandler.controlLoopElementStateChange(controlLoopElementId1, ControlLoopState.PASSIVE, + ControlLoopOrderedState.UNINITIALISED); + + doThrow(new ServiceException("Error uninstalling the chart")).when(chartService) + .uninstallChart(charts.get(0)); + + assertDoesNotThrow(() -> controlLoopElementHandler + .controlLoopElementStateChange(controlLoopElementId1, ControlLoopState.PASSIVE, + ControlLoopOrderedState.UNINITIALISED)); + + assertDoesNotThrow(() -> controlLoopElementHandler + .controlLoopElementStateChange(controlLoopElementId1, ControlLoopState.PASSIVE, + ControlLoopOrderedState.RUNNING)); + + } + + @Test + void test_ControlLoopElementUpdate() throws PfModelException, IOException, ServiceException { + + UUID elementId1 = UUID.randomUUID(); + ControlLoopElement element = new ControlLoopElement(); + element.setId(elementId1); + element.setDefinition(new ToscaConceptIdentifier(KEY_NAME, "1.0.1")); + element.setOrderedState(ControlLoopOrderedState.PASSIVE); + + controlLoopElementHandler.controlLoopElementUpdate(element, toscaServiceTemplate); + + assertThat(controlLoopElementHandler.getChartMap()).hasSize(1).containsKey(elementId1); + + doThrow(new ServiceException("Error installing the chart")).when(chartService) + .installChart(Mockito.any()); + + UUID elementId2 = UUID.randomUUID(); + element.setId(elementId2); + controlLoopElementHandler.controlLoopElementUpdate(element, toscaServiceTemplate); + + assertThat(controlLoopElementHandler.getChartMap().containsKey(elementId2)).isFalse(); + } +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/helm/HelmClientTest.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/helm/HelmClientTest.java new file mode 100644 index 000000000..5f8b7dc78 --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/helm/HelmClientTest.java @@ -0,0 +1,122 @@ +/*- + * ============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.participant.kubernetes.helm; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mockStatic; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartList; +import org.onap.policy.clamp.controlloop.participant.kubernetes.service.ChartStore; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.springframework.test.context.junit.jupiter.SpringExtension; + + +@ExtendWith(SpringExtension.class) +class HelmClientTest { + + private static final Coder CODER = new StandardCoder(); + private static final String CHART_INFO_YAML = "src/test/resources/ChartList.json"; + private static List<ChartInfo> charts; + + @InjectMocks + @Spy + private HelmClient helmClient = new HelmClient(); + + @Mock + ChartStore chartStore; + + private static MockedStatic<HelmClient> mockedClient; + + @BeforeAll + static void init() throws CoderException { + charts = CODER.decode(new File(CHART_INFO_YAML), ChartList.class).getCharts(); + //Mock static method for bash command execution + mockedClient = mockStatic(HelmClient.class); + } + + @Test + void test_installChart() throws IOException { + mockedClient.when(() -> HelmClient.executeCommand(any())) + .thenReturn("success"); + doReturn(new File("/target/tmp/override.yaml")).when(chartStore) + .getOverrideFile(any()); + assertDoesNotThrow(() -> helmClient.installChart(charts.get(0))); + } + + @Test + void test_findChartRepository() throws IOException, ServiceException { + mockedClient.when(() -> HelmClient.executeCommand(Mockito.any())) + .thenReturn("nginx-stable/nginx-ingress\t0.9.3\t1.11.3" + + " \tNGINX Ingress Controller"); + String configuredRepo = helmClient.findChartRepository(charts.get(1)); + + assertThat(configuredRepo).isEqualTo("nginx-stable"); + + doReturn(Path.of("/target/tmp/dummyChart/1.0")).when(chartStore).getAppPath(charts.get(1).getChartName(), + charts.get(1).getVersion()); + + doReturn(null).when(helmClient).verifyConfiguredRepo(charts.get(1)); + + String localRepoName = helmClient.findChartRepository(charts.get(1)); + assertNotNull(localRepoName); + assertThat(localRepoName).endsWith(charts.get(0).getVersion()); + } + + @Test + void test_uninstallChart() throws ServiceException { + helmClient.uninstallChart(charts.get(0)); + mockedClient.when(() -> HelmClient.executeCommand(any())).thenThrow(new ServiceException("error in execution")); + + assertThatThrownBy(() -> helmClient.uninstallChart(charts.get(0))) + .isInstanceOf(ServiceException.class); + } + + @Test + void test_verifyConfiguredRepoForInvalidChart() throws IOException, ServiceException { + mockedClient.when(() -> HelmClient.executeCommand(Mockito.any())) + .thenReturn(""); + String configuredRepo = helmClient.verifyConfiguredRepo(charts.get(1)); + assertNull(configuredRepo); + } + +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/parameters/CommonTestData.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/parameters/CommonTestData.java new file mode 100644 index 000000000..d8d477d1b --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/parameters/CommonTestData.java @@ -0,0 +1,152 @@ +/*- + * ============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.participant.kubernetes.parameters; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.onap.policy.common.endpoints.parameters.TopicParameters; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; + +public class CommonTestData { + + public static final String PARTICIPANT_GROUP_NAME = "ControlLoopParticipantGroup"; + public static final String DESCRIPTION = "Participant description"; + public static final long TIME_INTERVAL = 2000; + public static final List<TopicParameters> TOPIC_PARAMS = Arrays.asList(getTopicParams()); + public static final Coder CODER = new StandardCoder(); + + + /** + * Get ParticipantK8sParameters. + * + * @return ParticipantK8sParameters + */ + public ParticipantK8sParameters getParticipantK8sParameters() { + try { + return CODER.convert(getParticipantK8sParametersMap(PARTICIPANT_GROUP_NAME), + ParticipantK8sParameters.class); + } catch (final CoderException e) { + throw new RuntimeException("cannot create ParticipantK8sParameters from map", e); + } + } + + /** + * Returns a property map for a ParticipantK8sParameters map for test cases. + * + * @param name name of the parameters + * + * @return a property map suitable for constructing an object + */ + public Map<String, Object> getParticipantK8sParametersMap(final String name) { + final Map<String, Object> map = new TreeMap<>(); + + map.put("name", name); + map.put("intermediaryParameters", getIntermediaryParametersMap(false)); + map.put("localChartDirectory", getLocalChartDir()); + map.put("infoFileName", getInfoFileName()); + return map; + } + + + /** + * Returns string value of local chart Directory. + * @return a string value + */ + public String getLocalChartDir() { + return "/var/helm-manager/local-charts"; + } + + /** + * Returns string value of Info file name. + * @return string value + */ + public String getInfoFileName() { + return "CHART-INFO.json"; + } + + + + /** + * Returns a property map for a intermediaryParameters map for test cases. + * + * @param isEmpty boolean value to represent that object created should be empty or not + * @return a property map suitable for constructing an object + */ + public Map<String, Object> getIntermediaryParametersMap(final boolean isEmpty) { + final Map<String, Object> map = new TreeMap<>(); + if (!isEmpty) { + map.put("name", "Participant parameters"); + map.put("reportingTimeInterval", TIME_INTERVAL); + map.put("description", DESCRIPTION); + map.put("participantId", getParticipantId()); + map.put("participantType", getParticipantId()); + map.put("clampControlLoopTopics", getTopicParametersMap(false)); + } + + return map; + } + + /** + * Returns participantId for test cases. + * + * @return participant Id + */ + public static ToscaConceptIdentifier getParticipantId() { + final ToscaConceptIdentifier participantId = new ToscaConceptIdentifier(); + participantId.setName("K8sParticipant0"); + participantId.setVersion("1.0.0"); + return participantId; + } + + + /** + * Returns a property map for a TopicParameters map for test cases. + * + * @param isEmpty boolean value to represent that object created should be empty or not + * @return a property map suitable for constructing an object + */ + public Map<String, Object> getTopicParametersMap(final boolean isEmpty) { + final Map<String, Object> map = new TreeMap<>(); + if (!isEmpty) { + map.put("topicSources", TOPIC_PARAMS); + map.put("topicSinks", TOPIC_PARAMS); + } + return map; + } + + /** + * Returns topic parameters for test cases. + * + * @return topic parameters + */ + public static TopicParameters getTopicParams() { + final TopicParameters topicParams = new TopicParameters(); + topicParams.setTopic("POLICY-CLRUNTIME-PARTICIPANT"); + topicParams.setTopicCommInfrastructure("dmaap"); + topicParams.setServers(Arrays.asList("localhost")); + return topicParams; + } +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/parameters/ParticipantK8sParametersTest.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/parameters/ParticipantK8sParametersTest.java new file mode 100644 index 000000000..177d7bc23 --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/parameters/ParticipantK8sParametersTest.java @@ -0,0 +1,88 @@ +/*- + * ============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.participant.kubernetes.parameters; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.ValidatorFactory; +import org.junit.jupiter.api.Test; + +class ParticipantK8sParametersTest { + + private CommonTestData commonTestData = new CommonTestData(); + private ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); + + @Test + void testParticipantPolicyParameters() { + final ParticipantK8sParameters participantParameters = commonTestData.getParticipantK8sParameters(); + assertThat(validatorFactory.getValidator().validate(participantParameters)).isNullOrEmpty(); + } + + @Test + void testParticipantK8sParameters_NullTopicSinks() { + final ParticipantK8sParameters participantParameters = commonTestData.getParticipantK8sParameters(); + participantParameters.getIntermediaryParameters().getClampControlLoopTopics().setTopicSinks(null); + assertThat(validatorFactory.getValidator().validate(participantParameters)).isNotEmpty(); + } + + @Test + void testParticipantK8sParameters_NullTopicSources() { + final ParticipantK8sParameters participantParameters = commonTestData.getParticipantK8sParameters(); + participantParameters.getIntermediaryParameters().getClampControlLoopTopics().setTopicSources(null); + assertThat(validatorFactory.getValidator().validate(participantParameters)).isNotEmpty(); + } + + @Test + void testParticipantK8sParameters_BlankLocalChartDirParameter() { + final ParticipantK8sParameters participantParameters = commonTestData.getParticipantK8sParameters(); + participantParameters.setLocalChartDirectory(" "); + Set<ConstraintViolation<ParticipantK8sParameters>> violations = validatorFactory.getValidator() + .validate(participantParameters); + assertThat(violations.size()).isEqualTo(1); + } + + @Test + void testParticipantK8sParameters_BlankInfoFileParameter() { + final ParticipantK8sParameters participantParameters = commonTestData.getParticipantK8sParameters(); + participantParameters.setInfoFileName(""); + Set<ConstraintViolation<ParticipantK8sParameters>> violations = validatorFactory.getValidator() + .validate(participantParameters); + assertThat(violations.size()).isEqualTo(1); + } + + @Test + void testNoIntermediaryParameters() { + final ParticipantK8sParameters participantParameters = commonTestData.getParticipantK8sParameters(); + participantParameters.setIntermediaryParameters(null); + assertThat(validatorFactory.getValidator().validate(participantParameters)).isNotEmpty(); + } + + @Test + void testNoParticipantId() { + final ParticipantK8sParameters participantParameters = commonTestData.getParticipantK8sParameters(); + participantParameters.getIntermediaryParameters().setParticipantId(null); + assertThat(validatorFactory.getValidator().validate(participantParameters)).isNotEmpty(); + } + +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/rest/ChartControllerTest.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/rest/ChartControllerTest.java new file mode 100644 index 000000000..1a1bdae5b --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/rest/ChartControllerTest.java @@ -0,0 +1,230 @@ +/*- + * ============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.participant.kubernetes.rest; + +import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.File; +import java.util.List; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.onap.policy.clamp.controlloop.participant.kubernetes.controller.ChartController; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartList; +import org.onap.policy.clamp.controlloop.participant.kubernetes.parameters.ParticipantK8sParameters; +import org.onap.policy.clamp.controlloop.participant.kubernetes.service.ChartService; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + + +@ExtendWith(SpringExtension.class) +@WebMvcTest(value = ChartController.class) +@EnableConfigurationProperties(value = ParticipantK8sParameters.class) +class ChartControllerTest { + + private static final Coder CODER = new StandardCoder(); + private static final String CHART_INFO_YAML = "src/test/resources/ChartList.json"; + private static List<ChartInfo> charts; + private static String DEFAULT_CHART_URL = "/helm/charts"; + private static String INSTALL_CHART_URL = "/helm/install"; + private static String UNINSTALL_CHART_URL = "/helm/uninstall/"; + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ChartService chartService; + + @Autowired + private WebApplicationContext context; + + /** + * Read input chart info json. + * @throws Exception incase of error. + */ + @BeforeAll + static void setupParams() throws CoderException { + charts = CODER.decode(new File(CHART_INFO_YAML), ChartList.class).getCharts(); + } + + /** + * Mock service layer in Controller. + * @throws Exception incase of error. + */ + @BeforeEach + void mockServiceClass() { + when(chartService.getAllCharts()).thenReturn(charts); + when(chartService.getChart(charts.get(0).getChartName(), charts.get(0).getVersion())) + .thenReturn(charts.get(0)); + + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + /** + * Test endpoint for retrieving all charts. + * @throws Exception incase of error. + */ + @Test + void retrieveAllCharts() throws Exception { + RequestBuilder requestBuilder; + requestBuilder = MockMvcRequestBuilders.get(DEFAULT_CHART_URL).accept(MediaType.APPLICATION_JSON_VALUE); + + mockMvc.perform(requestBuilder).andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.charts.[0].chartName", is("HelloWorld"))); + } + + /** + * Test endpoint for installing a chart. + * @throws Exception incase of error. + */ + @Test + void installChart() throws Exception { + RequestBuilder requestBuilder; + + //Mocking successful installation for void install method + doNothing().when(chartService).installChart(charts.get(0)); + + requestBuilder = MockMvcRequestBuilders.post(INSTALL_CHART_URL).accept(MediaType.APPLICATION_JSON_VALUE) + .content(getInstallationJson(charts.get(0).getChartName(), charts.get(0).getVersion())) + .contentType(MediaType.APPLICATION_JSON_VALUE); + + mockMvc.perform(requestBuilder).andExpect(status().isCreated()); + + //Install Invalid chart, expects HTTP status NOT_FOUND + requestBuilder = MockMvcRequestBuilders.post(INSTALL_CHART_URL).accept(MediaType.APPLICATION_JSON_VALUE) + .content(getInstallationJson("invalidName", "invalidVersion")) + .contentType(MediaType.APPLICATION_JSON_VALUE); + + mockMvc.perform(requestBuilder).andExpect(status().isNotFound()); + } + + /** + * Test endpoint for uninstalling a chart. + * @throws Exception incase of error. + */ + @Test + void uninstallChart() throws Exception { + RequestBuilder requestBuilder; + + //Mocking successful scenario for void uninstall method + doNothing().when(chartService).uninstallChart(charts.get(0)); + + requestBuilder = MockMvcRequestBuilders.delete(UNINSTALL_CHART_URL + charts.get(0).getChartName() + + "/" + charts.get(0).getVersion()).accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE); + + mockMvc.perform(requestBuilder).andExpect(status().isNoContent()); + + //Invalid chart + requestBuilder = MockMvcRequestBuilders.delete(UNINSTALL_CHART_URL + "invalidName" + + "/" + "invalidVersion").accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE); + + mockMvc.perform(requestBuilder).andExpect(status().isNotFound()); + } + + /** + * Test endpoint for chart onboarding. + * @throws Exception incase of error. + */ + @Test + void onboardChart() throws Exception { + RequestBuilder requestBuilder; + MockMultipartFile chartFile = new MockMultipartFile("chart", "hello.tgz", + MediaType.TEXT_PLAIN_VALUE, "Dummy data".getBytes()); + + MockMultipartFile overrideFile = new MockMultipartFile("values", "values.yaml", + MediaType.TEXT_PLAIN_VALUE, "Dummy data".getBytes()); + + //Mocking successful scenario for void uninstall method + when(chartService.saveChart(charts.get(0), chartFile, null)).thenReturn(charts.get(0)); + + requestBuilder = MockMvcRequestBuilders.multipart(DEFAULT_CHART_URL) + .file(chartFile).file(overrideFile).param("info", getChartInfoJson()); + + mockMvc.perform(requestBuilder).andExpect(status().isOk()); + } + + /** + * Test endpoint for deleting a chart. + * @throws Exception incase of error. + */ + @Test + void deleteChart() throws Exception { + RequestBuilder requestBuilder; + + //Mocking successful scenario for void uninstall method + doNothing().when(chartService).deleteChart(charts.get(0)); + + requestBuilder = MockMvcRequestBuilders.delete(DEFAULT_CHART_URL + "/" + charts.get(0).getChartName() + + "/" + charts.get(0).getVersion()).accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE); + + mockMvc.perform(requestBuilder).andExpect(status().isNoContent()); + //Invalid chart + requestBuilder = MockMvcRequestBuilders.delete(UNINSTALL_CHART_URL + "invalidName" + + "/" + "invalidVersion").accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE); + + mockMvc.perform(requestBuilder).andExpect(status().isNotFound()); + + } + + private String getInstallationJson(String name, String version) { + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", name); + jsonObj.put("version", version); + return jsonObj.toString(); + } + + private String getChartInfoJson() { + JSONObject jsonObj = new JSONObject(); + jsonObj.put("chartName", charts.get(0).getChartName()); + jsonObj.put("version", charts.get(0).getVersion()); + jsonObj.put("namespace", charts.get(0).getNamespace()); + jsonObj.put("repository", charts.get(0).getRepository()); + jsonObj.put("releaseName", charts.get(0).getReleaseName()); + return jsonObj.toString(); + } + +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/service/ChartServiceTest.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/service/ChartServiceTest.java new file mode 100644 index 000000000..957a69a08 --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/service/ChartServiceTest.java @@ -0,0 +1,147 @@ +/*- + * ============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.participant.kubernetes.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException; +import org.onap.policy.clamp.controlloop.participant.kubernetes.helm.HelmClient; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartList; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +class ChartServiceTest { + + private static final Coder CODER = new StandardCoder(); + private static final String CHART_INFO_YAML = "src/test/resources/ChartList.json"; + private static List<ChartInfo> charts; + + @InjectMocks + @Spy + private ChartService chartService = new ChartService(); + + @Mock + private ChartStore chartStore; + + @Mock + private HelmClient helmClient; + + @BeforeAll + static void init() throws CoderException { + charts = CODER.decode(new File(CHART_INFO_YAML), ChartList.class).getCharts(); + } + + @Test + void test_getAllCharts() { + assertThat(chartService.getAllCharts()).isEmpty(); + + doReturn(charts).when(chartStore).getAllCharts(); + Collection<ChartInfo> result = chartService.getAllCharts(); + assertNotNull(result); + assertThat(result).containsAll(charts); + } + + @Test + void test_getChart() { + assertNull(chartService.getChart("dummyName", "dummyversion")); + + doReturn(charts.get(0)).when(chartStore).getChart(any(), any()); + ChartInfo chart = chartService.getChart(charts.get(0).getChartName(), + charts.get(0).getVersion()); + assertNotNull(chart); + assertThat(chart.getNamespace()).isEqualTo(charts.get(0).getNamespace()); + } + + @Test + void test_saveChart() throws IOException, ServiceException { + doThrow(IOException.class).when(chartStore).saveChart(charts.get(0), null, null); + assertThatThrownBy(() -> chartService.saveChart(charts.get(0), null, null)) + .isInstanceOf(IOException.class); + + MockMultipartFile mockChartFile = new MockMultipartFile("chart", "dummy".getBytes()); + MockMultipartFile mockOverrideFile = new MockMultipartFile("override", "dummy".getBytes()); + + doReturn(charts.get(0)).when(chartStore).saveChart(any(), any(), any()); + + ChartInfo chart = chartService.saveChart(charts.get(0), mockChartFile, mockOverrideFile); + assertNotNull(chart); + assertThat(chart.getChartName()).isEqualTo(charts.get(0).getChartName()); + + } + + @Test + void test_installChart() throws IOException, ServiceException { + assertDoesNotThrow(() -> chartService.installChart(charts.get(0))); + doThrow(ServiceException.class).when(helmClient).installChart(any()); + assertThatThrownBy(() -> chartService.installChart(charts.get(0))).isInstanceOf(ServiceException.class); + + doReturn("dummyRepoName").when(chartService).findChartRepo(any()); + doNothing().when(helmClient).installChart(any()); + chartService.installChart(charts.get(1)); + assertEquals("dummyRepoName", charts.get(1).getRepository()); + + ChartInfo testChart = charts.get(1); + testChart.setRepository(null); + doReturn(null).when(chartService).findChartRepo(any()); + chartService.installChart(charts.get(1)); + } + + @Test + void test_UninstallChart() throws ServiceException { + assertDoesNotThrow(() -> chartService.uninstallChart(charts.get(0))); + doThrow(ServiceException.class).when(helmClient).uninstallChart(any()); + assertThatThrownBy(() -> chartService.uninstallChart(charts.get(0))).isInstanceOf(ServiceException.class); + } + + @Test + void test_findChartRepo() throws IOException, ServiceException { + assertDoesNotThrow(() -> chartService.findChartRepo(charts.get(0))); + doReturn("dummyRepoName").when(helmClient).findChartRepository(any()); + assertEquals("dummyRepoName", chartService.findChartRepo(charts.get(1))); + + doThrow(ServiceException.class).when(helmClient).findChartRepository(any()); + assertThatThrownBy(() -> chartService.findChartRepo(charts.get(0))).isInstanceOf(ServiceException.class); + } +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/service/ChartStoreTest.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/service/ChartStoreTest.java new file mode 100644 index 000000000..2d05a7a0e --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/service/ChartStoreTest.java @@ -0,0 +1,170 @@ +/*- + * ============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.participant.kubernetes.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo; +import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartList; +import org.onap.policy.clamp.controlloop.participant.kubernetes.parameters.ParticipantK8sParameters; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.util.FileSystemUtils; + + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class ChartStoreTest { + + private static final Coder CODER = new StandardCoder(); + private static final String CHART_INFO_YAML = "src/test/resources/ChartList.json"; + private static List<ChartInfo> charts; + + @Mock + private ParticipantK8sParameters parameters; + + private ChartStore chartStore; + + + @BeforeAll + static void init() throws CoderException { + charts = CODER.decode(new File(CHART_INFO_YAML), ChartList.class).getCharts(); + } + + //Overriding the local chart dir parameter to a temp folder under target for testing java FILE IO operations. + @BeforeEach + void setup() { + Mockito.doReturn("target/tmp/").when(parameters).getLocalChartDirectory(); + Mockito.doReturn("info.json").when(parameters).getInfoFileName(); + chartStore = new ChartStore(parameters); + } + + //Clean up the 'tmp' dir after each test case. + @AfterEach + void cleanUp() throws IOException { + FileSystemUtils.deleteRecursively(Path.of(parameters.getLocalChartDirectory())); + chartStore.getLocalChartMap().clear(); + } + + @Test + void test_getHelmChartFile() { + File file = chartStore.getHelmChartFile(charts.get(0)); + assertNotNull(file); + assertThat(file.getPath()).endsWith(charts.get(0).getChartName()); + } + + @Test + void test_getOverrideFile() { + File file = chartStore.getOverrideFile(charts.get(0)); + assertNotNull(file); + assertThat(file.getPath()).endsWith("values.yaml"); + } + + @Test + void test_saveChart() throws IOException, ServiceException { + MockMultipartFile mockChartFile = new MockMultipartFile("chart", "dummy".getBytes()); + MockMultipartFile mockOverrideFile = new MockMultipartFile("override", "dummy".getBytes()); + ChartInfo testChart = charts.get(0); + testChart.setChartName("testChart"); + ChartInfo result = chartStore.saveChart(charts.get(0), mockChartFile, mockOverrideFile); + + assertThat(result.getChartName()).isEqualTo("testChart"); + assertThat(chartStore.getLocalChartMap()).hasSize(1); + + assertThatThrownBy(() -> chartStore.saveChart(charts.get(0), mockChartFile, mockOverrideFile)) + .isInstanceOf(ServiceException.class); + } + + + @Test + void test_getChart() { + assertNull(chartStore.getChart(charts.get(0).getChartName(), charts.get(0).getVersion())); + chartStore.getLocalChartMap().put(charts.get(0).getChartName() + "_" + charts.get(0).getVersion(), + charts.get(0)); + ChartInfo chart = chartStore.getChart(charts.get(0).getChartName(), charts.get(0).getVersion()); + assertThat(chart.getChartName()).isEqualTo(charts.get(0).getChartName()); + } + + @Test + void test_getAllChart() { + // When the chart store is empty before adding any charts + assertThat(chartStore.getAllCharts()).isEmpty(); + + for (ChartInfo chart : charts) { + chartStore.getLocalChartMap().put(chart.getChartName() + "_" + chart.getVersion(), chart); + } + List<ChartInfo> retrievedChartList = chartStore.getAllCharts(); + assertThat(retrievedChartList).isNotEmpty(); + assertThat(retrievedChartList.size()).isEqualTo(charts.size()); + } + + @Test + void test_deleteChart() { + chartStore.getLocalChartMap().put(charts.get(0).getChartName() + "_" + charts.get(0).getVersion(), + charts.get(0)); + assertThat(chartStore.getLocalChartMap()).hasSize(1); + chartStore.deleteChart(charts.get(0)); + assertThat(chartStore.getLocalChartMap()).isEmpty(); + } + + @Test + void test_getAppPath() { + Path path = chartStore.getAppPath(charts.get(0).getChartName(), charts.get(0).getVersion()); + assertNotNull(path); + assertThat(path.toString()).endsWith(charts.get(0).getVersion()); + assertThat(path.toString()).startsWith("target"); + } + + @Test + void test_chartSoreInstantiationWithExistingChartFiles() throws IOException, ServiceException { + MockMultipartFile mockChartFile = new MockMultipartFile("HelmChartFile", "dummyData".getBytes()); + MockMultipartFile mockOverrideFile = new MockMultipartFile("overrideFile.yaml", "dummyData".getBytes()); + ChartInfo testChart = charts.get(0); + testChart.setChartName("dummyChart"); + + //Creating a dummy chart in local dir. + chartStore.saveChart(charts.get(0), mockChartFile, mockOverrideFile); + + //Instantiating a new chartStore object with pre available chart in local. + ChartStore chartStore2 = new ChartStore(parameters); + assertThat(chartStore2.getLocalChartMap()).hasSize(1).containsKey("dummyChart_" + charts.get(0).getVersion()); + } +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/utils/TestUtils.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/utils/TestUtils.java new file mode 100644 index 000000000..8585e9d11 --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org.onap.policy.clamp.controlloop.participant.kubernetes/utils/TestUtils.java @@ -0,0 +1,43 @@ +/*- + * ============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.participant.kubernetes.utils; + +import org.onap.policy.common.utils.coder.YamlJsonTranslator; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; + +public class TestUtils { + + private static final YamlJsonTranslator yamlTranslator = new YamlJsonTranslator(); + private static final String TOSCA_TEMPLATE_YAML = "src/test/resources/servicetemplates/KubernetesHelm.yaml"; + + + public static ToscaServiceTemplate testControlLoopRead() { + return testControlLoopYamlSerialization(TOSCA_TEMPLATE_YAML); + } + + + private static ToscaServiceTemplate testControlLoopYamlSerialization(String controlLoopFilePath) { + String controlLoopString = ResourceUtils.getResourceAsString(controlLoopFilePath); + ToscaServiceTemplate serviceTemplate = yamlTranslator.fromYaml(controlLoopString, ToscaServiceTemplate.class); + return serviceTemplate; + } +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/resources/ChartList.json b/participant/participant-impl/participant-impl-kubernetes/src/test/resources/ChartList.json new file mode 100644 index 000000000..4e355c38e --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/resources/ChartList.json @@ -0,0 +1,15 @@ +{ + "charts" : [ + { + "chartName" : "HelloWorld", + "version" : "1.0", + "namespace" : "onap", + "repository" : "chartMuseum" + }, + { + "chartName" : "nginx", + "version" : "1.1", + "namespace" : "onap" + } + ] +} diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/resources/application_test.properties b/participant/participant-impl/participant-impl-kubernetes/src/test/resources/application_test.properties new file mode 100644 index 000000000..188623af0 --- /dev/null +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/resources/application_test.properties @@ -0,0 +1,26 @@ +spring.security.user.name=healthcheck +spring.security.user.password=zb!XztG34 + +server.servlet.context-path=/onap/participantsim +server.error.path=/error +server.http-port=8083 + +participant.name=ControlLoopParticipant Kubernetes Test +participant.intermediaryParameters.name=Participant parameters +participant.intermediaryParameters.reportingTimeInterval=120000 +participant.intermediaryParameters.description=Participant Description +participant.intermediaryParameters.participantId.name=K8sParticipant0 +participant.intermediaryParameters.participantId.version=1.0.0 +participant.intermediaryParameters.participantType.name=org.onap.k8s.controlloop.K8SControlLoopParticipant +participant.intermediaryParameters.participantType.version=2.3.4 +participant.intermediaryParameters.clampControlLoopTopics.name=ControlLoop Topics +participant.intermediaryParameters.clampControlLoopTopics.topicSources[0].topic=POLICY-CLRUNTIME-PARTICIPANT +participant.intermediaryParameters.clampControlLoopTopics.topicSources[0].servers[0]=localhost +participant.intermediaryParameters.clampControlLoopTopics.topicSources[0].topicCommInfrastructure=dmaap +participant.intermediaryParameters.clampControlLoopTopics.topicSources[0].fetchTimeout=15000 +participant.intermediaryParameters.clampControlLoopTopics.topicSinks[0].topic=POLICY-CLRUNTIME-PARTICIPANT +participant.intermediaryParameters.clampControlLoopTopics.topicSinks[0].servers[0]=localhost +participant.intermediaryParameters.clampControlLoopTopics.topicSinks[0].topicCommInfrastructure=dmaap +participant.intermediaryParameters.clampControlLoopTopics.topicSinks[1].topic=POLICY-NOTIFICATION +participant.intermediaryParameters.clampControlLoopTopics.topicSinks[1].servers[0]=localhost +participant.intermediaryParameters.clampControlLoopTopics.topicSinks[1].topicCommInfrastructure=dmaap diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/resources/KubernetesHelm.yaml b/participant/participant-impl/participant-impl-kubernetes/src/test/resources/servicetemplates/KubernetesHelm.yaml index 3212b5ad2..3212b5ad2 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/test/resources/KubernetesHelm.yaml +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/resources/servicetemplates/KubernetesHelm.yaml @@ -187,5 +187,9 @@ <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> + <dependency> + <groupId>commons-fileupload</groupId> + <artifactId>commons-fileupload</artifactId> + </dependency> </dependencies> </project> diff --git a/runtime/extra/docker/clamp/docker-compose.yml b/runtime/extra/docker/clamp/docker-compose.yml index c56be0aae..f27e1de02 100644 --- a/runtime/extra/docker/clamp/docker-compose.yml +++ b/runtime/extra/docker/clamp/docker-compose.yml @@ -27,7 +27,7 @@ services: - clamp.env ports: - "10443:8443" - + third-party-proxy: image: python:2-slim volumes: diff --git a/runtime/src/main/java/org/onap/policy/clamp/clds/Application.java b/runtime/src/main/java/org/onap/policy/clamp/clds/Application.java index bdab9c9a2..aad678bf7 100644 --- a/runtime/src/main/java/org/onap/policy/clamp/clds/Application.java +++ b/runtime/src/main/java/org/onap/policy/clamp/clds/Application.java @@ -121,8 +121,8 @@ public class Application extends SpringBootServletInitializer { @Bean public ServletRegistrationBean<ClampServlet> camelServletRegistrationBean() throws IOException { eelfLogger.info(ResourceFileUtils.getResourceAsString("boot-message.txt") + "(v" - + ClampVersioning.getCldsVersionFromProps() + ")" + System.getProperty("line.separator") - + getSslExpirationDate()); + + ClampVersioning.getCldsVersionFromProps() + ")" + System.getProperty("line.separator") + + getSslExpirationDate()); var registration = new ServletRegistrationBean<ClampServlet>(new ClampServlet(), "/restservices/clds/*"); registration.setName("CamelServlet"); return registration; @@ -150,7 +150,7 @@ public class Application extends SpringBootServletInitializer { private Connector createRedirectConnector(int redirectSecuredPort) { if (redirectSecuredPort <= 0) { eelfLogger.warn("HTTP port redirection to HTTPS is disabled because the HTTPS port is 0 (random port) or -1" - + " (Connector disabled)"); + + " (Connector disabled)"); return null; } var connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); @@ -167,15 +167,15 @@ public class Application extends SpringBootServletInitializer { if (keystoreFile != null) { var keystore = KeyStore.getInstance(keyStoreType); keystore.load(ResourceFileUtils.getResourceAsStream(keystoreFile.replace("classpath:", "")), - PassDecoder.decode(keyStorePass, keyFile).toCharArray()); + PassDecoder.decode(keyStorePass, keyFile).toCharArray()); Enumeration<String> aliases = keystore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if ("X.509".equals(keystore.getCertificate(alias).getType())) { result.append("* " + alias + " expires " - + ((X509Certificate) keystore.getCertificate(alias)).getNotAfter() - + System.getProperty("line.separator")); + + ((X509Certificate) keystore.getCertificate(alias)).getNotAfter() + + System.getProperty("line.separator")); } } } else { diff --git a/runtime/src/main/java/org/onap/policy/clamp/loop/template/LoopElementModel.java b/runtime/src/main/java/org/onap/policy/clamp/loop/template/LoopElementModel.java index 52f1c5d23..f1ab35722 100644 --- a/runtime/src/main/java/org/onap/policy/clamp/loop/template/LoopElementModel.java +++ b/runtime/src/main/java/org/onap/policy/clamp/loop/template/LoopElementModel.java @@ -40,6 +40,8 @@ import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; import org.hibernate.annotations.SortNatural; import org.onap.policy.clamp.clds.tosca.update.ToscaConverterWithDictionarySupport; import org.onap.policy.clamp.loop.Loop; @@ -52,7 +54,8 @@ import org.onap.policy.clamp.policy.operational.OperationalPolicy; * This class represents a micro service/operational/... model for a loop template. * So it's an element in the flow (a box shown in the loop). */ - +@Getter +@Setter @Entity @Table(name = "loop_element_models") public class LoopElementModel extends AuditEntity implements Serializable { @@ -113,15 +116,6 @@ public class LoopElementModel extends AuditEntity implements Serializable { private Set<LoopTemplateLoopElementModel> usedByLoopTemplates = new HashSet<>(); /** - * policyModels getter. - * - * @return the policyModel - */ - public SortedSet<PolicyModel> getPolicyModels() { - return policyModels; - } - - /** * Method to add a new policyModel to the list. * * @param policyModel The policy model @@ -132,103 +126,6 @@ public class LoopElementModel extends AuditEntity implements Serializable { } /** - * name getter. - * - * @return the name - */ - public String getName() { - return name; - } - - /** - * name setter. - * - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * blueprint getter. - * - * @return the blueprint - */ - public String getBlueprint() { - return blueprint; - } - - /** - * blueprint setter. - * - * @param blueprint the blueprint to set - */ - public void setBlueprint(String blueprint) { - this.blueprint = blueprint; - } - - /** - * dcaeBlueprintId getter. - * - * @return the dcaeBlueprintId - */ - public String getDcaeBlueprintId() { - return dcaeBlueprintId; - } - - /** - * dcaeBlueprintId setter. - * - * @param dcaeBlueprintId the dcaeBlueprintId to set - */ - public void setDcaeBlueprintId(String dcaeBlueprintId) { - this.dcaeBlueprintId = dcaeBlueprintId; - } - - /** - * loopElementType getter. - * - * @return the loopElementType - */ - public String getLoopElementType() { - return loopElementType; - } - - /** - * loopElementType setter. - * - * @param loopElementType the loopElementType to set - */ - public void setLoopElementType(String loopElementType) { - this.loopElementType = loopElementType; - } - - /** - * shortName getter. - * - * @return the shortName - */ - public String getShortName() { - return shortName; - } - - /** - * * @param shortName the shortName to set. - */ - public void setShortName(String shortName) { - this.shortName = shortName; - } - - /** - * usedByLoopTemplates getter. - * - * @return the usedByLoopTemplates - */ - public Set<LoopTemplateLoopElementModel> getUsedByLoopTemplates() { - return usedByLoopTemplates; - } - - /** * Default constructor for serialization. */ public LoopElementModel() { diff --git a/runtime/src/main/java/org/onap/policy/clamp/tosca/Dictionary.java b/runtime/src/main/java/org/onap/policy/clamp/tosca/Dictionary.java index 40ea25ff8..da6454a44 100644 --- a/runtime/src/main/java/org/onap/policy/clamp/tosca/Dictionary.java +++ b/runtime/src/main/java/org/onap/policy/clamp/tosca/Dictionary.java @@ -177,9 +177,9 @@ public class Dictionary extends AuditEntity implements Serializable { /** * Constructor. * - * @param name The Dictionary name + * @param name The Dictionary name * @param secondLevelDictionary defines if dictionary is a secondary level - * @param subDictionaryType defines the type of secondary level dictionary + * @param subDictionaryType defines the type of secondary level dictionary */ public Dictionary(String name, int secondLevelDictionary, String subDictionaryType) { this.name = name; diff --git a/runtime/src/main/resources/application-noaaf.properties b/runtime/src/main/resources/application-noaaf.properties index a8f4399c4..e698cc0ce 100644 --- a/runtime/src/main/resources/application-noaaf.properties +++ b/runtime/src/main/resources/application-noaaf.properties @@ -4,6 +4,7 @@ # ================================================================================ # Copyright (C) 2017-2019, 2021 AT&T Intellectual Property. All rights # reserved. +# Modifications 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. @@ -16,6 +17,8 @@ # 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============================================ # =================================================================== # diff --git a/runtime/src/main/resources/clds/camel/routes/controlloop-flows.xml b/runtime/src/main/resources/clds/camel/routes/controlloop-flows.xml index 009e00fca..d12cd9df1 100644 --- a/runtime/src/main/resources/clds/camel/routes/controlloop-flows.xml +++ b/runtime/src/main/resources/clds/camel/routes/controlloop-flows.xml @@ -20,8 +20,7 @@ </setProperty> <log loggingLevel="INFO" message="Endpoint to get Tosca Service Template: {{clamp.config.controlloop.runtime.url}}/onap/controlloop/v2/commission/toscaservicetemplate"></log> - <toD - uri="{{clamp.config.controlloop.runtime.url}}/onap/controlloop/v2/commission/toscaservicetemplate?name=${exchangeProperty[name]}&version=${exchangeProperty[version]}&bridgeEndpoint=true&useSystemProperties=true&throwExceptionOnFailure=${exchangeProperty[raiseHttpExceptionFlag]}&authMethod=Basic&authUsername={{clamp.config.controlloop.runtime.userName}}&authPassword={{clamp.config.controlloop.runtime.password}}&authenticationPreemptive=true&connectionClose=true"/> + <toD uri="{{clamp.config.controlloop.runtime.url}}/onap/controlloop/v2/commission/toscaservicetemplate?name=${exchangeProperty[name]}&version=${exchangeProperty[version]}&bridgeEndpoint=true&useSystemProperties=true&throwExceptionOnFailure=${exchangeProperty[raiseHttpExceptionFlag]}&authMethod=Basic&authUsername={{clamp.config.controlloop.runtime.userName}}&authPassword={{clamp.config.controlloop.runtime.password}}&authenticationPreemptive=true&connectionClose=true"/> <convertBodyTo type="java.lang.String"/> <doFinally> <to uri="direct:reset-raise-http-exception-flag"/> @@ -40,10 +39,13 @@ <setHeader name="CamelHttpMethod"> <constant>POST</constant> </setHeader> + <setHeader name="Content-Type"> + <constant>application/json</constant> + </setHeader> <log loggingLevel="INFO" message="Endpoint to send Tosca Service Template: {{clamp.config.controlloop.runtime.url}}/onap/controlloop/v2/commission"></log> <toD - uri="{{clamp.config.controlloop.runtime.url}}/onap/controlloop/v2/commission?bridgeEndpoint=true&useSystemProperties=true&mapHttpMessageHeaders=false&throwExceptionOnFailure=${exchangeProperty[raiseHttpExceptionFlag]}&authMethod=Basic&authUsername={{clamp.config.controlloop.runtime.userName}}&authPassword={{clamp.config.controlloop.runtime.password}}&authenticationPreemptive=true&connectionClose=true"/> + uri="{{clamp.config.controlloop.runtime.url}}/onap/controlloop/v2/commission?bridgeEndpoint=true&useSystemProperties=true&throwExceptionOnFailure=${exchangeProperty[raiseHttpExceptionFlag]}&authMethod=Basic&authUsername={{clamp.config.controlloop.runtime.userName}}&authPassword={{clamp.config.controlloop.runtime.password}}&authenticationPreemptive=true&connectionClose=true"/> <convertBodyTo type="java.lang.String"/> <doFinally> <to uri="direct:reset-raise-http-exception-flag"/> diff --git a/runtime/src/test/java/org/onap/policy/clamp/policy/downloader/PolicyEngineControllerTestItCase.java b/runtime/src/test/java/org/onap/policy/clamp/policy/downloader/PolicyEngineControllerTestItCase.java index 4dd620c4b..8fbd2712b 100644 --- a/runtime/src/test/java/org/onap/policy/clamp/policy/downloader/PolicyEngineControllerTestItCase.java +++ b/runtime/src/test/java/org/onap/policy/clamp/policy/downloader/PolicyEngineControllerTestItCase.java @@ -72,15 +72,15 @@ public class PolicyEngineControllerTestItCase { List<PolicyModel> policyModelsList = policyModelsRepository.findAll(); assertThat(policyModelsList.size()).isGreaterThanOrEqualTo(5); assertThat(policyModelsList).contains(new PolicyModel("onap.policies.controlloop.operational.common.Drools", - null, "1.0.0")); + null, "1.0.0")); assertThat(policyModelsList).contains(new PolicyModel("onap.policies.controlloop.operational.common.Apex", - null, "1.0.0")); + null, "1.0.0")); assertThat(policyModelsList) - .contains(new PolicyModel("onap.policies.controlloop.guard.common.FrequencyLimiter", null, "1.0.0")); + .contains(new PolicyModel("onap.policies.controlloop.guard.common.FrequencyLimiter", null, "1.0.0")); assertThat(policyModelsList) - .contains(new PolicyModel("onap.policies.controlloop.guard.common.Blacklist", null, "1.0.0")); + .contains(new PolicyModel("onap.policies.controlloop.guard.common.Blacklist", null, "1.0.0")); assertThat(policyModelsList) - .contains(new PolicyModel("onap.policies.controlloop.guard.common.MinMax", null, "2.0.0")); + .contains(new PolicyModel("onap.policies.controlloop.guard.common.MinMax", null, "2.0.0")); // Re-do it to check that there is no issue with duplicate key policyController.synchronizeAllPolicies(); @@ -104,9 +104,9 @@ public class PolicyEngineControllerTestItCase { assertThat(policyModelsList.size()).isGreaterThanOrEqualTo(2); PolicyModel policy1 = policyModelsRepository - .getOne(new PolicyModelId("onap.policies.monitoring.test", "1.0.0")); + .getOne(new PolicyModelId("onap.policies.monitoring.test", "1.0.0")); PolicyModel policy2 = policyModelsRepository - .getOne(new PolicyModelId("onap.policies.controlloop.Operational", "1.0.0")); + .getOne(new PolicyModelId("onap.policies.controlloop.Operational", "1.0.0")); String expectedRes1 = "{\"supportedPdpGroups\":[{\"monitoring\":[\"xacml\"]}]}"; JsonObject expectedJson1 = JsonUtils.GSON.fromJson(expectedRes1, JsonObject.class); diff --git a/runtime/src/test/java/org/onap/policy/clamp/runtime/RuntimeCommissioningResponseTestItCase.java b/runtime/src/test/java/org/onap/policy/clamp/runtime/RuntimeCommissioningResponseTestItCase.java new file mode 100644 index 000000000..0ba1486bb --- /dev/null +++ b/runtime/src/test/java/org/onap/policy/clamp/runtime/RuntimeCommissioningResponseTestItCase.java @@ -0,0 +1,91 @@ +/*- + * ============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.runtime; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.ExchangeBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.onap.policy.clamp.clds.Application; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class) +public class RuntimeCommissioningResponseTestItCase { + @Autowired + CamelContext camelContext; + + private static final String SAMPLE_TOSCA_TEMPLATE = + "{\"tosca_definitions_version\": \"tosca_simple_yaml_1_1_0\"," + + "\"data_types\": {},\"node_types\": {}, \"policy_types\": {}," + + " \"topology_template\": {}," + + " \"name\": \"ToscaServiceTemplateSimple\", \"version\": \"1.0.0\", \"metadata\": {}}"; + + @Test + public void testToscaServiceTemplateStatus() { + ProducerTemplate prodTemplate = camelContext.createProducerTemplate(); + + Exchange exchangeResponse = + prodTemplate.send("direct:get-service-template", ExchangeBuilder.anExchange(camelContext) + .withProperty("name", "ToscaServiceTemplate") + .withProperty("version", "1.0.0") + .withProperty("raiseHttpExceptionFlag", "true") + .build()); + + assertThat(HttpStatus.valueOf((Integer) exchangeResponse.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE)) + .is2xxSuccessful()).isTrue(); + } + + @Test + public void testToscaServiceTemplateGetResponseBody() { + ProducerTemplate prodTemplate = camelContext.createProducerTemplate(); + + Exchange exchangeResponse = + prodTemplate.send("direct:get-service-template", ExchangeBuilder.anExchange(camelContext) + .withProperty("name", "ToscaServiceTemplate") + .withProperty("version", "1.0.0") + .withProperty("raiseHttpExceptionFlag", "true") + .build()); + + assertThat(exchangeResponse.getIn().getBody().toString()).isEqualTo(SAMPLE_TOSCA_TEMPLATE); + } + + @Test + public void testCommissioningOfToscaServiceTemplateStatus() { + ProducerTemplate prodTemplate = camelContext.createProducerTemplate(); + + Exchange exchangeResponse = + prodTemplate.send("direct:commission-service-template", ExchangeBuilder.anExchange(camelContext) + .withBody(SAMPLE_TOSCA_TEMPLATE) + .withProperty("raiseHttpExceptionFlag", "true") + .build()); + + assertThat(HttpStatus.valueOf((Integer) exchangeResponse.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE)) + .is2xxSuccessful()).isTrue(); + } +} diff --git a/runtime/src/test/resources/application.properties b/runtime/src/test/resources/application.properties index 60704acf5..54a89078d 100644 --- a/runtime/src/test/resources/application.properties +++ b/runtime/src/test/resources/application.properties @@ -4,6 +4,7 @@ # ================================================================================ # Copyright (C) 2017-2018, 2021 AT&T Intellectual Property. All rights # reserved. +# Modifications 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. @@ -16,6 +17,8 @@ # 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============================================ # =================================================================== # @@ -180,6 +183,6 @@ clamp.config.tosca.converter.default.datatypes=classpath:/clds/tosca-converter/d clamp.config.tosca.converter.dictionary.support.enabled=true # Configuration settings for ControlLoop Runtime Rest API -clamp.config.controlloop.runtime.url=http://localhost:6969 +clamp.config.controlloop.runtime.url=http://localhost:${docker.http-cache.port.host} clamp.config.controlloop.runtime.userName=healthcheck clamp.config.controlloop.runtime.password=zb!XztG34 diff --git a/runtime/src/test/resources/http-cache/third_party_proxy.py b/runtime/src/test/resources/http-cache/third_party_proxy.py index 7fe316852..50bd43a35 100644 --- a/runtime/src/test/resources/http-cache/third_party_proxy.py +++ b/runtime/src/test/resources/http-cache/third_party_proxy.py @@ -5,6 +5,7 @@ # ================================================================================ # Copyright (C) 2018 AT&T Intellectual Property. All rights # reserved. +# Modifications 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. @@ -17,6 +18,8 @@ # 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============================================ # =================================================================== # @@ -94,23 +97,23 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): def _get_cached_file_folder_name(self,folder): cached_file_folder = '%s/%s' % (folder, self.path,) print("Cached file name before escaping : %s" % cached_file_folder) - cached_file_folder = cached_file_folder.replace('<','<').replace('>','>').replace('?','?').replace('*','*').replace('\\','*').replace(':',':').replace('|','|') + cached_file_folder = cached_file_folder.replace('+','').replace('<','<').replace('>','>').replace('?','?').replace('*','*').replace('\\','*').replace(':',':').replace('|','|') print("Cached file name after escaping (used for cache storage) : %s" % cached_file_folder) return cached_file_folder - + def _get_cached_content_file_name(self,cached_file_folder): return "%s/.file" % (cached_file_folder,) - + def _get_cached_header_file_name(self,cached_file_folder): return "%s/.header" % (cached_file_folder,) - + def _execute_content_generated_cases(self,http_type): print("Testing special cases, cache files will be sent to :" +TMP_ROOT) cached_file_folder = self._get_cached_file_folder_name(TMP_ROOT) cached_file_content = self._get_cached_content_file_name(cached_file_folder) cached_file_header = self._get_cached_header_file_name(cached_file_folder) _file_available = os.path.exists(cached_file_content) - + if self.path.startswith("/dcae-service-types?asdcResourceId=") and http_type == "GET": if not _file_available: print "self.path start with /dcae-service-types?asdcResourceId=, generating response json..." @@ -120,7 +123,7 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): print "typeId generated: " + typeName + " and typeName: "+ typeId jsonGenerated = "{\"totalCount\":1, \"items\":[{\"typeId\":\"" + typeId + "\", \"typeName\":\"" + typeName +"\"}]}" print "jsonGenerated: " + jsonGenerated - + os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") @@ -132,14 +135,14 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): print "self.path start with /dcae-operationstatus/install, generating response json..." jsonGenerated = "{\"operationType\": \"install\", \"status\": \"succeeded\"}" print "jsonGenerated: " + jsonGenerated - + try: os.makedirs(cached_file_folder, 0777) except OSError as e: if e.errno != errno.EEXIST: raise print(cached_file_folder+" already exists") - + with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w') as f: @@ -150,14 +153,14 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): print "self.path start with /dcae-operationstatus/uninstall, generating response json..." jsonGenerated = "{\"operationType\": \"uninstall\", \"status\": \"succeeded\"}" print "jsonGenerated: " + jsonGenerated - + try: os.makedirs(cached_file_folder, 0777) except OSError as e: if e.errno != errno.EEXIST: raise print(cached_file_folder+" already exists") - + with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w') as f: @@ -169,7 +172,7 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): jsondata = json.loads(self.data_string) jsonGenerated = "{\"artifactName\":\"" + jsondata['artifactName'] + "\",\"artifactType\":\"" + jsondata['artifactType'] + "\",\"artifactURL\":\"" + self.path + "\",\"artifactDescription\":\"" + jsondata['description'] + "\",\"artifactChecksum\":\"ZjJlMjVmMWE2M2M1OTM2MDZlODlmNTVmZmYzNjViYzM=\",\"artifactUUID\":\"" + str(uuid.uuid4()) + "\",\"artifactVersion\":\"1\"}" print "jsonGenerated: " + jsonGenerated - + os.makedirs(cached_file_folder, 0777) with open(cached_file_header, 'w') as f: f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") @@ -187,7 +190,7 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") with open(cached_file_content, 'w+') as f: f.write(jsonGenerated) - return True + return True elif self.path.startswith("/dcae-deployments/") and http_type == "DELETE": print "self.path start with /dcae-deployments/ UNDEPLOY, generating response json..." #jsondata = json.loads(self.data_string) @@ -222,7 +225,7 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): print "self.path start with DELETE new policy API /policy/api/v1/policytypes/ ..." if not os.path.exists(cached_file_folder): os.makedirs(cached_file_folder, 0777) - + with open(cached_file_header, 'w+') as f: f.write("{\"Content-Length\": \"" + str(len("")) + "\", \"Content-Type\": \""+str("")+"\"}") with open(cached_file_content, 'w+') as f: @@ -265,10 +268,34 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): with open(cached_file_content, 'w') as f: f.write(response) return True + elif (self.path.startswith("/onap/controlloop/v2/commission/toscaservicetemplate")) and http_type == "GET": + if not _file_available: + cached_file_folder = cached_file_folder.split('bridgeEndpoint')[0] + print ("cached file folder for onap is %s: ", cached_file_folder) + print "self.path start with /onap/controlloop/v2/commission/, generating response json..." + jsonGenerated = "{\"tosca_definitions_version\": \"tosca_simple_yaml_1_1_0\",\"data_types\": {},\"node_types\": {}, \"policy_types\": {}, \"topology_template\": {}, \"name\": \"ToscaServiceTemplateSimple\", \"version\": \"1.0.0\", \"metadata\": {}}" + print "jsonGenerated: " + jsonGenerated + if not os.path.exists(cached_file_folder): + os.makedirs(cached_file_folder, 0777) + + with open(cached_file_header, 'w+') as f: + f.write("{\"Content-Length\": \"" + str(len(jsonGenerated)) + "\", \"Content-Type\": \"application/json\"}") + with open(cached_file_content, 'w+') as f: + f.write(jsonGenerated) + return True + elif (self.path.startswith("/onap/controlloop/v2/commission")) and http_type == "POST": + print "self.path start with POST /onap/controlloop/v2/commission, copying body to response ..." + if not os.path.exists(cached_file_folder): + os.makedirs(cached_file_folder, 0777) + with open(cached_file_header, 'w+') as f: + f.write("{\"Content-Length\": \"" + str(len(self.data_string)) + "\", \"Content-Type\": \""+str(self.headers['Content-Type'])+"\"}") + with open(cached_file_content, 'w+') as f: + f.write(self.data_string) + return True else: return False - + def do_GET(self): cached_file_folder = "" cached_file_content ="" @@ -352,7 +379,7 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): _file_available = os.path.exists(cached_file_content) if not _file_available: - + if not HOST: self.send_response(404) self.end_headers() @@ -492,6 +519,6 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): httpd = SocketServer.ForkingTCPServer(('', PORT), Proxy) httpd.allow_reuse_address = True print "Listening on port "+ str(PORT) + "(Press Ctrl+C/Ctrl+Z to stop HTTPD Caching script)" -print "Caching folder " + CACHE_ROOT + ", Tmp folder for generated files " + TMP_ROOT +print "Caching folder " + CACHE_ROOT + ", Tmp folder for generated files " + TMP_ROOT signal.signal(signal.SIGINT, signal_handler) -httpd.serve_forever()
\ No newline at end of file +httpd.serve_forever() |