Merge "Python based rapp manager"
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 ca1994b..04f0f05 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
@@ -56,13 +56,12 @@
     @Autowired
     private AppService appService;
 
-    @Autowired
     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")})
-    public ResponseEntity<AppList> getAllApps() throws ServiceException {
+    public ResponseEntity<AppList> getAllApps() {
         Collection<AppInfo> apps = new ArrayList<>();
         appService.getAllApps().forEach(app -> apps.add(toAppInfo(app)));
         AppList list = AppList.builder().apps(apps).build();
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 05a8e00..bfdb439 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
@@ -40,13 +40,17 @@
     }
 
     public void installApp(App app) throws ServiceException {
-        ProcessBuilder builder = prepareInstallCommand(app);
+        ProcessBuilder builder = prepareCreateNamespaceCommand(app.getNamespace());
+        executeCommand(builder);
+        builder = prepareInstallCommand(app);
         executeCommand(builder);
     }
 
     public void uninstallApp(App app) throws ServiceException {
         ProcessBuilder builder = prepareUnInstallCommand(app);
         executeCommand(builder);
+        builder = prepareDeleteNamespaceCommand(app.getNamespace());
+        executeCommand(builder);
     }
 
     protected String getOverrideFileName(App app) {
@@ -66,14 +70,15 @@
             Process process = builder.start();
             process.waitFor();
             int exitValue = process.exitValue();
+            String commandStr = toString(builder);
 
             if (exitValue != 0) {
                 String error = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
-                logger.error("Executing helm command {} failed, exitValue: {} {}", toString(builder), exitValue, error);
-                throw new ServiceException("Command execution failed: " + builder + " " + error);
+                logger.error("Executing helm command <{}> failed, exitValue: {} {}", commandStr, exitValue, error);
+                throw new ServiceException("Command execution failed: " + commandStr + " " + error);
             }
             String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
-            logger.debug("Command execution, output:{}", output);
+            logger.debug("Command <{}> execution, output:{}", commandStr, output);
             return output;
         } catch (Exception e) {
             throw new ServiceException("Failed to execute the Command", e);
@@ -84,8 +89,8 @@
 
         List<String> helmArguments = Arrays.asList(//
             "helm", //
-            "install", getChartFileName(app), //
-            "--name", releaseName(app), //
+            "install", app.getName(), getChartFileName(app), //
+            "--version", app.getVersion(), //
             "--namespace", app.getNamespace(), //
             "--values", getOverrideFileName(app));
 
@@ -95,7 +100,19 @@
     }
 
     public ProcessBuilder prepareUnInstallCommand(App app) {
-        return new ProcessBuilder("helm", "delete", releaseName(app), "--purge");
+        return new ProcessBuilder("helm", "delete", app.getName(), "--namespace", app.getNamespace());
+    }
+
+    // Assuming to have a separate namespace for each App,
+    // modifications needed if multiple Apps could share the same namespace
+    public ProcessBuilder prepareCreateNamespaceCommand(String namespace) {
+        return new ProcessBuilder(Arrays.asList(//
+            "kubectl", "create", "namespace", namespace));
+    }
+
+    public ProcessBuilder prepareDeleteNamespaceCommand(String namespace) {
+        return new ProcessBuilder(Arrays.asList(//
+            "kubectl", "delete", "namespace", namespace, "--wait=false"));
     }
 
     private void addKubeApiInfo(List<String> helmArguments) {
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 11b4529..939ac43 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
@@ -20,10 +20,8 @@
 
 import lombok.Builder;
 import lombok.Getter;
-import lombok.Setter;
 
 @Getter
-@Setter
 @Builder
 public class App {
 
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 39b503f..4c49dce 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,11 +18,17 @@
 
 package org.oransc.rappmanager.service;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
 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.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -42,13 +48,19 @@
 public class AppStore {
 
     private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+    private static Gson gson = new GsonBuilder().create();
+
     /**
      * The appStore map contains app name as key & App as value
      */
     private Map<String, App> appMap = new HashMap<>();
 
-    @Autowired
-    ApplicationConfig appConfig;
+    private final ApplicationConfig appConfig;
+
+    public AppStore(@Autowired ApplicationConfig appConfig) {
+        this.appConfig = appConfig;
+        this.restoreFromDatabase();
+    }
 
     public File getOverrideFile(App app) {
         Path appPath = getAppPath(app.getName(), app.getVersion());
@@ -78,6 +90,7 @@
         overrideFile.transferTo(getOverrideFile(app));
 
         appMap.put(key(app), app);
+        storeAppInFile(app);
         return app;
     }
 
@@ -104,9 +117,45 @@
         appMap.remove(key(app));
     }
 
+    private void storeAppInFile(App app) {
+        try (PrintStream out = new PrintStream(new FileOutputStream(getFile(app)))) {
+            out.print(gson.toJson(app));
+        } catch (Exception e) {
+            logger.warn("Could not store app: {} {}", app.getName(), e.getMessage());
+        }
+    }
+
+    private File getFile(App app) {
+        String appPath = getAppPath(app.getName(), app.getVersion()).toString();
+        return Path.of(appPath, APP_FILE_NAME).toFile();
+    }
+
+    private static final String APP_FILE_NAME = "App.json";
+
+    public synchronized void restoreFromDatabase() {
+        try {
+            Files.createDirectories(Paths.get(getDatabaseDirectory()));
+            restoreFromDatabase(new File(getDatabaseDirectory()));
+        } catch (IOException e) {
+            logger.warn("Could not restore apps from database: {}", e.getMessage());
+        }
+    }
+
+    public synchronized void restoreFromDatabase(File file) throws IOException {
+        if (file.isDirectory()) {
+            for (File dirElement : file.listFiles()) {
+                restoreFromDatabase(dirElement);
+            }
+        } else if (file.getName().equals(APP_FILE_NAME)) {
+            String json = Files.readString(file.toPath());
+            App app = gson.fromJson(json, App.class);
+            appMap.put(key(app), app);
+        }
+    }
+
     public synchronized void reset() {
         try {
-            FileSystemUtils.deleteRecursively(Path.of(this.appConfig.getVardataDirectory()));
+            FileSystemUtils.deleteRecursively(Path.of(getDatabaseDirectory()));
         } catch (IOException e) {
             logger.warn("Could not delete database : {}", e.getMessage());
         }
diff --git a/rapp-manager/src/test/java/org/oransc/rappmanager/controller/KubernetesTest.java b/rapp-manager/src/test/java/org/oransc/rappmanager/controller/KubernetesTest.java
index 547c4d3..d47461a 100644
--- a/rapp-manager/src/test/java/org/oransc/rappmanager/controller/KubernetesTest.java
+++ b/rapp-manager/src/test/java/org/oransc/rappmanager/controller/KubernetesTest.java
@@ -11,9 +11,8 @@
 import java.util.Arrays;
 import java.util.List;
 
-import org.junit.Test;
 import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.runner.RunWith;
 import org.oransc.rappmanager.BeanFactory;
@@ -29,7 +28,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
 import org.springframework.boot.test.context.TestConfiguration;
@@ -48,7 +46,6 @@
 import org.springframework.web.reactive.function.BodyInserters;
 import org.springframework.web.reactive.function.BodyInserters.MultipartInserter;
 
-@AutoConfigureMockMvc
 @RunWith(SpringRunner.class)
 @ExtendWith(SpringExtension.class)
 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@@ -87,8 +84,20 @@
 
         @Override
         public ProcessBuilder prepareUnInstallCommand(App app) {
-            String unistall = toString(super.prepareUnInstallCommand(app));
-            return executeInVagrant(unistall);
+            String uninstallCommand = toString(super.prepareUnInstallCommand(app));
+            return executeInVagrant(uninstallCommand);
+        }
+
+        @Override
+        public ProcessBuilder prepareCreateNamespaceCommand(String namespace) {
+            String createCommand = toString(super.prepareCreateNamespaceCommand(namespace));
+            return executeInVagrant(createCommand);
+        }
+
+        @Override
+        public ProcessBuilder prepareDeleteNamespaceCommand(String namespace) {
+            String deleteCommand = toString(super.prepareDeleteNamespaceCommand(namespace));
+            return executeInVagrant(deleteCommand);
         }
 
         private ProcessBuilder executeInVagrant(String command) {
@@ -152,7 +161,6 @@
         }
     }
 
-    @BeforeEach
     @AfterEach
     void reset() {
         appStore.reset();
@@ -172,6 +180,15 @@
         deleteApp();
     }
 
+    @Test
+    public void testStoreApp() throws ServiceException {
+        appStore.reset();
+        onboardApp();
+        AppStore tmpAppStore = new AppStore(this.applicationConfig);
+        App app = tmpAppStore.getApp(APP_NAME, VERSION);
+        assertThat(app.getNamespace()).isEqualTo(NAMESPACE);
+    }
+
     private void onboardApp() {
         String url = "/rms/apps";
         AppInfo appInfo = AppInfo.builder().name(APP_NAME).version(VERSION).namespace(NAMESPACE).build();