Initial commit for kubernetes participant in CLAMP 81/121581/10
authorliamfallon <liam.fallon@est.tech>
Mon, 7 Jun 2021 15:41:12 +0000 (16:41 +0100)
committerliamfallon <liam.fallon@est.tech>
Wed, 9 Jun 2021 13:34:20 +0000 (14:34 +0100)
Spring application that exposes REST end points for installing, uninstalling, onboarding
and deleting of helm charts to/from local directory.
CL runtime can also trigger installation and uninstallation of helm charts from both local and configured helm repositories.

Junits will be committed as a separate review.

Issue-ID: POLICY-3240
Change-Id: I7633b6fd6ad41fc8fa55d3722e44f1b2ec132e50
Signed-off-by: zrrmmua <ramesh.murugan.iyer@est.tech>
Signed-off-by: liamfallon <liam.fallon@est.tech>
20 files changed:
participant/participant-impl/participant-impl-kubernetes/pom.xml
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/Application.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/BeanFactory.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/ParametersConfig.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/ParticipantIntermediaryConfig.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/controller/ChartController.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/exception/ServiceException.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/handler/ControlLoopElementHandler.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/helm/HelmClient.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartInfo.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartList.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/InstallationInfo.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/parameters/ParticipantK8sParameterHandler.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/parameters/ParticipantK8sParameters.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartService.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartStore.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/resources/config/KubernetesParticipantConfig.json [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/resources/config/application.yaml [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/test/resources/KubernetesHelm.yaml [new file with mode: 0644]
runtime/pom.xml

index a85c5fd..504d91d 100644 (file)
@@ -19,7 +19,7 @@
 -->
 
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
 
     <artifactId>policy-clamp-participant-impl-kubernetes</artifactId>
     <name>${project.artifactId}</name>
-    <description>Kubernetes participant, that allows microservices running in Kubernetes to partake in control loops</description>
+    <description>Kubernetes participant, that allows k8s pods to partake in control loops</description>
+
+    <properties>
+        <springboot.version>2.5.0</springboot.version>
+        <immutable.version>2.8.8</immutable.version>
+        <springfox.version>3.0.0</springfox.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- Spring Boot BOM -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${springboot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat.embed</groupId>
+            <artifactId>tomcat-embed-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.immutables</groupId>
+            <artifactId>value</artifactId>
+            <version>${immutable.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.immutables</groupId>
+            <artifactId>gson</artifactId>
+            <version>${immutable.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>${springfox.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>${springfox.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <version>1.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.clamp.participant</groupId>
+            <artifactId>policy-clamp-participant-intermediary</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <resources>
+            <!-- Output the version of the control loop system -->
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/version.txt</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>false</filtering>
+                <excludes>
+                    <exclude>**/version.txt</exclude>
+                </excludes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${springboot.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                        <phase>package</phase>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
 </project>
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
new file mode 100644 (file)
index 0000000..ffa0bce
--- /dev/null
@@ -0,0 +1,40 @@
+/*-
+ * ============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;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Starter.
+ *
+ */
+@SpringBootApplication
+public class Application {
+    /**
+     * Main class.
+     * @param args args
+     */
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+}
+
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/BeanFactory.java
new file mode 100644 (file)
index 0000000..3199d0c
--- /dev/null
@@ -0,0 +1,71 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.configurations;
+
+import org.apache.catalina.connector.Connector;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.multipart.MultipartResolver;
+import org.springframework.web.multipart.commons.CommonsMultipartResolver;
+
+/**
+ * Bean Factory class for helm client.
+ */
+@Configuration
+public class BeanFactory {
+
+    @Value("${server.http-port}")
+    private int httpPort = 0;
+
+    /**
+     * Method to create servlet container bean.
+     * @return webserver factory
+     */
+    @Bean
+    public ServletWebServerFactory servletContainer() {
+        var tomcat = new TomcatServletWebServerFactory();
+        if (httpPort > 0) {
+            tomcat.addAdditionalTomcatConnectors(getHttpConnector(httpPort));
+        }
+        return tomcat;
+    }
+
+    private static Connector getHttpConnector(int httpPort) {
+        var connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
+        connector.setScheme("http");
+        connector.setPort(httpPort);
+        connector.setSecure(false);
+        return connector;
+    }
+
+    /**
+     * Method to create multipartResolver bean.
+     * @return MultipartResolver
+     */
+    @Bean(name = "multipartResolver")
+    public MultipartResolver multipartResolver() {
+        var multipartResolver = new CommonsMultipartResolver();
+        multipartResolver.setMaxUploadSize(100000);
+        return multipartResolver;
+    }
+
+}
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/ParametersConfig.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/ParametersConfig.java
new file mode 100644 (file)
index 0000000..5f2a4e4
--- /dev/null
@@ -0,0 +1,41 @@
+/*-
+ * ============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.configurations;
+
+import org.onap.policy.clamp.controlloop.common.exception.ControlLoopException;
+import org.onap.policy.clamp.controlloop.participant.kubernetes.parameters.ParticipantK8sParameterHandler;
+import org.onap.policy.clamp.controlloop.participant.kubernetes.parameters.ParticipantK8sParameters;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ParametersConfig {
+
+    @Value("${participant.file}")
+    private String file;
+
+    @Bean
+    public ParticipantK8sParameters participantK8sParameters() throws ControlLoopException {
+        return new ParticipantK8sParameterHandler().toParticipantK8sParameters(file);
+    }
+}
+
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/ParticipantIntermediaryConfig.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/ParticipantIntermediaryConfig.java
new file mode 100644 (file)
index 0000000..d8c3992
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * ============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.configurations;
+
+import org.onap.policy.clamp.controlloop.participant.intermediary.api.ParticipantIntermediaryApi;
+import org.onap.policy.clamp.controlloop.participant.intermediary.api.ParticipantIntermediaryFactory;
+import org.onap.policy.clamp.controlloop.participant.kubernetes.handler.ControlLoopElementHandler;
+import org.onap.policy.clamp.controlloop.participant.kubernetes.parameters.ParticipantK8sParameters;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ParticipantIntermediaryConfig {
+
+    /**
+     * Create ParticipantIntermediaryApi.
+     *
+     * @param parameters the K8s Participant Parameters
+     * @param clElementHandler the ControlLoop Element Handler
+     * @return ParticipantIntermediaryApi
+     */
+    @Bean
+    public ParticipantIntermediaryApi participantIntermediaryApi(ParticipantK8sParameters parameters,
+                                                                 ControlLoopElementHandler clElementHandler) {
+        ParticipantIntermediaryApi intermediaryApi = new ParticipantIntermediaryFactory().createApiImplementation();
+        intermediaryApi.init(parameters.getIntermediaryParameters());
+        intermediaryApi.registerControlLoopElementListener(clElementHandler);
+        return intermediaryApi;
+    }
+}
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
new file mode 100644 (file)
index 0000000..427b06f
--- /dev/null
@@ -0,0 +1,166 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import java.io.IOException;
+import java.util.ArrayList;
+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.models.InstallationInfo;
+import org.onap.policy.clamp.controlloop.participant.kubernetes.service.ChartService;
+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.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+@RestController("chartController")
+@RequestMapping("helm")
+@Api(tags = {"chart"})
+public class ChartController {
+
+    @Autowired
+    private ChartService chartService;
+
+    private static final StandardCoder CODER = new StandardCoder();
+
+    /**
+     * REST endpoint to get all the charts.
+     *
+     * @return List of charts installed
+     */
+    @GetMapping(path = "/charts", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Return all Charts")
+    @ApiResponses(value = {@ApiResponse(code = 200, message = "chart List")})
+    public ResponseEntity<ChartList> getAllCharts() {
+        return new ResponseEntity<>(ChartList.builder().charts(new ArrayList<>(chartService.getAllCharts())).build(),
+                HttpStatus.OK);
+    }
+
+    /**
+     * REST endpoint to install a helm chart.
+     *
+     * @param info Info of the chart to be installed
+     * @return Status of the install operation
+     * @throws ServiceException incase of error
+     */
+    @PostMapping(path = "/install", consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Install the chart")
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "chart Installed")})
+    public ResponseEntity<Object> installChart(@RequestBody InstallationInfo info)
+            throws ServiceException, IOException {
+        ChartInfo chart = chartService.getChart(info.getName(), info.getVersion());
+        if (chart == null) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+
+        chartService.installChart(chart);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    /**
+     * REST endpoint to uninstall a specific chart.
+     *
+     * @param name name of the chart
+     * @param version version of the chart
+     * @return Status of operation
+     * @throws ServiceException incase of error.
+     */
+    @DeleteMapping(path = "/uninstall/{name}/{version}", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Uninstall the Chart")
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "chart Uninstalled")})
+    public ResponseEntity<Object> uninstallChart(@PathVariable("name") String name,
+            @PathVariable("version") String version) throws ServiceException {
+        ChartInfo chart = chartService.getChart(name, version);
+        if (chart == null) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+
+        chartService.uninstallChart(chart);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    /**
+     * REST endpoint to onboard a chart.
+     *
+     * @param chartFile Multipart file for the helm chart
+     * @param infoJson AppInfo of the chart
+     * @return Status of onboard operation
+     * @throws ServiceException incase of error
+     * @throws IOException incase of IO error
+     */
+    @PostMapping(path = "/charts", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Onboard the Chart")
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Chart Onboarded")})
+    public ResponseEntity<String> onboardChart(@RequestPart("chart") MultipartFile chartFile,
+            @RequestParam(name = "values", required = false) MultipartFile overrideFile,
+            @RequestParam("info") String infoJson) throws ServiceException, IOException {
+
+        ChartInfo info;
+        try {
+            info = CODER.decode(infoJson, ChartInfo.class);
+        } catch (CoderException e) {
+            throw new ServiceException("Error parsing the chart information", e);
+        }
+
+        chartService.saveChart(info, chartFile, overrideFile);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * REST endpoint to delete a specific helm chart.
+     *
+     * @param name name of the chart
+     * @param version version of the chart
+     * @return Status of operation
+     * @throws ServiceException incase 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 {
+
+        ChartInfo chart = chartService.getChart(name, version);
+        if (chart == null) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+
+        chartService.deleteChart(chart);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+}
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/exception/ServiceException.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/exception/ServiceException.java
new file mode 100644 (file)
index 0000000..9a825cf
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.exception;
+
+public class ServiceException extends Exception {
+
+    private static final long serialVersionUID = 6810785674716590648L;
+
+    public ServiceException(String message) {
+        super(message);
+    }
+
+    public ServiceException(String message, Exception originalException) {
+        super(message, originalException);
+    }
+}
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
new file mode 100644 (file)
index 0000000..5f1dcb8
--- /dev/null
@@ -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.handler;
+
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+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.ControlLoopElementListener;
+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.service.ChartService;
+import org.onap.policy.models.base.PfModelException;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * This class handles implementation of controlLoopElement updates.
+ */
+@Component
+public class ControlLoopElementHandler implements ControlLoopElementListener {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    @Autowired
+    private ChartService chartService;
+
+    @Autowired
+    private ParticipantIntermediaryApi intermediaryApi;
+
+    // Map of CLElement Id and installed Helm charts
+    private final Map<UUID, ChartInfo> chartMap = new HashMap<>();
+
+    /**
+     * Callback method to handle a control loop element state change.
+     *
+     * @param controlLoopElementId the ID of the control loop element
+     * @param currentState the current state of the control loop element
+     * @param newState the state to which the control loop element is changing to
+     */
+    @Override
+    public synchronized void controlLoopElementStateChange(UUID controlLoopElementId, ControlLoopState currentState,
+            ControlLoopOrderedState newState) {
+        switch (newState) {
+            case UNINITIALISED:
+                ChartInfo chart = chartMap.get(controlLoopElementId);
+                if (chart != null) {
+                    LOGGER.info("Helm deployment to be deleted {} ", chart.getReleaseName());
+                    try {
+                        chartService.uninstallChart(chart);
+                        intermediaryApi.updateControlLoopElementState(controlLoopElementId, newState,
+                                ControlLoopState.UNINITIALISED);
+                    } catch (ServiceException se) {
+                        LOGGER.warn("deletion of Helm deployment failed", se);
+                    }
+                }
+                break;
+            case PASSIVE:
+                intermediaryApi.updateControlLoopElementState(controlLoopElementId, newState, ControlLoopState.PASSIVE);
+                break;
+            case RUNNING:
+                intermediaryApi.updateControlLoopElementState(controlLoopElementId, newState, ControlLoopState.RUNNING);
+                break;
+            default:
+                LOGGER.warn("cannot transition from state {} to state {}", currentState, newState);
+                break;
+        }
+    }
+
+
+    /**
+     * Callback method to handle an update on a control loop element.
+     *
+     * @param element the information on the control loop element
+     * @param controlLoopDefinition toscaServiceTemplate
+     * @throws PfModelException in case of an exception
+     */
+    @Override
+    public synchronized void controlLoopElementUpdate(ControlLoopElement element,
+            ToscaServiceTemplate controlLoopDefinition) throws PfModelException {
+
+        for (Map.Entry<String, ToscaNodeTemplate> nodeTemplate : controlLoopDefinition.getToscaTopologyTemplate()
+                .getNodeTemplates().entrySet()) {
+
+            // Fetching the node template of corresponding CL element
+            if (element.getDefinition().getName().equals(nodeTemplate.getKey())
+                    && nodeTemplate.getValue().getProperties().containsKey("chart")) {
+                @SuppressWarnings("unchecked")
+                Map<String, Object> chartData =
+                        (Map<String, Object>) nodeTemplate.getValue().getProperties().get("chart");
+
+                LOGGER.info("Installation request received for the Helm Chart {} ", chartData);
+                var chart = new ChartInfo(String.valueOf(chartData.get("release_name")),
+                        String.valueOf(chartData.get("chart_name")), String.valueOf(chartData.get("version")),
+                        String.valueOf(chartData.get("namespace")));
+                try {
+                    var repositoryValue = chartData.get("repository");
+                    if (repositoryValue != null) {
+                        chart.setRepository(String.valueOf(repositoryValue));
+                    }
+                    chartService.installChart(chart);
+                    chartMap.put(element.getId(), chart);
+                } catch (IOException | ServiceException ise) {
+                    LOGGER.warn("installation of Helm chart failed", ise);
+                }
+            }
+        }
+    }
+
+    /**
+     * Overridden method.
+     *
+     * @param controlLoopElementId controlLoopElement id
+     * @throws PfModelException incase of error
+     */
+    @Override
+    public synchronized void handleStatistics(UUID controlLoopElementId) throws PfModelException {
+        // TODO Implement statistics functionality
+    }
+}
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
new file mode 100644 (file)
index 0000000..456122f
--- /dev/null
@@ -0,0 +1,201 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.helm;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.io.IOUtils;
+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.service.ChartStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Client to talk with Helm cli. Supports helm3 + version
+ */
+@Component
+public class HelmClient {
+
+    @Autowired
+    private ChartStore chartStore;
+
+    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+
+    /**
+     * Install a chart.
+     *
+     * @param chart name and version.
+     * @throws ServiceException incase of error
+     */
+    public void installChart(ChartInfo chart) throws ServiceException {
+        var processBuilder = prepareCreateNamespaceCommand(chart.getNamespace());
+        try {
+            executeCommand(processBuilder);
+        } catch (ServiceException e) {
+            logger.warn("Namespace not created", e);
+        }
+        processBuilder = prepareInstallCommand(chart);
+        logger.info("Installing helm chart {} from the repository {} ", chart.getChartName(), chart.getRepository());
+        executeCommand(processBuilder);
+        logger.info("Chart {} installed successfully", chart.getChartName());
+    }
+
+    /**
+     * Finds helm chart repository for the chart.
+     *
+     * @param chart ChartInfo.
+     * @throws ServiceException incase of error
+     */
+    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;
+
+        var process = helmRepoVerifyCommand(chart.getChartName()).start();
+
+        try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+            String line = reader.readLine();
+            while (line != null) {
+                if (line.contains(chart.getChartName())) {
+                    repository = line.split("/")[0];
+                    logger.info("Helm chart located in the repository {} ", repository);
+                    return repository;
+                }
+                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;
+    }
+
+    /**
+     * Uninstall a chart.
+     *
+     * @param chart name and version.
+     * @throws ServiceException incase of error
+     */
+    public void uninstallChart(ChartInfo chart) throws ServiceException {
+        executeCommand(prepareUnInstallCommand(chart));
+    }
+
+    static String executeCommand(ProcessBuilder processBuilder) throws ServiceException {
+        var commandStr = toString(processBuilder);
+
+        processBuilder.redirectInput(ProcessBuilder.Redirect.DISCARD);
+
+        try {
+            var process = processBuilder.start();
+            process.waitFor();
+            int exitValue = process.exitValue();
+
+            if (exitValue != 0) {
+                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);
+        } catch (Exception exc) {
+            throw new ServiceException("Failed to execute the Command: " + commandStr, exc);
+        }
+    }
+
+    private ProcessBuilder prepareInstallCommand(ChartInfo chart) {
+
+        // @formatter:off
+        List<String> helmArguments = new ArrayList<>(
+                Arrays.asList(
+                        "helm",
+                        "install", chart.getReleaseName(), chart.getRepository() + "/" + chart.getChartName(),
+                        "--version", chart.getVersion(),
+                        "--namespace", chart.getNamespace()
+                )
+        );
+        // @formatter:on
+
+        // Verify if values.yaml available for the chart
+        var overrideFile = chartStore.getOverrideFile(chart).getPath();
+        if (verifyLocalHelmRepo(overrideFile)) {
+            logger.info("Override yaml file available for the helm chart");
+            helmArguments.addAll(Arrays.asList("--values", overrideFile));
+        }
+
+        return new ProcessBuilder().command(helmArguments);
+    }
+
+    private ProcessBuilder prepareUnInstallCommand(ChartInfo chart) {
+        return new ProcessBuilder("helm", "delete", chart.getReleaseName(), "--namespace", chart.getNamespace());
+    }
+
+    private ProcessBuilder prepareCreateNamespaceCommand(String namespace) {
+        return new ProcessBuilder().command("kubectl", "create", "namespace", namespace);
+    }
+
+    private ProcessBuilder helmRepoVerifyCommand(String chartName) {
+        return new ProcessBuilder().command("bash", "-c", "helm search repo | grep " + chartName);
+    }
+
+    private ProcessBuilder localRepoVerifyCommand(String localFile) {
+        return new ProcessBuilder().command("bash", "-c", "ls " + localFile);
+    }
+
+    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));
+        logger.debug("Helm repositories updated successfully");
+    }
+
+    private boolean verifyLocalHelmRepo(String localFile) {
+        var isVerified = false;
+        var processBuilder = localRepoVerifyCommand(localFile);
+        try {
+            executeCommand(processBuilder);
+            isVerified = true;
+        } catch (ServiceException e) {
+            logger.error("Unable to verify file in local repository", e);
+        }
+        return isVerified;
+    }
+
+    protected static String toString(ProcessBuilder processBuilder) {
+        return String.join(" ", processBuilder.command());
+    }
+}
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartInfo.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartInfo.java
new file mode 100644 (file)
index 0000000..6bfb7ae
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.models;
+
+import lombok.Data;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.immutables.gson.Gson;
+
+@Data
+@RequiredArgsConstructor
+@Gson.TypeAdapters
+public class ChartInfo {
+
+    @NonNull
+    private String releaseName;
+
+    @NonNull
+    private String chartName;
+
+    @NonNull
+    private String version;
+
+    @NonNull
+    private String namespace;
+
+    private String repository;
+
+}
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
new file mode 100644 (file)
index 0000000..c86bff5
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.models;
+
+import java.util.Collection;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+public class ChartList {
+    private Collection<ChartInfo> charts;
+}
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/InstallationInfo.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/InstallationInfo.java
new file mode 100644 (file)
index 0000000..b21e93a
--- /dev/null
@@ -0,0 +1,29 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.models;
+
+import lombok.Getter;
+import org.immutables.gson.Gson;
+
+@Getter
+@Gson.TypeAdapters
+public class InstallationInfo {
+    private String name;
+    private String version;
+}
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/parameters/ParticipantK8sParameterHandler.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/parameters/ParticipantK8sParameterHandler.java
new file mode 100644 (file)
index 0000000..1a7dc35
--- /dev/null
@@ -0,0 +1,73 @@
+/*-
+ * ============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.io.File;
+import javax.ws.rs.core.Response;
+import org.onap.policy.clamp.controlloop.common.exception.ControlLoopException;
+import org.onap.policy.common.parameters.BeanValidationResult;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+
+/**
+ * This class handles reading, parsing and validating of control loop participant parameters from JSON files.
+ */
+public class ParticipantK8sParameterHandler {
+    private static final Coder CODER = new StandardCoder();
+
+    /**
+     * Read the parameters from the path of the file.
+     *
+     * @param path path of the config file.
+     * @return the parameters read from the configuration file
+     * @throws ControlLoopException on parameter exceptions
+     */
+    public ParticipantK8sParameters toParticipantK8sParameters(String path) throws ControlLoopException {
+        ParticipantK8sParameters parameters = null;
+        // Read the parameters
+        try {
+            // Read the parameters from JSON
+            var file = new File(path);
+            parameters = CODER.decode(file, ParticipantK8sParameters.class);
+        } catch (final CoderException e) {
+            final String errorMessage =
+                    "error reading parameters from \"" + path + "\"\n" + "(" + e.getClass().getSimpleName() + ")";
+            throw new ControlLoopException(Response.Status.NOT_ACCEPTABLE, errorMessage, e);
+        }
+
+        // The JSON processing returns null if there is an empty file
+        if (parameters == null) {
+            final String errorMessage = "no parameters found in \"" + path + "\"";
+            throw new ControlLoopException(Response.Status.NOT_ACCEPTABLE, errorMessage);
+        }
+
+        // validate the parameters
+        final BeanValidationResult validationResult = parameters.validate();
+        if (!validationResult.isValid()) {
+            String returnMessage =
+                    "validation error(s) on parameters from \"" + path + "\"\n" + validationResult.getResult();
+            throw new ControlLoopException(Response.Status.NOT_ACCEPTABLE, returnMessage);
+        }
+
+        return parameters;
+    }
+}
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
new file mode 100644 (file)
index 0000000..65b3243
--- /dev/null
@@ -0,0 +1,59 @@
+/*-
+ * ============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 javax.validation.constraints.NotBlank;
+import lombok.Getter;
+import org.onap.policy.clamp.controlloop.participant.intermediary.parameters.ParticipantIntermediaryParameters;
+import org.onap.policy.common.parameters.ParameterGroupImpl;
+import org.onap.policy.common.parameters.annotations.NotNull;
+import org.onap.policy.common.parameters.annotations.Valid;
+import org.onap.policy.models.provider.PolicyModelsProviderParameters;
+
+/**
+ * Class to hold all parameters needed for the kubernetes participant.
+ *
+ */
+@NotNull
+@NotBlank
+@Getter
+public class ParticipantK8sParameters extends ParameterGroupImpl {
+    public static final String DEFAULT_LOCAL_CHART_DIR = "/var/helm-manager/local-charts";
+    public static final String DEFAULT_INFO_FILE_NAME = "CHART_INFO.json";
+
+    @Valid
+    private ParticipantIntermediaryParameters intermediaryParameters;
+    @Valid
+    private PolicyModelsProviderParameters databaseProviderParameters;
+
+
+    private String localChartDirectory = DEFAULT_LOCAL_CHART_DIR;
+    private String infoFileName = DEFAULT_INFO_FILE_NAME;
+
+    /**
+     * Create the kubernetes participant parameter group.
+     *
+     * @param name the parameter group name
+     */
+    public ParticipantK8sParameters(final String name) {
+        super(name);
+    }
+}
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
new file mode 100644 (file)
index 0000000..6accac3
--- /dev/null
@@ -0,0 +1,122 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.service;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+@Service
+public class ChartService {
+    private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    @Autowired
+    private ChartStore chartStore;
+
+    @Autowired
+    private HelmClient helmClient;
+
+    /**
+     * Get all the installed charts.
+     * @return list of charts.
+     */
+    public Collection<ChartInfo> getAllCharts() {
+        return chartStore.getAllCharts();
+    }
+
+    /**
+     * Get specific chart info.
+     * @param name name of the app
+     * @param version version of the app
+     * @return chart
+     * @throws ServiceException incase of error.
+     */
+    public ChartInfo getChart(String name, String version) throws ServiceException {
+        return chartStore.getChart(name, version);
+    }
+
+    /**
+     * Save a helm chart.
+     * @param chartInfo name and version of the app.
+     * @param chartFile Helm chart file
+     * @return chart details of the helm chart
+     * @throws IOException incase of IO error
+     * @throws ServiceException incase of error
+     */
+    public ChartInfo saveChart(ChartInfo chartInfo, MultipartFile chartFile, MultipartFile overrideFile)
+        throws IOException, ServiceException {
+        return chartStore.saveChart(chartInfo, chartFile, overrideFile);
+    }
+
+    /**
+     * Delete a helm chart.
+     * @param chart name and version of the chart.
+     */
+    public void deleteChart(ChartInfo chart) {
+        chartStore.deleteChart(chart);
+    }
+
+    /**
+     * Install a helm chart.
+     * @param chart name and version.
+     * @throws ServiceException incase of error
+     */
+    public void installChart(ChartInfo chart) throws ServiceException, IOException {
+        if (chart.getRepository() == null) {
+            String repository = findChartRepo(chart);
+            if (repository == null) {
+                logger.error("Chart repository could not be found. Skipping chart Installation "
+                        + "for the chart {} ", chart.getChartName());
+                return;
+            } else {
+                chart.setRepository(repository);
+            }
+        }
+        helmClient.installChart(chart);
+    }
+
+    /**
+     * Finds helm chart repository for a given chart.
+     * @param chart chartInfo.
+     * @throws ServiceException incase of error
+     */
+    public String findChartRepo(ChartInfo chart) throws ServiceException, IOException {
+        logger.info("Fetching helm chart repository for the given chart {} ", chart.getChartName());
+        return helmClient.findChartRepository(chart);
+    }
+
+    /**
+     * Uninstall a helm chart.
+     * @param chart name and version
+     * @throws ServiceException incase of error.
+     */
+    public void uninstallChart(ChartInfo chart) throws ServiceException {
+        logger.info("Uninstalling helm deployment {}", chart.getReleaseName());
+        helmClient.uninstallChart(chart);
+    }
+
+}
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
new file mode 100644 (file)
index 0000000..2d0ce7a
--- /dev/null
@@ -0,0 +1,215 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.service;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+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;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.FileSystemUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+@Component
+public class ChartStore {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    private static final StandardCoder STANDARD_CODER = new StandardCoder();
+
+    @Autowired
+    private ParticipantK8sParameters participantK8sParameters;
+
+    /**
+     * The chartStore map contains chart name as key & ChartInfo as value.
+     */
+    private Map<String, ChartInfo> localChartMap = new ConcurrentHashMap<>();
+
+    /**
+     * Constructor method.
+     */
+    public ChartStore() {
+        this.restoreFromLocalFileSystem();
+    }
+
+    /**
+     * Get local helm chart file.
+     *
+     * @param chart ChartInfo
+     * @return the chart file.
+     */
+    public File getHelmChartFile(ChartInfo chart) {
+        var appPath = getAppPath(chart.getChartName(), chart.getVersion());
+        return new File(appPath.toFile(), chart.getChartName());
+    }
+
+    /**
+     * Get the override yaml file.
+     *
+     * @param chart ChartInfo
+     * @return the override yaml file
+     */
+    public File getOverrideFile(ChartInfo chart) {
+        var appPath = getAppPath(chart.getChartName(), chart.getVersion());
+        return new File(appPath.toFile(), "values.yaml");
+    }
+
+
+    /**
+     * Saves the helm chart.
+     *
+     * @param chartInfo chartInfo
+     * @param chartFile helm chart file.
+     * @return chart
+     * @throws IOException incase of IO error
+     * @throws ServiceException incase of error.
+     */
+    public synchronized ChartInfo saveChart(ChartInfo chartInfo, MultipartFile chartFile, MultipartFile overrideFile)
+            throws IOException, ServiceException {
+        if (localChartMap.containsKey(key(chartInfo.getChartName(), chartInfo.getVersion()))) {
+            throw new ServiceException("Chart already exist");
+        }
+        var appPath = getAppPath(chartInfo.getChartName(), chartInfo.getVersion());
+        Files.createDirectories(appPath);
+
+        chartFile.transferTo(getHelmChartFile(chartInfo));
+        if (overrideFile != null) {
+            overrideFile.transferTo(getOverrideFile(chartInfo));
+        }
+
+        localChartMap.put(key(chartInfo), chartInfo);
+        storeChartInFile(chartInfo);
+        return chartInfo;
+    }
+
+    /**
+     * Get the chart info.
+     *
+     * @param name name of the chart
+     * @param version version of the chart
+     * @return chart
+     */
+    public synchronized ChartInfo getChart(String name, String version) {
+        return localChartMap.get(key(name, version));
+    }
+
+    /**
+     * Get all the charts installed.
+     *
+     * @return list of charts.
+     */
+    public synchronized List<ChartInfo> getAllCharts() {
+        return new ArrayList<>(localChartMap.values());
+    }
+
+    /**
+     * Delete a chart.
+     *
+     * @param chart chart info
+     */
+    public synchronized void deleteChart(ChartInfo chart) {
+        var appPath = getAppPath(chart.getChartName(), chart.getVersion());
+        try {
+            FileSystemUtils.deleteRecursively(appPath);
+        } catch (IOException exc) {
+            LOGGER.warn("Could not delete chart from local file system : {}", appPath, exc);
+        }
+
+        localChartMap.remove(key(chart));
+    }
+
+    /**
+     * Fetch the local chart directory of specific chart.
+     *
+     * @param chartName name of the chart
+     * @param chartVersion version of the chart
+     * @return path
+     */
+    public Path getAppPath(String chartName, String chartVersion) {
+        return Path.of(participantK8sParameters.getLocalChartDirectory(), chartName, chartVersion);
+    }
+
+    private void storeChartInFile(ChartInfo chart) {
+        try (var out = new PrintStream(new FileOutputStream(getFile(chart)))) {
+            out.print(STANDARD_CODER.encode(chart));
+        } catch (Exception exc) {
+            LOGGER.warn("Could not store chart: {} {}", chart.getChartName(), exc);
+        }
+    }
+
+    private File getFile(ChartInfo chart) {
+        var appPath = getAppPath(chart.getChartName(), chart.getVersion()).toString();
+        return Path.of(appPath, participantK8sParameters.getInfoFileName()).toFile();
+    }
+
+    private synchronized void restoreFromLocalFileSystem() {
+        Path localChartDirectoryPath = Paths.get(participantK8sParameters.getLocalChartDirectory());
+
+        try {
+            Files.createDirectories(localChartDirectoryPath);
+            restoreFromLocalFileSystem(localChartDirectoryPath);
+        } catch (IOException ioe) {
+            LOGGER.warn("Could not restore charts from local file system: {}", ioe);
+        }
+    }
+
+    private synchronized void restoreFromLocalFileSystem(Path localChartDirectoryPath)
+            throws IOException {
+
+        Files.walkFileTree(localChartDirectoryPath, new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult visitFile(Path localChartFile, BasicFileAttributes attrs) throws IOException {
+                try {
+                    ChartInfo chart = STANDARD_CODER.decode(localChartFile.toFile(), ChartInfo.class);
+                    localChartMap.put(key(chart), chart);
+                    return FileVisitResult.CONTINUE;
+                } catch (CoderException ce) {
+                    throw new IOException("Error decoding chart file", ce);
+                }
+            }
+        });
+    }
+
+    private String key(ChartInfo chart) {
+        return key(chart.getChartName(), chart.getVersion());
+    }
+
+    private String key(String chartName, String chartVersion) {
+        return chartName + "_" + chartVersion;
+    }
+
+}
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/resources/config/KubernetesParticipantConfig.json b/participant/participant-impl/participant-impl-kubernetes/src/main/resources/config/KubernetesParticipantConfig.json
new file mode 100644 (file)
index 0000000..620e055
--- /dev/null
@@ -0,0 +1,56 @@
+{
+    "name": "ControlLoopParticipantK8s",
+    "localChartDirectory": "/var/helm-manager/local-charts",
+    "infoFileName": "CHART_INFO.json",
+
+    "intermediaryParameters":{
+        "name":"Participant parameters",
+        "reportingTimeInterval":120000,
+        "description":"Participant Description",
+        "participantId":{
+            "name":"K8sParticipant0",
+            "version":"1.0.0"
+        },
+        "participantType":{
+            "name":"org.onap.k8s.controlloop.K8SControlLoopParticipant",
+            "version":"2.3.4"
+        },
+        "clampControlLoopTopics":{
+            "topicSources":[
+                {
+                    "topic":"POLICY-CLRUNTIME-PARTICIPANT",
+                    "servers":[
+                        "localhost"
+                    ],
+                    "topicCommInfrastructure":"dmaap",
+                    "fetchTimeout":15000
+                }
+            ],
+            "topicSinks":[
+                {
+                    "topic":"POLICY-CLRUNTIME-PARTICIPANT",
+                    "servers":[
+                        "localhost"
+                    ],
+                    "topicCommInfrastructure":"dmaap"
+                },
+                {
+                    "topic":"POLICY-NOTIFICATION",
+                    "servers":[
+                        "localhost"
+                    ],
+                    "topicCommInfrastructure":"dmaap"
+                }
+            ]
+        }
+    },
+    "databaseProviderParameters":{
+        "name":"PolicyProviderParameterGroup",
+        "implementation":"org.onap.policy.models.provider.impl.DatabasePolicyModelsProviderImpl",
+        "databaseDriver":"org.mariadb.jdbc.Driver",
+        "databaseUrl":"jdbc:mariadb://localhost:3306/controlloop",
+        "databaseUser":"admin",
+        "databasePassword":"passme",
+        "persistenceUnit":"ToscaConceptTest"
+    }
+}
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/resources/config/application.yaml b/participant/participant-impl/participant-impl-kubernetes/src/main/resources/config/application.yaml
new file mode 100644 (file)
index 0000000..b424003
--- /dev/null
@@ -0,0 +1,25 @@
+spring:
+  profiles:
+    active: prod
+
+participant:
+  file: src/main/resources/config/KubernetesParticipantConfig.json
+management:
+  endpoints:
+    web:
+      exposure:
+        include: "loggers,logfile,health,info,metrics,threaddump,heapdump"
+server:
+  # Configuration of the HTTP/REST server. The parameters are defined and handled by the springboot framework.
+  # See springboot documentation.
+  http-port : 8083
+
+logging:
+  # Configuration of logging
+  level:
+    ROOT: INFO
+    org.springframework: ERROR
+    org.springframework.data: ERROR
+    org.springframework.web.reactive.function.client.ExchangeFunctions: ERROR
+  file:
+    name: /var/log/helm-manager/application.log
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/resources/KubernetesHelm.yaml b/participant/participant-impl/participant-impl-kubernetes/src/test/resources/KubernetesHelm.yaml
new file mode 100644 (file)
index 0000000..3212b5a
--- /dev/null
@@ -0,0 +1,154 @@
+# ============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=========================================================
+tosca_definitions_version: tosca_simple_yaml_1_3
+data_types:
+  onap.datatypes.ToscaConceptIdentifier:
+    derived_from: tosca.datatypes.Root
+    properties:
+      name:
+        type: string
+        required: true
+      version:
+        type: string
+        required: true
+node_types:
+  org.onap.policy.clamp.controlloop.Participant:
+    version: 1.0.1
+    derived_from: tosca.nodetypes.Root
+    properties:
+      provider:
+        type: string
+        requred: false
+  org.onap.policy.clamp.controlloop.ControlLoopElement:
+    version: 1.0.1
+    derived_from: tosca.nodetypes.Root
+    properties:
+      provider:
+        type: string
+        requred: false
+      participant_id:
+        type: onap.datatypes.ToscaConceptIdentifier
+        requred: true
+  org.onap.policy.clamp.controlloop.ControlLoop:
+    version: 1.0.1
+    derived_from: tosca.nodetypes.Root
+    properties:
+      provider:
+        type: string
+        requred: false
+      elements:
+        type: list
+        required: true
+        entry_schema:
+          type: onap.datatypes.ToscaConceptIdentifier
+  org.onap.policy.clamp.controlloop.K8SMicroserviceControlLoopElement:
+    version: 1.0.1
+    derived_from: org.onap.policy.clamp.controlloop.ControlLoopElement
+    properties:
+      chart:
+        type: string
+        required: true
+      configs:
+        type: list
+        required: false
+      requirements:
+        type: string
+        requred: false
+      templates:
+        type: list
+        required: false
+        entry_schema:
+      values:
+        type: string
+        requred: true
+topology_template:
+  node_templates:
+    org.onap.k8s.controlloop.K8SControlLoopParticipant:
+      version: 2.3.4
+      type: org.onap.policy.clamp.controlloop.Participant
+      type_version: 1.0.1
+      description: Participant for K8S
+      properties:
+        provider: ONAP
+
+    org.onap.domain.database.HelloWorld_K8SMicroserviceControlLoopElement:
+      # Chart from any chart repository configured on helm client.
+      version: 1.2.3
+      type: org.onap.policy.clamp.controlloop.K8SMicroserviceControlLoopElement
+      type_version: 1.0.0
+      description: Control loop element for the K8S microservice for Hello World
+      properties:
+        provider: ONAP
+        participant_id:
+          name: org.onap.k8s.controlloop.K8SControlLoopParticipant
+          version: 2.3.4
+        chart:
+          release_name: helloworld
+          chart_name: hello
+          version: 0.1.0
+          repository: chartMuseum
+          namespace: onap
+
+    org.onap.domain.database.PMSH_K8SMicroserviceControlLoopElement:
+      # Chart from local file system
+      version: 1.2.3
+      type: org.onap.policy.clamp.controlloop.K8SMicroserviceControlLoopElement
+      type_version: 1.0.0
+      description: Control loop element for the K8S microservice for PMSH
+      properties:
+        provider: ONAP
+        participant_id:
+          name: org.onap.k8s.controlloop.K8SControlLoopParticipant
+          version: 2.3.4
+        chart:
+          release_name: pmshmicroservice
+          chart_name: test
+          version: 1.0.1
+          namespace: onap
+
+    org.onap.domain.database.Local_K8SMicroserviceControlLoopElement:
+      # Chart installation without passing repository name
+      version: 1.2.3
+      type: org.onap.policy.clamp.controlloop.K8SMicroserviceControlLoopElement
+      type_version: 1.0.0
+      description: Control loop element for the K8S microservice for local chart
+      properties:
+        provider: ONAP
+        participant_id:
+          name: org.onap.k8s.controlloop.K8SControlLoopParticipant
+          version: 2.3.4
+        chart:
+          release_name: nginxms
+          chart_name: nginx-ingress
+          version: 0.9.1
+          namespace: onap
+
+    org.onap.domain.sample.GenericK8s_ControlLoopDefinition:
+      version: 1.2.3
+      type: org.onap.policy.clamp.controlloop.ControlLoop
+      type_version: 1.0.0
+      description: Control loop for Hello World
+      properties:
+        provider: ONAP
+        elements:
+          - name: org.onap.domain.database.HelloWorld_K8SMicroserviceControlLoopElement
+            version: 1.2.3
+          - name: org.onap.domain.database.PMSH_K8SMicroserviceControlLoopElement
+            version: 1.2.3
+          - name: org.onap.domain.database.Local_K8SMicroserviceControlLoopElement
+            version: 1.2.3
index f027757..f83ffd0 100644 (file)
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>exec-maven-plugin</artifactId>
-                <version>1.3.2</version>
                 <executions>
                     <execution>
                         <id>libIndexCheck</id>