diff options
Diffstat (limited to 'participant/participant-impl/participant-impl-kubernetes')
21 files changed, 1190 insertions, 32 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 |