Structural changes

Added values.yaml
Added namespace

Change-Id: I5ec9af0833ce5c51d735eacb6ce81775b0e578f3
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppController.java b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppController.java
index 56809fe..3bdc4a4 100644
--- a/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppController.java
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppController.java
@@ -18,6 +18,9 @@
 
 package org.oransc.rappmanager.controller;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
@@ -25,15 +28,10 @@
 
 import java.io.IOException;
 
-import javax.validation.Valid;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-
 import org.oransc.rappmanager.exception.ServiceException;
 import org.oransc.rappmanager.service.App;
 import org.oransc.rappmanager.service.AppList;
 import org.oransc.rappmanager.service.AppService;
-import org.oransc.rappmanager.service.ChartService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
@@ -49,74 +47,70 @@
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
 @RestController("rAppController")
 @RequestMapping("rms")
-@Api(tags = { "rapp" })
+@Api(tags = {"rapp"})
 public class AppController {
 
     @Autowired
     private AppService appService;
 
     @Autowired
-    private ChartService chartService;
     private static Gson gson = new GsonBuilder().create();
 
     @GetMapping(path = "/apps", produces = MediaType.APPLICATION_JSON_VALUE)
     @ApiOperation(value = "Return all Apps")
-    @ApiResponses(value = { @ApiResponse(code = 200, message = "rApp List") })
+    @ApiResponses(value = {@ApiResponse(code = 200, message = "rApp List")})
     public ResponseEntity<AppList> getAllApps() throws ServiceException {
         return new ResponseEntity<>(appService.getAllApps(), HttpStatus.OK);
     }
 
-    @GetMapping(path = "/apps/{name}", produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Return a App")
-    @ApiResponses(value = { @ApiResponse(code = 200, message = "rApp") })
-    public ResponseEntity<App> getApp(@PathVariable("name") String name) throws ServiceException {
-        return new ResponseEntity<>(appService.getApp(name), HttpStatus.OK);
-    }
-
-    @PostMapping(path = "/install", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+    @PostMapping(
+        path = "/install",
+        consumes = MediaType.APPLICATION_JSON_VALUE,
+        produces = MediaType.APPLICATION_JSON_VALUE)
     @ApiOperation(value = "Install the rApp")
-    @ApiResponses(value = { @ApiResponse(code = 201, message = "rApp Installed") })
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "rApp Installed")})
     public ResponseEntity<Object> installApp(@RequestBody InstallationInfo info) throws ServiceException {
-        App app = App.builder().name(info.getName()).namespace(info.getNamespace()).version(info.getVersion()).build();
+        App app = appService.getApp(info.getName(), info.getVersion());
         appService.installApp(app);
         return new ResponseEntity<>(HttpStatus.CREATED);
     }
 
     @DeleteMapping(path = "/install/{name}/{version}", produces = MediaType.APPLICATION_JSON_VALUE)
     @ApiOperation(value = "Uninstall the App")
-    @ApiResponses(value = { @ApiResponse(code = 201, message = "rApp Uninstalled") })
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "rApp Uninstalled")})
     public ResponseEntity<Object> deleteApp( //
-            @PathVariable("name") String name, //
-            @PathVariable("version") String version) throws ServiceException {
-        App app = appService.getApp(name);
+        @PathVariable("name") String name, //
+        @PathVariable("version") String version) throws ServiceException {
+        App app = appService.getApp(name, version);
         appService.uninstallApp(app);
-        return new ResponseEntity<>(HttpStatus.OK);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
 
-    @PostMapping(path = "/apps", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+    @PostMapping(
+        path = "/apps",
+        consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+        produces = MediaType.APPLICATION_JSON_VALUE)
     @ApiOperation(value = "Onboard the Chart")
-    @ApiResponses(value = { @ApiResponse(code = 201, message = "Chart Onboarded") })
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Chart Onboarded")})
     public ResponseEntity<String> onboardChart( //
-            @RequestPart("chart") MultipartFile chartFile, //
-            @RequestPart("values") MultipartFile overrideFile, //
-            @RequestParam("info") String infoJson) throws ServiceException, IOException {
+        @RequestPart("chart") MultipartFile chartFile, //
+        @RequestPart("values") MultipartFile overrideFile, //
+        @RequestParam("info") String infoJson) throws ServiceException, IOException {
 
         AppInfo info = gson.fromJson(infoJson, AppInfo.class);
         appService.saveApp(info, chartFile, overrideFile);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @DeleteMapping(path = "/charts/{name}/{version}")
-    @ApiOperation(value = "Delete the Chart")
-    @ApiResponses(value = { @ApiResponse(code = 204, message = "Chart Deleted") })
+    @DeleteMapping(path = "/apps/{name}/{version}")
+    @ApiOperation(value = "Delete the app")
+    @ApiResponses(value = {@ApiResponse(code = 204, message = "Chart Deleted")})
     public ResponseEntity<Object> deleteChart(@PathVariable("name") String name,
-            @PathVariable("version") String version) throws ServiceException {
-        chartService.deleteChart(name, version);
+        @PathVariable("version") String version) throws ServiceException {
+        App app = appService.getApp(name, version);
+        appService.deleteApp(app);
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
 
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppInfo.java b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppInfo.java
new file mode 100644
index 0000000..c097899
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppInfo.java
@@ -0,0 +1,48 @@
+/*-
+ * ========================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.oransc.rappmanager.controller;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Getter;
+
+import org.immutables.gson.Gson;
+
+@Schema(name = "application-information", description = "Application information")
+@Getter
+@Builder
+@JsonInclude(Include.NON_NULL)
+@Gson.TypeAdapters
+public class AppInfo {
+
+    @Schema(name = "name", description = "name", required = true)
+    @JsonProperty(value = "name", required = true)
+    @SerializedName("name")
+    private final String name;
+
+    private final String version;
+
+    private final String namespace; // kube
+
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/controller/InstallationInfo.java b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/InstallationInfo.java
new file mode 100644
index 0000000..e8013f6
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/InstallationInfo.java
@@ -0,0 +1,36 @@
+/*-
+ * ========================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.oransc.rappmanager.controller;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Getter;
+
+import org.immutables.gson.Gson;
+
+@Getter
+@Builder
+@Schema(name = "installation_information", description = "Information for one installation")
+@Gson.TypeAdapters
+public class InstallationInfo {
+
+    private final String name;
+    private final String version;
+
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmClient.java b/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmClient.java
index 51c046b..025122b 100644
--- a/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmClient.java
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmClient.java
@@ -49,6 +49,13 @@
         executeCommand(builder);
     }
 
+    protected String getOverrideFileName(App app) {
+        Path appPath = getAppPath(app);
+        File file = new File(appPath.toFile(), "values.yaml");
+        return file.getAbsolutePath();
+
+    }
+
     protected String getChartFileName(App app) {
         return getChartFile(app).getAbsolutePath();
     }
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/App.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/App.java
index c827b39..11b4529 100644
--- a/rapp-manager/src/main/java/org/oransc/rappmanager/service/App.java
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/service/App.java
@@ -27,16 +27,9 @@
 @Builder
 public class App {
 
-    private String name = "";
-    private String version = "";
+    private String name;
+    private String version;
 
-    private String namespace = ""; // kube
+    private String namespace; // kube
 
-    /**
-     * private Status status; // how to determine the status of the app. Which
-     * kubernetes object will tell me //that the app is running? It can have pod or
-     * deployment or jobs, statefulset. //Easy way is to get meta-data of the app
-     * 
-     * public enum Status { RUNNING, ERROR, }
-     **/
 }
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppService.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppService.java
index b2c6ada..2f16455 100644
--- a/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppService.java
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppService.java
@@ -18,21 +18,22 @@
 
 package org.oransc.rappmanager.service;
 
-import java.io.File;
 import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.lang.invoke.MethodHandles;
 
 import org.oransc.rappmanager.configuration.ApplicationConfig;
 import org.oransc.rappmanager.controller.AppInfo;
 import org.oransc.rappmanager.exception.ServiceException;
 import org.oransc.rappmanager.helm.HelmClient;
+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 AppService {
+    private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     @Autowired
     private AppStore appStore;
@@ -49,16 +50,17 @@
         return AppList.builder().apps(appStore.getAllApp()).build();
     }
 
-    public App getApp(String name) throws ServiceException {
-        return appStore.getApp(name);
+    public App getApp(String name, String version) throws ServiceException {
+        return appStore.getApp(name, version);
     }
 
-    public void saveApp(AppInfo appInfo, MultipartFile chartFile, MultipartFile overrideFile) throws IOException {
-        Path appPath = getAppPath(appInfo);
-        Files.createDirectories(appPath);
+    public App saveApp(AppInfo appInfo, MultipartFile chartFile, MultipartFile overrideFile)
+        throws IOException, ServiceException {
+        return appStore.saveApp(appInfo, chartFile, overrideFile);
+    }
 
-        chartFile.transferTo(new File(appPath.toFile(), "chart.tgz"));
-        overrideFile.transferTo(new File(appPath.toFile(), "values.yaml"));
+    public void deleteApp(App app) {
+        appStore.deleteApp(app);
     }
 
     public void installApp(App app) throws ServiceException {
@@ -66,22 +68,11 @@
         // Call the kubernetes api to create namespace
         // Validate the override file
         helmClient.installApp(app);
-        appStore.addApp(app);
-
     }
 
     public void uninstallApp(App app) throws ServiceException {
         // call the kubernetes api to delete objects (helm call)
         helmClient.uninstallApp(app);
-        appStore.deleteApp(app.getName());
-    }
-
-    private Path getAppPath(AppInfo chart) {
-        return Path.of(getDatabaseDirectory(), chart.getName(), chart.getVersion());
-    }
-
-    private String getDatabaseDirectory() {
-        return appConfig.getVardataDirectory() + "/database";
     }
 
 }
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppStore.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppStore.java
index c74440a..6422b30 100644
--- a/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppStore.java
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppStore.java
@@ -18,33 +18,61 @@
 
 package org.oransc.rappmanager.service;
 
+import java.io.File;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.oransc.rappmanager.configuration.ApplicationConfig;
+import org.oransc.rappmanager.controller.AppInfo;
 import org.oransc.rappmanager.exception.ServiceException;
+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 AppStore {
 
+    private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
     /**
      * The appStore map contains app name as key & App as value
      */
-    private Map<String, App> appStore = new HashMap<>();
+    private Map<String, App> appMap = new HashMap<>();
 
-    public synchronized void addApp(App app) throws ServiceException {
+    @Autowired
+    ApplicationConfig appConfig;
 
-        if (!appStore.containsKey(app.getName())) {
-            appStore.put(app.getName(), app);
-        } else {
+    public synchronized App saveApp(AppInfo appInfo, MultipartFile chartFile, MultipartFile overrideFile)
+        throws IOException, ServiceException {
+        if (appMap.containsKey(key(appInfo.getName(), appInfo.getVersion()))) {
             throw new ServiceException("App Already Exist");
         }
+        Path appPath = getAppPath(appInfo.getName(), appInfo.getVersion());
+        Files.createDirectories(appPath);
+
+        chartFile.transferTo(new File(appPath.toFile(), "chart.tgz"));
+        overrideFile.transferTo(new File(appPath.toFile(), "values.yaml"));
+
+        App app = App.builder() //
+            .name(appInfo.getName()) //
+            .version(appInfo.getVersion()) //
+            .namespace(appInfo.getNamespace()) //
+            .build();
+
+        appMap.put(key(app), app);
+        return app;
     }
 
-    public synchronized App getApp(String name) throws ServiceException {
-        App app = appStore.get(name);
+    public synchronized App getApp(String name, String version) throws ServiceException {
+        App app = appMap.get(key(name, version));
         if (app == null) {
             throw new ServiceException("App not found: " + name);
         }
@@ -52,10 +80,43 @@
     }
 
     public synchronized List<App> getAllApp() {
-        return new ArrayList<App>(appStore.values());
+        return new ArrayList<>(appMap.values());
     }
 
-    public synchronized void deleteApp(String name) {
-        appStore.remove(name);
+    public synchronized void deleteApp(App app) {
+        try {
+            Path appPath = getAppPath(app.getName(), app.getVersion());
+            FileSystemUtils.deleteRecursively(appPath);
+        } catch (IOException e) {
+            logger.warn("Could not delete app database : {}", e.getMessage());
+        }
+
+        appMap.remove(key(app));
     }
+
+    public synchronized void reset() {
+        try {
+            FileSystemUtils.deleteRecursively(Path.of(this.appConfig.getVardataDirectory()));
+        } catch (IOException e) {
+            logger.warn("Could not delete database : {}", e.getMessage());
+        }
+        appMap.clear();
+    }
+
+    private Path getAppPath(String appName, String appVersion) {
+        return Path.of(getDatabaseDirectory(), appName, appVersion);
+    }
+
+    private String getDatabaseDirectory() {
+        return appConfig.getVardataDirectory() + "/database";
+    }
+
+    private String key(App app) {
+        return key(app.getName(), app.getVersion());
+    }
+
+    private String key(String appName, String appVersion) {
+        return appName + "_" + appVersion;
+    }
+
 }
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/ChartService.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/ChartService.java
deleted file mode 100644
index 3730acf..0000000
--- a/rapp-manager/src/main/java/org/oransc/rappmanager/service/ChartService.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*-
- * ========================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.oransc.rappmanager.service;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.reflect.TypeToken;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Map;
-
-import org.oransc.rappmanager.client.AsyncRestClient;
-import org.oransc.rappmanager.client.AsyncRestClientFactory;
-import org.oransc.rappmanager.configuration.ApplicationConfig;
-import org.oransc.rappmanager.controller.AppInfo;
-import org.oransc.rappmanager.exception.ServiceException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
-
-// Need to support multiple Chart registry. So add an interface and use it based on the registry choice.
-@Service
-public class ChartService {
-
-    private static final Logger logger = LoggerFactory.getLogger(ChartService.class);
-
-    private final AsyncRestClient chartClient;
-    private final ApplicationConfig appConfig;
-
-    private static Gson gson = new GsonBuilder().serializeNulls().create();
-
-    private static String basePath = "/api/charts";
-
-    public ChartService(@Autowired ApplicationConfig appConfig) {
-        AsyncRestClientFactory factory = new AsyncRestClientFactory(appConfig.getWebClientConfig());
-        chartClient = factory.createRestClientNoHttpProxy(appConfig.getChartApi());
-        this.appConfig = appConfig;
-    }
-
-    public Map<String, AppInfo[]> getCharts() throws ServiceException {
-        ResponseEntity<String> rsp = chartClient.getForEntity(basePath).block();
-        if (rsp.getStatusCodeValue() != 200) {
-            throw new ServiceException("Error Getting Charts");
-        }
-        logger.debug("Sucessfully fetched the charts");
-        return parseChart(rsp.getBody());
-    }
-
-    public AppInfo[] getChart(String name) throws ServiceException {
-        ResponseEntity<String> rsp = chartClient.getForEntity(basePath + "/" + name).block();
-        if (!rsp.getStatusCode().is2xxSuccessful()) {
-            throw new ServiceException("Error Getting Chart " + name);
-        }
-        logger.debug("Sucessfully fetched the chart");
-        return gson.fromJson(rsp.getBody(), AppInfo[].class);
-    }
-
-    public void saveChart(AppInfo chart, MultipartFile chartFile, MultipartFile overrideFile) throws IOException {
-        Path appPath = getAppPath(chart);
-        Files.createDirectories(appPath);
-
-        chartFile.transferTo(new File(appPath.toFile(), "chart.tgz"));
-        overrideFile.transferTo(new File(appPath.toFile(), "values.yaml"));
-    }
-
-    private Path getAppPath(AppInfo chart) {
-        return Path.of(getDatabaseDirectory(), chart.getName(), chart.getVersion());
-    }
-
-    private String getDatabaseDirectory() {
-        return appConfig.getVardataDirectory() + "/database";
-    }
-
-    public void deleteChart(String name, String version) throws ServiceException {
-        ResponseEntity<String> rsp = chartClient.deleteForEntity(basePath + "/" + name + "/" + version).block();
-        if (!rsp.getStatusCode().is2xxSuccessful()) {
-            throw new ServiceException("Error in deleting Chart " + name);
-        }
-        logger.debug("Sucessfully deleted the chart");
-    }
-
-    private Map<String, AppInfo[]> parseChart(String json) {
-        Type mapType = new TypeToken<Map<String, AppInfo[]>>() {
-        }.getType();
-        return gson.fromJson(json, mapType);
-    }
-
-}
diff --git a/rapp-manager/src/test/java/org/oransc/rappmanager/controller/ChartControllerTest.java b/rapp-manager/src/test/java/org/oransc/rappmanager/controller/Microk8sTest.java
similarity index 64%
rename from rapp-manager/src/test/java/org/oransc/rappmanager/controller/ChartControllerTest.java
rename to rapp-manager/src/test/java/org/oransc/rappmanager/controller/Microk8sTest.java
index 46c28fc..e5a8c29 100644
--- a/rapp-manager/src/test/java/org/oransc/rappmanager/controller/ChartControllerTest.java
+++ b/rapp-manager/src/test/java/org/oransc/rappmanager/controller/Microk8sTest.java
@@ -7,13 +7,9 @@
 import com.google.gson.GsonBuilder;
 
 import java.io.File;
-import java.io.IOException;
 import java.lang.invoke.MethodHandles;
-import java.nio.file.Path;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.junit.Test;
 import org.junit.jupiter.api.AfterEach;
@@ -26,8 +22,10 @@
 import org.oransc.rappmanager.configuration.ApplicationConfig;
 import org.oransc.rappmanager.configuration.ImmutableWebClientConfig;
 import org.oransc.rappmanager.configuration.WebClientConfig;
+import org.oransc.rappmanager.exception.ServiceException;
 import org.oransc.rappmanager.helm.HelmClient;
 import org.oransc.rappmanager.service.App;
+import org.oransc.rappmanager.service.AppStore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,8 +45,6 @@
 import org.springframework.test.context.TestPropertySource;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.util.FileSystemUtils;
 import org.springframework.web.reactive.function.BodyInserters;
 import org.springframework.web.reactive.function.BodyInserters.MultipartInserter;
 
@@ -56,25 +52,27 @@
 @RunWith(SpringRunner.class)
 @ExtendWith(SpringExtension.class)
 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
-@TestPropertySource(properties = { //
+@TestPropertySource(
+    properties = { //
         "server.ssl.key-store=./config/keystore.jks", //
         "app.webclient.trust-store=./config/truststore.jks", //
         "app.vardata-directory=./target" //
-})
-public class ChartControllerTest {
+    })
+public class Microk8sTest {
     private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
     private static Gson gson = new GsonBuilder().serializeNulls().create();
+    private final static String APP_NAME = "test-application";
+    private final static String VERSION = "1.0.0";
+    private final static String NAMESPACE = "test-application-namespace";
 
     @LocalServerPort
     private int port;
 
     @Autowired
-    private MockMvc mvc;
-
-    @Autowired
     ApplicationConfig applicationConfig;
 
-    private Map<String, AppInfo[]> charts = new HashMap<>();
+    @Autowired
+    AppStore appStore;
 
     static class Microk8sHelmClient extends HelmClient {
         public Microk8sHelmClient(@Autowired ApplicationConfig appConfig) {
@@ -84,8 +82,10 @@
         @Override
         public ProcessBuilder prepareInstallCommand(App app) {
             List<String> helmArguments = Arrays.asList("microk8s.helm", //
-                    "install", getChartFileName(app), //
-                    "--name", releaseName(app));
+                "install", getChartFileName(app), //
+                "--name", releaseName(app), //
+                "--namespace", app.getNamespace(), //
+                "--values", getOverrideFileName(app));
 
             ProcessBuilder processBuilder = new ProcessBuilder();
             return processBuilder.command(helmArguments);
@@ -118,45 +118,52 @@
     @BeforeEach
     @AfterEach
     void reset() {
-        try {
-            FileSystemUtils.deleteRecursively(Path.of(this.applicationConfig.getVardataDirectory()));
-        } catch (IOException e) {
-            logger.warn("Could not delete database : {}", e.getMessage());
-        }
+        appStore.reset();
     }
 
     @Test
-    public void testPostApp_InstallApp_DeleteApp() {
+    public void testPostApp_InstallApp_DeleteApp() throws ServiceException {
+        onboardApp();
+        App app = appStore.getApp(APP_NAME, VERSION);
+        assertThat(app.getNamespace()).isEqualTo(NAMESPACE);
 
-        AsyncRestClient client = restClient();
-        {
-            // onboard APP
-            String url = "/rms/apps";
-            AppInfo appInfo = AppInfo.builder().name("test-application").version("1.0.0").build();
+        installApp();
 
-            File chartFile = new File(getClass().getClassLoader().getResource("testchart-0.1.0.tgz").getFile());
-            File overrideFile = new File(getClass().getClassLoader().getResource("testValues.yaml").getFile());
-            MultipartInserter body = createPostAppBody(chartFile, appInfo, overrideFile);
-            ResponseEntity<String> e = client.postForEntity(url, body, MediaType.MULTIPART_FORM_DATA).block();
+        uninstallApp();
 
-            assertThat(e.getStatusCode()).isEqualTo(HttpStatus.OK);
-        }
-        {
-            // Install App
-            InstallationInfo app = InstallationInfo.builder().name("test-application").version("1.0.0")
-                    .namespace("test-application-namespace").build();
-            String body = gson.toJson(app);
-            String url = "/rms/install";
-            ResponseEntity<String> e = client.postForEntity(url, body).block();
-            assertThat(e.getStatusCode()).isEqualTo(HttpStatus.CREATED);
-        }
-        {
-            // Uninstall App
-            String url = "/rms/install/test-application/1.0.0";
-            ResponseEntity<String> e = client.deleteForEntity(url).block();
-            assertThat(e.getStatusCode()).isEqualTo(HttpStatus.OK);
-        }
+        deleteApp();
+    }
 
+    private void onboardApp() {
+        String url = "/rms/apps";
+        AppInfo appInfo = AppInfo.builder().name(APP_NAME).version(VERSION).namespace(NAMESPACE).build();
+
+        File chartFile = new File(getClass().getClassLoader().getResource("testchart-0.1.0.tgz").getFile());
+        File overrideFile = new File(getClass().getClassLoader().getResource("testValues.yaml").getFile());
+        MultipartInserter body = createPostAppBody(chartFile, appInfo, overrideFile);
+        ResponseEntity<String> e = restClient().postForEntity(url, body, MediaType.MULTIPART_FORM_DATA).block();
+
+        assertThat(e.getStatusCode()).isEqualTo(HttpStatus.OK);
+    }
+
+    private void installApp() {
+        InstallationInfo app = InstallationInfo.builder().name(APP_NAME).version(VERSION).build();
+        String body = gson.toJson(app);
+        String url = "/rms/install";
+        ResponseEntity<String> e = restClient().postForEntity(url, body).block();
+        assertThat(e.getStatusCode()).isEqualTo(HttpStatus.CREATED);
+    }
+
+    private void uninstallApp() {
+        String url = "/rms/install/" + APP_NAME + "/" + VERSION;
+        ResponseEntity<String> e = restClient().deleteForEntity(url).block();
+        assertThat(e.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
+    }
+
+    private void deleteApp() {
+        String url = "/rms/apps/" + APP_NAME + "/" + VERSION;
+        ResponseEntity<String> e = restClient().deleteForEntity(url).block();
+        assertThat(e.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
     }
 
     private MultipartInserter createPostAppBody(File chartFile, AppInfo appInfo, File overrideFile) {
@@ -170,15 +177,15 @@
     private AsyncRestClient restClient(String baseUrl, boolean useTrustValidation) {
         WebClientConfig config = this.applicationConfig.getWebClientConfig();
         config = ImmutableWebClientConfig.builder() //
-                .keyStoreType(config.keyStoreType()) //
-                .keyStorePassword(config.keyStorePassword()) //
-                .keyStore(config.keyStore()) //
-                .keyPassword(config.keyPassword()) //
-                .isTrustStoreUsed(useTrustValidation) //
-                .trustStore(config.trustStore()) //
-                .trustStorePassword(config.trustStorePassword()) //
-                .httpProxyConfig(config.httpProxyConfig()) //
-                .build();
+            .keyStoreType(config.keyStoreType()) //
+            .keyStorePassword(config.keyStorePassword()) //
+            .keyStore(config.keyStore()) //
+            .keyPassword(config.keyPassword()) //
+            .isTrustStoreUsed(useTrustValidation) //
+            .trustStore(config.trustStore()) //
+            .trustStorePassword(config.trustStorePassword()) //
+            .httpProxyConfig(config.httpProxyConfig()) //
+            .build();
 
         AsyncRestClientFactory f = new AsyncRestClientFactory(config);
         return f.createRestClientNoHttpProxy(baseUrl);