From 3fd07361dae0abab8254f39911039fa261a3c02b Mon Sep 17 00:00:00 2001 From: Jason Vigil Date: Mon, 19 Jul 2021 14:08:48 -0700 Subject: [PATCH] [#8950][Platform] Use matching helm chart version to operate on db k8s pods Summary: Modify platform release metadata to associate a helm chart with each release version. Perform kubernetes universe operations (create, edit, upgrade, etc.), using the helm chart associated with the universe version. If there is no associated helm chart (and the universe version < 2.7), use special "legacy" helm chart to manage the universe. Test Plan: Launch previous version platform. Create k8s provider and universe. Stop the platform. Add two new releases to releases dir and add their helm charts to helm.packagePath. Start new version of platform and verify that the helm charts in the helm.packagePpath are now copied into the releases directory (alongside the release binary archives) and the platform stores the helm chart paths in the release metadata. Observe the releases page shows the helm chart paths for each release. Perform following operations and ensure the correct version of the helm chart is used for each step: 1. Increase # of pods (verify legacy 2.7 helm chart is used) 2. Upgrade universe (to newer version that has associated chart) 3. Modify gflags 4. Upgrade universe version (to other newer version) 5. Decrease # of pods 6. Modify gflags 7. Delete universe Run unit tests. Run K8s integration tests. Reviewers: arnav, sanketh Reviewed By: sanketh Subscribers: jason, yugaware, jenkins-bot Differential Revision: https://phabricator.dev.yugabyte.com/D12255 --- managed/devops/replicated.yml | 2 +- .../tasks/EditKubernetesUniverse.java | 4 +- .../tasks/KubernetesTaskBase.java | 21 ++- .../tasks/UpgradeKubernetesUniverse.java | 9 +- .../subtasks/KubernetesCommandExecutor.java | 7 +- .../yugabyte/yw/common/KubernetesManager.java | 70 ++++++++-- .../yugabyte/yw/common/ReleaseManager.java | 92 +++++++++---- .../handlers/UniverseCRUDHandler.java | 21 +++ managed/src/main/resources/application.conf | 4 +- managed/src/main/resources/swagger.json | 34 ++++- .../tasks/CreateKubernetesUniverseTest.java | 11 +- .../tasks/EditKubernetesUniverseTest.java | 15 +- .../tasks/UpgradeKubernetesUniverseTest.java | 21 ++- .../KubernetesCommandExecutorTest.java | 47 +++++++ .../yw/common/KubernetesManagerTest.java | 85 ++++++++++-- .../yw/common/ReleaseManagerTest.java | 130 ++++++++++++------ .../UniverseControllerTestBase.java | 4 + .../UniverseCreateControllerTestBase.java | 29 +++- managed/src/test/resources/dev.expected.conf | 2 +- managed/src/test/resources/helm.expected.conf | 2 +- .../test/resources/replicated.expected.conf | 2 +- .../src/test/resources/test.helm.params.conf | 2 +- .../resources/test.replicated.params.conf | 2 +- .../releases/ReleaseList/ReleaseList.js | 30 ++-- 24 files changed, 509 insertions(+), 137 deletions(-) diff --git a/managed/devops/replicated.yml b/managed/devops/replicated.yml index a3b1be1a4805..9ddfbca20e2e 100644 --- a/managed/devops/replicated.yml +++ b/managed/devops/replicated.yml @@ -294,7 +294,7 @@ components: releases.path = "/opt/yugabyte/releases" docker.release = "/opt/yugabyte/release" thirdparty.packagePath = /opt/third-party - helm.package = "/opt/yugabyte/helm/yugabyte-latest.tgz" + helm.packagePath = "/opt/yugabyte/helm" health.check_interval_ms = 300000 health.status_interval_ms = 43200000 health.default_email = "YB_ALERTS_EMAIL_REPLACE" diff --git a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/EditKubernetesUniverse.java b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/EditKubernetesUniverse.java index 4ae271151a36..7c549e47d831 100644 --- a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/EditKubernetesUniverse.java +++ b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/EditKubernetesUniverse.java @@ -286,12 +286,14 @@ public void updateRemainingPods( PlacementInfoUtil.computeMasterAddresses( newPI, newPlacement.masters, taskParams().nodePrefix, provider, masterRpcPort); + String ybSoftwareVersion = taskParams().getPrimaryCluster().userIntent.ybSoftwareVersion; + upgradePodsTask( newPlacement, masterAddresses, currPlacement, serverType, - null, + ybSoftwareVersion, DEFAULT_WAIT_TIME_MS, masterChanged, tserverChanged); diff --git a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/KubernetesTaskBase.java b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/KubernetesTaskBase.java index 57c95e9f41b8..d086d035e826 100644 --- a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/KubernetesTaskBase.java +++ b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/KubernetesTaskBase.java @@ -64,6 +64,8 @@ public void createPodsTask( ServerType serverType, PlacementInfo activeZones) { + String ybSoftwareVersion = taskParams().getPrimaryCluster().userIntent.ybSoftwareVersion; + boolean edit = currPlacement != null; boolean isMultiAz = masterAddresses != null; @@ -151,7 +153,7 @@ public void createPodsTask( tempPI, azCode, masterAddresses, - null, + ybSoftwareVersion, serverType, config, masterPartition, @@ -189,6 +191,7 @@ public void createPodsTask( tempPI, azCode, masterAddresses, + ybSoftwareVersion, config)); // Add zone to active configs. @@ -324,6 +327,8 @@ public void deletePodsTask( KubernetesPlacement newPlacement, boolean userIntentChange) { + String ybSoftwareVersion = taskParams().getPrimaryCluster().userIntent.ybSoftwareVersion; + boolean edit = newPlacement != null; boolean isMultiAz = masterAddresses != null; @@ -370,7 +375,12 @@ public void deletePodsTask( newPlacement.masters.getOrDefault(azUUID, 0); helmDeletes.addTask( createKubernetesExecutorTask( - CommandType.HELM_UPGRADE, tempPI, azCode, masterAddresses, config)); + CommandType.HELM_UPGRADE, + tempPI, + azCode, + masterAddresses, + ybSoftwareVersion, + config)); podsWait.addTask( createKubernetesCheckPodNumTask( KubernetesCheckNumPod.CommandType.WAIT_FOR_PODS, @@ -472,22 +482,23 @@ public Set getPodsToRemove( // Create Kubernetes Executor task for creating the namespaces and pull secrets. public KubernetesCommandExecutor createKubernetesExecutorTask( KubernetesCommandExecutor.CommandType commandType, String az, Map config) { - return createKubernetesExecutorTask(commandType, null, az, null, config); + return createKubernetesExecutorTask(commandType, null, az, null, null, config); } // Create the Kubernetes Executor task for the helm deployments. (USED) public KubernetesCommandExecutor createKubernetesExecutorTask( - KubernetesCommandExecutor.CommandType commandType, + CommandType commandType, PlacementInfo pi, String az, String masterAddresses, + String ybSoftwareVersion, Map config) { return createKubernetesExecutorTaskForServerType( commandType, pi, az, masterAddresses, - null, + ybSoftwareVersion, ServerType.EITHER, config, 0 /* master partition */, diff --git a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/UpgradeKubernetesUniverse.java b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/UpgradeKubernetesUniverse.java index 575e0f4dcb69..608011e978de 100644 --- a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/UpgradeKubernetesUniverse.java +++ b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/UpgradeKubernetesUniverse.java @@ -124,14 +124,15 @@ private SubTaskGroupType getTaskSubGroupType() { } private void createUpgradeTask(UserIntent userIntent, Universe universe, PlacementInfo pi) { - String version = null; + String ybSoftwareVersion = null; boolean masterChanged = false; boolean tserverChanged = false; if (taskParams().taskType == UpgradeTaskType.Software) { - version = taskParams().ybSoftwareVersion; + ybSoftwareVersion = taskParams().ybSoftwareVersion; masterChanged = true; tserverChanged = true; } else { + ybSoftwareVersion = userIntent.ybSoftwareVersion; if (!taskParams().masterGFlags.equals(userIntent.masterGFlags)) { masterChanged = true; } @@ -164,7 +165,7 @@ private void createUpgradeTask(UserIntent userIntent, Universe universe, Placeme masterAddresses, null, ServerType.MASTER, - version, + ybSoftwareVersion, taskParams().sleepAfterMasterRestartMillis, masterChanged, tserverChanged); @@ -179,7 +180,7 @@ private void createUpgradeTask(UserIntent userIntent, Universe universe, Placeme masterAddresses, null, ServerType.TSERVER, - version, + ybSoftwareVersion, taskParams().sleepAfterTServerRestartMillis, false /* master change is false since it has already been upgraded.*/, tserverChanged); diff --git a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/subtasks/KubernetesCommandExecutor.java b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/subtasks/KubernetesCommandExecutor.java index 4d66cdb66dbc..4cfcd45d3ac7 100644 --- a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/subtasks/KubernetesCommandExecutor.java +++ b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/subtasks/KubernetesCommandExecutor.java @@ -174,6 +174,7 @@ public void run() { overridesFile = this.generateHelmOverride(); response = kubernetesManager.helmInstall( + taskParams().ybSoftwareVersion, config, taskParams().providerUUID, taskParams().nodePrefix, @@ -185,7 +186,11 @@ public void run() { overridesFile = this.generateHelmOverride(); response = kubernetesManager.helmUpgrade( - config, taskParams().nodePrefix, taskParams().namespace, overridesFile); + taskParams().ybSoftwareVersion, + config, + taskParams().nodePrefix, + taskParams().namespace, + overridesFile); flag = true; break; case UPDATE_NUM_NODES: diff --git a/managed/src/main/java/com/yugabyte/yw/common/KubernetesManager.java b/managed/src/main/java/com/yugabyte/yw/common/KubernetesManager.java index eb46f46323c3..b32a1f5c1743 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/KubernetesManager.java +++ b/managed/src/main/java/com/yugabyte/yw/common/KubernetesManager.java @@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; -import com.yugabyte.yw.models.Provider; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -16,17 +16,22 @@ @Singleton public class KubernetesManager { - public static final Logger LOG = LoggerFactory.getLogger(KubernetesManager.class); - private static final long DEFAULT_TIMEOUT_SECS = 300; + @Inject ReleaseManager releaseManager; @Inject ShellProcessHandler shellProcessHandler; @Inject play.Configuration appConfig; - private static String SERVICE_INFO_JSONPATH = + public static final Logger LOG = LoggerFactory.getLogger(KubernetesManager.class); + + private static final long DEFAULT_TIMEOUT_SECS = 300; + + private static final String SERVICE_INFO_JSONPATH = "{.spec.clusterIP}|" + "{.status.*.ingress[0].ip}|{.status.*.ingress[0].hostname}"; + private static final String LEGACY_HELM_CHART_FILENAME = "yugabyte-2.7-helm-legacy.tar.gz"; + public ShellResponse createNamespace(Map config, String universePrefix) { List commandList = ImmutableList.of("kubectl", "create", "namespace", universePrefix); return execCommand(config, commandList); @@ -54,17 +59,15 @@ public String getTimeout() { } public ShellResponse helmInstall( + String ybSoftwareVersion, Map config, UUID providerUUID, String universePrefix, String namespace, String overridesFile) { - String helmPackagePath = appConfig.getString("yb.helm.package"); - if (helmPackagePath == null || helmPackagePath.isEmpty()) { - throw new RuntimeException("Helm Package path not provided."); - } - Provider provider = Provider.get(providerUUID); - Map configProvider = provider.getConfig(); + + String helmPackagePath = this.getHelmPackagePath(ybSoftwareVersion); + List commandList = ImmutableList.of( "helm", @@ -180,11 +183,14 @@ public ShellResponse runGetSecret( } public ShellResponse helmUpgrade( - Map config, String universePrefix, String namespace, String overridesFile) { - String helmPackagePath = appConfig.getString("yb.helm.package"); - if (helmPackagePath == null || helmPackagePath.isEmpty()) { - throw new RuntimeException("Helm Package path not provided."); - } + String ybSoftwareVersion, + Map config, + String universePrefix, + String namespace, + String overridesFile) { + + String helmPackagePath = this.getHelmPackagePath(ybSoftwareVersion); + List commandList = ImmutableList.of( "helm", @@ -257,4 +263,38 @@ private ShellResponse execCommand(Map config, List comma String description = String.join(" ", command); return shellProcessHandler.run(command, config, description); } + + public String getHelmPackagePath(String ybSoftwareVersion) { + String helmPackagePath = null; + + // Get helm package filename from release metadata. + ReleaseManager.ReleaseMetadata releaseMetadata = + releaseManager.getReleaseByVersion(ybSoftwareVersion); + if (releaseMetadata != null) { + helmPackagePath = releaseMetadata.chartPath; + } + + if (helmPackagePath == null || helmPackagePath.isEmpty()) { + // TODO: The "legacy" helm chart is included in the yugaware container build to ensure that + // universes deployed using previous versions of the platform (that did not use versioned + // helm charts) will still be usable after upgrading to newer versions of the platform (that + // use versioned helm charts). We can (and should) remove this special case once all customers + // that use the k8s provider have upgraded their platforms and universes to versions > 2.7. + if (Util.compareYbVersions(ybSoftwareVersion, "2.8.0.0") < 0) { + helmPackagePath = + new File(appConfig.getString("yb.helm.packagePath"), LEGACY_HELM_CHART_FILENAME) + .toString(); + } else { + throw new RuntimeException("Helm Package path not found for release: " + ybSoftwareVersion); + } + } + + // Ensure helm package file actually exists. + File helmPackage = new File(helmPackagePath); + if (!helmPackage.exists()) { + throw new RuntimeException("Helm Package file not found: " + helmPackagePath); + } + + return helmPackagePath; + } } diff --git a/managed/src/main/java/com/yugabyte/yw/common/ReleaseManager.java b/managed/src/main/java/com/yugabyte/yw/common/ReleaseManager.java index edf36d8e6ce8..b43d5c2f2c83 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/ReleaseManager.java +++ b/managed/src/main/java/com/yugabyte/yw/common/ReleaseManager.java @@ -55,6 +55,10 @@ public static class ReleaseMetadata { @ApiModelProperty(value = "Release file path") public String filePath; + // File path where the release helm chart is stored + @ApiModelProperty(value = "Helm chart path") + public String chartPath; + // Docker image tag corresponding to the release @ApiModelProperty(value = "Release image tag") public String imageTag; @@ -64,6 +68,7 @@ public static ReleaseMetadata fromLegacy(String version, Object metadata) { // convert those to new format. ReleaseMetadata rm = create(version); rm.filePath = (String) metadata; + rm.chartPath = ""; return rm; } @@ -75,44 +80,76 @@ public static ReleaseMetadata create(String version) { return rm; } + public ReleaseMetadata withFilePath(String filePath) { + this.filePath = filePath; + return this; + } + + public ReleaseMetadata withChartPath(String chartPath) { + this.chartPath = chartPath; + return this; + } + public String toString() { return getClass().getName() + " state: " + String.valueOf(state) + " filePath: " + String.valueOf(filePath) + + " chartPath: " + + String.valueOf(chartPath) + " imageTag: " + String.valueOf(imageTag); } } public static final Logger LOG = LoggerFactory.getLogger(ReleaseManager.class); + final PathMatcher ybPackageMatcher = - FileSystems.getDefault().getPathMatcher("glob:**yugabyte*.tar.gz"); - Predicate packagesFilter = p -> Files.isRegularFile(p) && ybPackageMatcher.matches(p); + FileSystems.getDefault().getPathMatcher("glob:**yugabyte*centos*.tar.gz"); + final Predicate ybPackageFilter = + p -> Files.isRegularFile(p) && ybPackageMatcher.matches(p); + + final PathMatcher ybChartMatcher = + FileSystems.getDefault().getPathMatcher("glob:**yugabyte-*-helm.tar.gz"); + final Predicate ybChartFilter = p -> Files.isRegularFile(p) && ybChartMatcher.matches(p); // This regex needs to support old style packages with -ee as well as new style packages without. // There are previously existing YW deployments that will have the old packages and users will // need to still be able to use said universes and their existing YB releases. final Pattern ybPackagePattern = Pattern.compile("[^.]+yugabyte-(?:ee-)?(.*)-centos(.*).tar.gz"); - public Map getLocalReleases(String localReleasePath) { - Map releaseMap = new HashMap<>(); + final Pattern ybHelmChartPattern = Pattern.compile("[^.]+yugabyte-(.*)-helm.tar.gz"); + public Map getReleaseFiles(String releasesPath, Predicate fileFilter) { + Map fileMap = new HashMap<>(); try { - // Return a map of version# and software package - releaseMap = - Files.walk(Paths.get(localReleasePath)) - .filter(packagesFilter) + fileMap = + Files.walk(Paths.get(releasesPath)) + .filter(fileFilter) .collect( Collectors.toMap( p -> p.getName(p.getNameCount() - 2).toString(), p -> p.toAbsolutePath().toString())); - LOG.debug("Local releases: [ {} ]", releaseMap.toString()); } catch (IOException e) { LOG.error(e.getMessage()); } - return releaseMap; + return fileMap; + } + + public Map getLocalReleases(String releasesPath) { + Map localReleases = new HashMap<>(); + Map releaseFiles = getReleaseFiles(releasesPath, ybPackageFilter); + Map releaseCharts = getReleaseFiles(releasesPath, ybChartFilter); + releaseFiles.forEach( + (version, filePath) -> { + ReleaseMetadata r = + ReleaseMetadata.create(version) + .withFilePath(filePath) + .withChartPath(releaseCharts.getOrDefault(version, "")); + localReleases.put(version, r); + }); + return localReleases; } public Map getReleaseMetadata() { @@ -148,18 +185,19 @@ public void addReleaseWithMetadata(String version, ReleaseMetadata metadata) { public void importLocalReleases() { String ybReleasesPath = appConfig.getString("yb.releases.path"); + String ybReleasePath = appConfig.getString("yb.docker.release"); + String ybHelmChartPath = appConfig.getString("yb.helm.packagePath"); if (ybReleasesPath != null && !ybReleasesPath.isEmpty()) { - moveDockerReleaseFile(ybReleasesPath); - Map localReleases = getLocalReleases(ybReleasesPath); + moveFiles(ybReleasePath, ybReleasesPath, ybPackagePattern); + moveFiles(ybHelmChartPath, ybReleasesPath, ybHelmChartPattern); + Map localReleases = getLocalReleases(ybReleasesPath); Map currentReleases = getReleaseMetadata(); localReleases.keySet().removeAll(currentReleases.keySet()); LOG.debug("Current releases: [ {} ]", currentReleases.keySet().toString()); LOG.debug("Local releases: [ {} ]", localReleases.keySet()); if (!localReleases.isEmpty()) { - LOG.info("Importing local releases: [ {} ]", localReleases.keySet().toString()); - localReleases.forEach( - (version, releaseFile) -> - currentReleases.put(version, ReleaseMetadata.fromLegacy(version, releaseFile))); + LOG.info("Importing local releases: [ {} ]", localReleases.toString()); + localReleases.forEach(currentReleases::put); configHelper.loadConfigToDB(ConfigHelper.ConfigType.SoftwareReleases, currentReleases); } } @@ -174,26 +212,26 @@ public void updateReleaseMetadata(String version, ReleaseMetadata newData) { } /** - * This method would move the yugabyte server package that we bundle with docker image to yb - * releases path. + * This method moves files that match a specific regex to a destination directory. * - * @param ybReleasesPath (str): Yugabyte releases path to create the move the files to. + * @param sourceDir (str): Source directory to move files from + * @param destinationDir (str): Destination directory to move files to + * @param fileRegex (str): Regular expression specifying files to move */ - private void moveDockerReleaseFile(String ybReleasesPath) { - String ybDockerRelease = appConfig.getString("yb.docker.release"); - if (ybDockerRelease == null || ybDockerRelease.isEmpty()) { + private static void moveFiles(String sourceDir, String destinationDir, Pattern fileRegex) { + if (sourceDir == null || sourceDir.isEmpty()) { return; } try { - Files.walk(Paths.get(ybDockerRelease)) + Files.walk(Paths.get(sourceDir)) .map(String::valueOf) - .map(ybPackagePattern::matcher) + .map(fileRegex::matcher) .filter(Matcher::matches) .forEach( match -> { File releaseFile = new File(match.group()); - File destinationFolder = new File(ybReleasesPath, match.group(1)); + File destinationFolder = new File(destinationDir, match.group(1)); File destinationFile = new File(destinationFolder, releaseFile.getName()); if (!destinationFolder.exists()) { destinationFolder.mkdir(); @@ -202,7 +240,7 @@ private void moveDockerReleaseFile(String ybReleasesPath) { Files.move(releaseFile.toPath(), destinationFile.toPath(), REPLACE_EXISTING); } catch (IOException e) { throw new RuntimeException( - "Unable to move docker release file " + "Unable to move release file " + releaseFile.toPath() + " to " + destinationFile); @@ -210,7 +248,7 @@ private void moveDockerReleaseFile(String ybReleasesPath) { }); } catch (IOException e) { LOG.error(e.getMessage()); - throw new RuntimeException("Unable to look up release files in " + ybDockerRelease); + throw new RuntimeException("Unable to look up release files in " + sourceDir); } } diff --git a/managed/src/main/java/com/yugabyte/yw/controllers/handlers/UniverseCRUDHandler.java b/managed/src/main/java/com/yugabyte/yw/controllers/handlers/UniverseCRUDHandler.java index a9408f95ddbf..2c88e6012c3a 100644 --- a/managed/src/main/java/com/yugabyte/yw/controllers/handlers/UniverseCRUDHandler.java +++ b/managed/src/main/java/com/yugabyte/yw/controllers/handlers/UniverseCRUDHandler.java @@ -21,6 +21,7 @@ import com.yugabyte.yw.commissioner.tasks.DestroyUniverse; import com.yugabyte.yw.commissioner.tasks.ReadOnlyClusterDelete; import com.yugabyte.yw.common.CertificateHelper; +import com.yugabyte.yw.common.KubernetesManager; import com.yugabyte.yw.common.PlacementInfoUtil; import com.yugabyte.yw.common.Util; import com.yugabyte.yw.common.YWServiceException; @@ -65,6 +66,8 @@ public class UniverseCRUDHandler { @Inject RuntimeConfigFactory runtimeConfigFactory; + @Inject KubernetesManager kubernetesManager; + /** * Function to Trim keys and values of the passed map. * @@ -151,6 +154,7 @@ public UniverseResp createUniverse(Customer customer, UniverseDefinitionTaskPara } catch (IllegalArgumentException e) { throw new YWServiceException(BAD_REQUEST, e.getMessage()); } + checkHelmChartExists(c.userIntent.ybSoftwareVersion); } // Set the node exporter config based on the provider @@ -372,6 +376,7 @@ private UUID updatePrimaryCluster( if (primaryCluster.userIntent.providerType.equals(Common.CloudType.kubernetes)) { taskType = TaskType.EditKubernetesUniverse; notHelm2LegacyOrBadRequest(u); + checkHelmChartExists(primaryCluster.userIntent.ybSoftwareVersion); } else { mergeNodeExporterInfo(u, taskParams); } @@ -902,6 +907,14 @@ public UUID upgrade(Customer customer, Universe universe, UpgradeParams taskPara + "Manually migrate the deployment to helm3 " + "and then mark the universe as helm 3 compatible."); } + + if (customerTaskType == CustomerTask.TaskType.UpgradeGflags) { + // UpgradeGflags does not change universe version. Check for current version of helm chart. + checkHelmChartExists( + universe.getUniverseDetails().getPrimaryCluster().userIntent.ybSoftwareVersion); + } else { + checkHelmChartExists(taskParams.ybSoftwareVersion); + } } taskParams.rootCA = checkValidRootCA(universe.getUniverseDetails().rootCA); @@ -990,4 +1003,12 @@ public UUID updateDiskSize( universe.name); return taskUUID; } + + private void checkHelmChartExists(String ybSoftwareVersion) { + try { + kubernetesManager.getHelmPackagePath(ybSoftwareVersion); + } catch (RuntimeException e) { + throw new YWServiceException(BAD_REQUEST, e.getMessage()); + } + } } diff --git a/managed/src/main/resources/application.conf b/managed/src/main/resources/application.conf index 44eb1f729909..34f0e4c00a23 100644 --- a/managed/src/main/resources/application.conf +++ b/managed/src/main/resources/application.conf @@ -54,8 +54,8 @@ yb { multiTenant = true releases.path = "/opt/yugabyte/releases" thirdparty.packagePath = /opt/third-party - helm.package = "" - helm.package = ${?HELM_PACKAGE_PATH} + helm.packagePath = "" + helm.packagePath = ${?HELM_PACKAGE_PATH} helm.timeout_secs = 900 # Interval at which to check the status of every universe. Default: 5 minutes. health.check_interval_ms = 300000 diff --git a/managed/src/main/resources/swagger.json b/managed/src/main/resources/swagger.json index d6975bfa2235..d33e1a506f2e 100644 --- a/managed/src/main/resources/swagger.json +++ b/managed/src/main/resources/swagger.json @@ -57,13 +57,13 @@ "type" : "string" }, "groupType" : { - "description" : "Alert definition group type", + "description" : "Alert definition group type.", "enum" : [ "CUSTOMER", "UNIVERSE" ], "readOnly" : true, "type" : "string" }, "groupUuid" : { - "description" : "Alert group Uuid", + "description" : "Alert group Uuid.", "format" : "uuid", "readOnly" : true, "type" : "string" @@ -84,6 +84,30 @@ "readOnly" : true, "type" : "string" }, + "nextNotificationTime" : { + "description" : "Time of the nex notification attempt.", + "format" : "date-time", + "readOnly" : true, + "type" : "string" + }, + "notificationAttemptTime" : { + "description" : "Time of the last notification attempt.", + "format" : "date-time", + "readOnly" : true, + "type" : "string" + }, + "notificationsFailed" : { + "description" : "Count of failures to send a notification.", + "format" : "int32", + "readOnly" : true, + "type" : "integer" + }, + "notifiedState" : { + "description" : "Alert state in last sent notification.", + "enum" : [ "CREATED", "ACTIVE", "ACKNOWLEDGED", "RESOLVED" ], + "readOnly" : true, + "type" : "string" + }, "resolvedTime" : { "description" : "Resolved Date time info.", "format" : "date-time", @@ -2874,6 +2898,10 @@ "Release data" : { "description" : "Release data", "properties" : { + "chartPath" : { + "description" : "Helm chart path", + "type" : "string" + }, "filePath" : { "description" : "Release file path", "type" : "string" @@ -8793,4 +8821,4 @@ }, { "name" : "Users" } ] -} +} \ No newline at end of file diff --git a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/CreateKubernetesUniverseTest.java b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/CreateKubernetesUniverseTest.java index fb2f00e60c91..21644dd13bda 100644 --- a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/CreateKubernetesUniverseTest.java +++ b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/CreateKubernetesUniverseTest.java @@ -66,6 +66,7 @@ public class CreateKubernetesUniverseTest extends CommissionerBaseTest { YBClient mockClient; String nodePrefix = "demo-universe"; + String ybSoftwareVersion = "1.0.0"; String nodePrefix1, nodePrefix2, nodePrefix3; String ns, ns1, ns2, ns3; @@ -89,7 +90,7 @@ private void setupUniverseMultiAZ( userIntent.masterGFlags = new HashMap<>(); userIntent.tserverGFlags = new HashMap<>(); userIntent.universeName = "demo-universe"; - userIntent.ybSoftwareVersion = "1.0.0"; + userIntent.ybSoftwareVersion = ybSoftwareVersion; userIntent.enableYEDIS = enabledYEDIS; defaultUniverse = createUniverse(defaultCustomer.getCustomerId()); Universe.saveDetails( @@ -160,7 +161,7 @@ private void setupUniverse(boolean setMasters, boolean enabledYEDIS, boolean set userIntent.masterGFlags = new HashMap<>(); userIntent.tserverGFlags = new HashMap<>(); userIntent.universeName = "demo-universe"; - userIntent.ybSoftwareVersion = "1.0.0"; + userIntent.ybSoftwareVersion = ybSoftwareVersion; userIntent.enableYEDIS = enabledYEDIS; defaultUniverse = createUniverse(defaultCustomer.getCustomerId()); Universe.saveDetails( @@ -220,7 +221,7 @@ private void setupUniverse(boolean setMasters, boolean enabledYEDIS, boolean set private void setupCommon() { ShellResponse response = new ShellResponse(); when(mockKubernetesManager.createNamespace(anyMap(), any())).thenReturn(response); - when(mockKubernetesManager.helmInstall(anyMap(), any(), any(), any(), any())) + when(mockKubernetesManager.helmInstall(any(), anyMap(), any(), any(), any(), any())) .thenReturn(response); // Table RPCs. mockClient = mock(YBClient.class); @@ -369,6 +370,7 @@ private void testCreateKubernetesUniverseSuccessMultiAZBase(boolean setNamespace verify(mockKubernetesManager, times(1)) .helmInstall( + eq(ybSoftwareVersion), eq(config1), eq(defaultProvider.uuid), eq(nodePrefix1), @@ -376,6 +378,7 @@ private void testCreateKubernetesUniverseSuccessMultiAZBase(boolean setNamespace expectedOverrideFile.capture()); verify(mockKubernetesManager, times(1)) .helmInstall( + eq(ybSoftwareVersion), eq(config2), eq(defaultProvider.uuid), eq(nodePrefix2), @@ -383,6 +386,7 @@ private void testCreateKubernetesUniverseSuccessMultiAZBase(boolean setNamespace expectedOverrideFile.capture()); verify(mockKubernetesManager, times(1)) .helmInstall( + eq(ybSoftwareVersion), eq(config3), eq(defaultProvider.uuid), eq(nodePrefix3), @@ -435,6 +439,7 @@ private void testCreateKubernetesUniverseSuccessSingleAZBase(boolean setNamespac verify(mockKubernetesManager, times(1)) .helmInstall( + eq(ybSoftwareVersion), eq(config), eq(defaultProvider.uuid), eq(nodePrefix), diff --git a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/EditKubernetesUniverseTest.java b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/EditKubernetesUniverseTest.java index d053aebbfa47..4e1ad0d15a02 100644 --- a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/EditKubernetesUniverseTest.java +++ b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/EditKubernetesUniverseTest.java @@ -67,13 +67,15 @@ public class EditKubernetesUniverseTest extends CommissionerBaseTest { ShellResponse dummyShellResponse; String nodePrefix = "demo-universe"; + String ybSoftwareVersion = "1.0.0"; Map config = new HashMap(); private void setup() { ShellResponse responseEmpty = new ShellResponse(); ShellResponse responsePod = new ShellResponse(); - when(mockKubernetesManager.helmUpgrade(any(), any(), any(), any())).thenReturn(responseEmpty); + when(mockKubernetesManager.helmUpgrade(any(), any(), any(), any(), any())) + .thenReturn(responseEmpty); responsePod.message = "{\"status\": { \"phase\": \"Running\", \"conditions\": [{\"status\": \"True\"}]}}"; when(mockKubernetesManager.getPodStatus(any(), any(), any())).thenReturn(responsePod); @@ -114,7 +116,7 @@ private void setupUniverseSingleAZ(boolean setMasters) { userIntent.masterGFlags = new HashMap<>(); userIntent.tserverGFlags = new HashMap<>(); userIntent.universeName = "demo-universe"; - userIntent.ybSoftwareVersion = "old-version"; + userIntent.ybSoftwareVersion = ybSoftwareVersion; defaultUniverse = createUniverse(defaultCustomer.getCustomerId()); Universe.saveDetails( defaultUniverse.universeUUID, @@ -281,6 +283,7 @@ public JsonNode parseShellResponseAsJson(ShellResponse response) { public void testAddNode() { setupUniverseSingleAZ(/* Create Masters */ true); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedOverrideFile = ArgumentCaptor.forClass(String.class); @@ -336,6 +339,7 @@ public void testAddNode() { verify(mockKubernetesManager, times(1)) .helmUpgrade( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), @@ -344,6 +348,7 @@ public void testAddNode() { .getPodInfos( expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(nodePrefix, expectedNodePrefix.getValue()); assertEquals(nodePrefix, expectedNamespace.getValue()); @@ -362,6 +367,7 @@ public void testRemoveNode() { setupUniverseSingleAZ(/* Create Masters */ true); ArgumentCaptor expectedUniverseUUID = ArgumentCaptor.forClass(UUID.class); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedOverrideFile = ArgumentCaptor.forClass(String.class); @@ -401,6 +407,7 @@ public void testRemoveNode() { verify(mockKubernetesManager, times(1)) .helmUpgrade( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), @@ -409,6 +416,7 @@ public void testRemoveNode() { .getPodInfos( expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(nodePrefix, expectedNodePrefix.getValue()); assertEquals(nodePrefix, expectedNamespace.getValue()); @@ -429,6 +437,7 @@ public void testRemoveNode() { public void testChangeInstanceType() { setupUniverseSingleAZ(/* Create Masters */ true); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedUniverseUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -476,6 +485,7 @@ public void testChangeInstanceType() { verify(mockKubernetesManager, times(3)) .helmUpgrade( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), @@ -487,6 +497,7 @@ public void testChangeInstanceType() { .getPodInfos( expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(nodePrefix, expectedNodePrefix.getValue()); assertEquals(nodePrefix, expectedNamespace.getValue()); diff --git a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/UpgradeKubernetesUniverseTest.java b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/UpgradeKubernetesUniverseTest.java index d1ce9f4dab6a..92a952e61c8e 100644 --- a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/UpgradeKubernetesUniverseTest.java +++ b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/UpgradeKubernetesUniverseTest.java @@ -67,6 +67,8 @@ public class UpgradeKubernetesUniverseTest extends CommissionerBaseTest { ShellResponse dummyShellResponse; String nodePrefix = "demo-universe"; + String ybSoftwareVersionOld = "old-version"; + String ybSoftwareVersionNew = "new-version"; Map config = new HashMap(); @@ -77,7 +79,7 @@ private void setupUniverse( userIntent.masterGFlags = new HashMap<>(); userIntent.tserverGFlags = new HashMap<>(); userIntent.universeName = "demo-universe"; - userIntent.ybSoftwareVersion = "old-version"; + userIntent.ybSoftwareVersion = ybSoftwareVersionOld; defaultUniverse = createUniverse(defaultCustomer.getCustomerId()); config.put("KUBECONFIG", "test"); defaultProvider.setConfig(config); @@ -92,7 +94,8 @@ private void setupUniverse( ShellResponse responseEmpty = new ShellResponse(); ShellResponse responsePod = new ShellResponse(); - when(mockKubernetesManager.helmUpgrade(any(), any(), any(), any())).thenReturn(responseEmpty); + when(mockKubernetesManager.helmUpgrade(any(), any(), any(), any(), any())) + .thenReturn(responseEmpty); Master.SysClusterConfigEntryPB.Builder configBuilder = Master.SysClusterConfigEntryPB.newBuilder().setVersion(2); @@ -401,6 +404,7 @@ public void testSoftwareUpgradeSingleAZ() { setupUniverseSingleAZ(/* Create Masters */ false); ArgumentCaptor expectedUniverseUUID = ArgumentCaptor.forClass(UUID.class); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedOverrideFile = ArgumentCaptor.forClass(String.class); @@ -415,6 +419,7 @@ public void testSoftwareUpgradeSingleAZ() { verify(mockKubernetesManager, times(6)) .helmUpgrade( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), @@ -426,6 +431,7 @@ public void testSoftwareUpgradeSingleAZ() { .getPodInfos( expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture()); + assertEquals(ybSoftwareVersionNew, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(nodePrefix, expectedNodePrefix.getValue()); assertEquals(nodePrefix, expectedNamespace.getValue()); @@ -443,6 +449,7 @@ public void testGFlagUpgradeSingleAZ() { setupUniverseSingleAZ(/* Create Masters */ false); ArgumentCaptor expectedUniverseUUID = ArgumentCaptor.forClass(UUID.class); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedOverrideFile = ArgumentCaptor.forClass(String.class); @@ -458,6 +465,7 @@ public void testGFlagUpgradeSingleAZ() { verify(mockKubernetesManager, times(6)) .helmUpgrade( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), @@ -469,6 +477,7 @@ public void testGFlagUpgradeSingleAZ() { .getPodInfos( expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture()); + assertEquals(ybSoftwareVersionOld, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(nodePrefix, expectedNodePrefix.getValue()); assertEquals(nodePrefix, expectedNamespace.getValue()); @@ -486,6 +495,7 @@ public void testSoftwareUpgradeMultiAZ() { setupUniverseMultiAZ(/* Create Masters */ false); ArgumentCaptor expectedUniverseUUID = ArgumentCaptor.forClass(UUID.class); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedOverrideFile = ArgumentCaptor.forClass(String.class); @@ -495,11 +505,12 @@ public void testSoftwareUpgradeMultiAZ() { String overrideFileRegex = "(.*)" + defaultUniverse.universeUUID + "(.*).yml"; UpgradeKubernetesUniverse.Params taskParams = new UpgradeKubernetesUniverse.Params(); - taskParams.ybSoftwareVersion = "new-version"; + taskParams.ybSoftwareVersion = ybSoftwareVersionNew; TaskInfo taskInfo = submitTask(taskParams, UpgradeTaskType.Software); verify(mockKubernetesManager, times(6)) .helmUpgrade( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), @@ -511,6 +522,7 @@ public void testSoftwareUpgradeMultiAZ() { .getPodInfos( expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture()); + assertEquals(ybSoftwareVersionNew, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertTrue(expectedNodePrefix.getValue().contains(nodePrefix)); assertTrue(expectedNamespace.getValue().contains(nodePrefix)); @@ -528,6 +540,7 @@ public void testGFlagUpgradeMultiAZ() { setupUniverseMultiAZ(/* Create Masters */ false); ArgumentCaptor expectedUniverseUUID = ArgumentCaptor.forClass(UUID.class); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedOverrideFile = ArgumentCaptor.forClass(String.class); @@ -543,6 +556,7 @@ public void testGFlagUpgradeMultiAZ() { verify(mockKubernetesManager, times(6)) .helmUpgrade( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), @@ -554,6 +568,7 @@ public void testGFlagUpgradeMultiAZ() { .getPodInfos( expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture()); + assertEquals(ybSoftwareVersionOld, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertTrue(expectedNodePrefix.getValue().contains(nodePrefix)); assertTrue(expectedNamespace.getValue().contains(nodePrefix)); diff --git a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/subtasks/KubernetesCommandExecutorTest.java b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/subtasks/KubernetesCommandExecutorTest.java index 66f566f47bac..9b0b0862eb1f 100644 --- a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/subtasks/KubernetesCommandExecutorTest.java +++ b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/subtasks/KubernetesCommandExecutorTest.java @@ -155,6 +155,7 @@ private KubernetesCommandExecutor createExecutor( KubernetesCommandExecutor kubernetesCommandExecutor = AbstractTaskBase.createTask(KubernetesCommandExecutor.class); KubernetesCommandExecutor.Params params = new KubernetesCommandExecutor.Params(); + params.ybSoftwareVersion = ybSoftwareVersion; params.providerUUID = defaultProvider.uuid; params.commandType = commandType; params.config = config; @@ -172,6 +173,7 @@ private KubernetesCommandExecutor createExecutor( KubernetesCommandExecutor kubernetesCommandExecutor = AbstractTaskBase.createTask(KubernetesCommandExecutor.class); KubernetesCommandExecutor.Params params = new KubernetesCommandExecutor.Params(); + params.ybSoftwareVersion = ybSoftwareVersion; params.providerUUID = defaultProvider.uuid; params.commandType = commandType; params.nodePrefix = defaultUniverse.getUniverseDetails().nodePrefix; @@ -355,6 +357,7 @@ public void testHelmInstall() throws IOException { kubernetesCommandExecutor.run(); assertEquals(hackPlacementUUID, defaultUniverse.getUniverseDetails().getPrimaryCluster().uuid); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -362,11 +365,13 @@ public void testHelmInstall() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(hackPlacementUUID, defaultUniverse.getUniverseDetails().getPrimaryCluster().uuid); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); @@ -397,6 +402,7 @@ public void testHelmInstallIPV6() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -404,11 +410,13 @@ public void testHelmInstallIPV6() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -437,6 +445,7 @@ public void testHelmInstallWithoutLoadbalancer() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -444,11 +453,13 @@ public void testHelmInstallWithoutLoadbalancer() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -478,6 +489,7 @@ public void testHelmInstallWithGflags() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -485,11 +497,13 @@ public void testHelmInstallWithGflags() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -523,6 +537,7 @@ public void testHelmInstallWithTLS() throws IOException { kubernetesCommandExecutor.taskParams().rootCA = defaultCert.uuid; kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -530,11 +545,13 @@ public void testHelmInstallWithTLS() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -568,6 +585,7 @@ public void testHelmInstallWithTLSNodeToNode() throws IOException { kubernetesCommandExecutor.taskParams().rootCA = defaultCert.uuid; kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -575,11 +593,13 @@ public void testHelmInstallWithTLSNodeToNode() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -613,6 +633,7 @@ public void testHelmInstallWithTLSClientToServer() throws IOException { kubernetesCommandExecutor.taskParams().rootCA = defaultCert.uuid; kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -620,11 +641,13 @@ public void testHelmInstallWithTLSClientToServer() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -647,6 +670,7 @@ private void testHelmInstallForInstanceType(String instanceType) throws IOExcept KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -654,11 +678,13 @@ private void testHelmInstallForInstanceType(String instanceType) throws IOExcept ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -703,6 +729,7 @@ public void testHelmInstallForAnnotations() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -710,11 +737,13 @@ public void testHelmInstallForAnnotations() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -743,6 +772,7 @@ public void testHelmInstallForAnnotationsRegion() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -750,11 +780,13 @@ public void testHelmInstallForAnnotationsRegion() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -782,6 +814,7 @@ public void testHelmInstallForAnnotationsAZ() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -789,11 +822,13 @@ public void testHelmInstallForAnnotationsAZ() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -823,6 +858,7 @@ public void testHelmInstallForAnnotationsPrecendence() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -830,11 +866,13 @@ public void testHelmInstallForAnnotationsPrecendence() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -861,6 +899,7 @@ public void testHelmInstallResourceOverrideMerge() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -868,11 +907,13 @@ public void testHelmInstallResourceOverrideMerge() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -930,6 +971,7 @@ public void testHelmInstallWithStorageClass() throws IOException { KubernetesCommandExecutor.CommandType.HELM_INSTALL, /* set namespace */ true); kubernetesCommandExecutor.run(); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -937,11 +979,13 @@ public void testHelmInstallWithStorageClass() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), expectedNamespace.capture(), expectedOverrideFile.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(defaultUniverse.getUniverseDetails().nodePrefix, expectedNodePrefix.getValue()); @@ -1192,6 +1236,7 @@ public void testHelmInstallLegacy() throws IOException { kubernetesCommandExecutor.run(); assertEquals(hackPlacementUUID, defaultUniverse.getUniverseDetails().getPrimaryCluster().uuid); + ArgumentCaptor expectedYbSoftwareVersion = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedProviderUUID = ArgumentCaptor.forClass(UUID.class); ArgumentCaptor expectedNodePrefix = ArgumentCaptor.forClass(String.class); ArgumentCaptor expectedNamespace = ArgumentCaptor.forClass(String.class); @@ -1199,6 +1244,7 @@ public void testHelmInstallLegacy() throws IOException { ArgumentCaptor expectedConfig = ArgumentCaptor.forClass(HashMap.class); verify(kubernetesManager, times(1)) .helmInstall( + expectedYbSoftwareVersion.capture(), expectedConfig.capture(), expectedProviderUUID.capture(), expectedNodePrefix.capture(), @@ -1207,6 +1253,7 @@ public void testHelmInstallLegacy() throws IOException { verify(kubernetesManager, times(1)) .getServices( expectedConfig.capture(), expectedNodePrefix.capture(), expectedNamespace.capture()); + assertEquals(ybSoftwareVersion, expectedYbSoftwareVersion.getValue()); assertEquals(config, expectedConfig.getValue()); assertEquals(hackPlacementUUID, defaultUniverse.getUniverseDetails().getPrimaryCluster().uuid); assertEquals(defaultProvider.uuid, expectedProviderUUID.getValue()); diff --git a/managed/src/test/java/com/yugabyte/yw/common/KubernetesManagerTest.java b/managed/src/test/java/com/yugabyte/yw/common/KubernetesManagerTest.java index 8494b31de5fb..4d34cf7f913b 100644 --- a/managed/src/test/java/com/yugabyte/yw/common/KubernetesManagerTest.java +++ b/managed/src/test/java/com/yugabyte/yw/common/KubernetesManagerTest.java @@ -2,6 +2,7 @@ package com.yugabyte.yw.common; +import static com.yugabyte.yw.common.TestHelper.createTempFile; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyList; import static org.mockito.Matchers.anyMap; @@ -14,9 +15,13 @@ import com.yugabyte.yw.commissioner.tasks.subtasks.KubernetesCommandExecutor; import com.yugabyte.yw.models.Customer; import com.yugabyte.yw.models.Provider; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +48,8 @@ public class KubernetesManagerTest extends FakeDBApplication { ArgumentCaptor description; Map configProvider = new HashMap(); + static String TMP_CHART_PATH = "/tmp/yugaware_tests/KubernetesManagerTest/charts"; + @Before public void setUp() { defaultCustomer = ModelFactory.testCustomer(); @@ -54,9 +61,20 @@ public void setUp() { command = ArgumentCaptor.forClass(ArrayList.class); config = ArgumentCaptor.forClass(HashMap.class); description = ArgumentCaptor.forClass(String.class); + new File(TMP_CHART_PATH).mkdirs(); + } + + @After + public void tearDown() throws IOException { + FileUtils.deleteDirectory(new File(TMP_CHART_PATH)); } private void runCommand(KubernetesCommandExecutor.CommandType commandType) { + runCommand(commandType, "2.8.0.0-b1"); + } + + private void runCommand( + KubernetesCommandExecutor.CommandType commandType, String ybSoftwareVersion) { ShellResponse response = new ShellResponse(); when(shellProcessHandler.run(anyList(), anyMap(), anyString())).thenReturn(response); @@ -64,6 +82,7 @@ private void runCommand(KubernetesCommandExecutor.CommandType commandType) { switch (commandType) { case HELM_INSTALL: kubernetesManager.helmInstall( + ybSoftwareVersion, configProvider, defaultProvider.uuid, "demo-universe", @@ -72,7 +91,11 @@ private void runCommand(KubernetesCommandExecutor.CommandType commandType) { break; case HELM_UPGRADE: kubernetesManager.helmUpgrade( - configProvider, "demo-universe", "demo-namespace", "/tmp/override.yml"); + ybSoftwareVersion, + configProvider, + "demo-universe", + "demo-namespace", + "/tmp/override.yml"); break; case POD_INFO: kubernetesManager.getPodInfos(configProvider, "demo-universe", "demo-namespace"); @@ -92,7 +115,11 @@ private void runCommand(KubernetesCommandExecutor.CommandType commandType) { @Test public void testHelmUpgrade() { - when(mockAppConfig.getString("yb.helm.package")).thenReturn("/my/helm.tgz"); + when(mockReleaseManager.getReleaseByVersion("2.8.0.0-b1")) + .thenReturn( + ReleaseManager.ReleaseMetadata.create("2.8.0.0-b1") + .withChartPath(TMP_CHART_PATH + "/yugabyte-2.8.0.0-b1-helm.tar.gz")); + createTempFile(TMP_CHART_PATH, "yugabyte-2.8.0.0-b1-helm.tar.gz", "Sample helm chart data"); when(mockAppConfig.getLong("yb.helm.timeout_secs")).thenReturn((long) 600); runCommand(KubernetesCommandExecutor.CommandType.HELM_UPGRADE); assertEquals( @@ -100,7 +127,7 @@ public void testHelmUpgrade() { "helm", "upgrade", "demo-universe", - "/my/helm.tgz", + "/tmp/yugaware_tests/KubernetesManagerTest/charts/yugabyte-2.8.0.0-b1-helm.tar.gz", "-f", "/tmp/override.yml", "--namespace", @@ -114,14 +141,18 @@ public void testHelmUpgrade() { @Test public void testHelmUpgradeNoTimeout() { - when(mockAppConfig.getString("yb.helm.package")).thenReturn("/my/helm.tgz"); + when(mockReleaseManager.getReleaseByVersion("2.8.0.0-b1")) + .thenReturn( + ReleaseManager.ReleaseMetadata.create("2.8.0.0-b1") + .withChartPath(TMP_CHART_PATH + "/yugabyte-2.8.0.0-b1-helm.tar.gz")); + createTempFile(TMP_CHART_PATH, "yugabyte-2.8.0.0-b1-helm.tar.gz", "Sample helm chart data"); runCommand(KubernetesCommandExecutor.CommandType.HELM_UPGRADE); assertEquals( ImmutableList.of( "helm", "upgrade", "demo-universe", - "/my/helm.tgz", + "/tmp/yugaware_tests/KubernetesManagerTest/charts/yugabyte-2.8.0.0-b1-helm.tar.gz", "-f", "/tmp/override.yml", "--namespace", @@ -138,13 +169,17 @@ public void testHelmUpgradeFailWithNoConfig() { try { runCommand(KubernetesCommandExecutor.CommandType.HELM_UPGRADE); } catch (RuntimeException e) { - assertEquals("Helm Package path not provided.", e.getMessage()); + assertEquals("Helm Package path not found for release: 2.8.0.0-b1", e.getMessage()); } } @Test public void helmInstallWithRequiredConfig() { - when(mockAppConfig.getString("yb.helm.package")).thenReturn("/my/helm.tgz"); + when(mockReleaseManager.getReleaseByVersion("2.8.0.0-b1")) + .thenReturn( + ReleaseManager.ReleaseMetadata.create("2.8.0.0-b1") + .withChartPath(TMP_CHART_PATH + "/yugabyte-2.8.0.0-b1-helm.tar.gz")); + createTempFile(TMP_CHART_PATH, "yugabyte-2.8.0.0-b1-helm.tar.gz", "Sample helm chart data"); when(mockAppConfig.getLong("yb.helm.timeout_secs")).thenReturn((long) 600); runCommand(KubernetesCommandExecutor.CommandType.HELM_INSTALL); assertEquals( @@ -152,7 +187,7 @@ public void helmInstallWithRequiredConfig() { "helm", "install", "demo-universe", - "/my/helm.tgz", + "/tmp/yugaware_tests/KubernetesManagerTest/charts/yugabyte-2.8.0.0-b1-helm.tar.gz", "--namespace", "demo-namespace", "-f", @@ -166,14 +201,18 @@ public void helmInstallWithRequiredConfig() { @Test public void helmInstallWithNoTimeout() { - when(mockAppConfig.getString("yb.helm.package")).thenReturn("/my/helm.tgz"); + when(mockReleaseManager.getReleaseByVersion("2.8.0.0-b1")) + .thenReturn( + ReleaseManager.ReleaseMetadata.create("2.8.0.0-b1") + .withChartPath(TMP_CHART_PATH + "/yugabyte-2.8.0.0-b1-helm.tar.gz")); + createTempFile(TMP_CHART_PATH, "yugabyte-2.8.0.0-b1-helm.tar.gz", "Sample helm chart data"); runCommand(KubernetesCommandExecutor.CommandType.HELM_INSTALL); assertEquals( ImmutableList.of( "helm", "install", "demo-universe", - "/my/helm.tgz", + "/tmp/yugaware_tests/KubernetesManagerTest/charts/yugabyte-2.8.0.0-b1-helm.tar.gz", "--namespace", "demo-namespace", "-f", @@ -190,10 +229,34 @@ public void helmInstallWithoutRequiredConfig() { try { runCommand(KubernetesCommandExecutor.CommandType.HELM_INSTALL); } catch (RuntimeException e) { - assertEquals("Helm Package path not provided.", e.getMessage()); + assertEquals("Helm Package path not found for release: 2.8.0.0-b1", e.getMessage()); } } + // TODO: Delete this test once all k8s customers are upgraded past 2.7 and legacy helm chart is no + // longer required + @Test + public void helmInstallWithLegacyVersion() { + when(mockAppConfig.getString("yb.helm.packagePath")).thenReturn(TMP_CHART_PATH); + createTempFile(TMP_CHART_PATH, "yugabyte-2.7-helm-legacy.tar.gz", "Sample helm chart data"); + runCommand(KubernetesCommandExecutor.CommandType.HELM_INSTALL, "2.7.0.0-b1"); + assertEquals( + ImmutableList.of( + "helm", + "install", + "demo-universe", + "/tmp/yugaware_tests/KubernetesManagerTest/charts/yugabyte-2.7-helm-legacy.tar.gz", + "--namespace", + "demo-namespace", + "-f", + "/tmp/override.yml", + "--timeout", + "300s", + "--wait"), + command.getValue()); + assertEquals(config.getValue(), configProvider); + } + @Test public void getPodInfos() { runCommand(KubernetesCommandExecutor.CommandType.POD_INFO); diff --git a/managed/src/test/java/com/yugabyte/yw/common/ReleaseManagerTest.java b/managed/src/test/java/com/yugabyte/yw/common/ReleaseManagerTest.java index fa68633f3ea7..236a0127ef33 100644 --- a/managed/src/test/java/com/yugabyte/yw/common/ReleaseManagerTest.java +++ b/managed/src/test/java/com/yugabyte/yw/common/ReleaseManagerTest.java @@ -8,6 +8,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -63,14 +64,15 @@ public void tearDown() throws IOException { private void createDummyReleases( List versions, boolean multipleRepos, boolean inDockerPath) { - createDummyReleases(versions, multipleRepos, inDockerPath, true); + createDummyReleases(versions, multipleRepos, inDockerPath, true, true); } private void createDummyReleases( List versions, boolean multipleRepos, boolean inDockerPath, - boolean hasEnterpriseStr) { + boolean hasEnterpriseStr, + boolean withHelmChart) { versions.forEach( (version) -> { String versionPath = String.format("%s/%s", TMP_STORAGE_PATH, version); @@ -85,6 +87,10 @@ private void createDummyReleases( createTempFile( versionPath, "devops.xyz." + version + "-centos-x86_64.tar.gz", "Sample data"); } + if (withHelmChart) { + createTempFile( + versionPath, "yugabyte-" + version + "-helm.tar.gz", "Sample helm chart data"); + } }); } @@ -92,35 +98,36 @@ private void createDummyReleases( public void testGetLocalReleasesWithValidPath() { List versions = ImmutableList.of("0.0.1", "0.0.2", "0.0.3"); createDummyReleases(versions, false, false); - Map releases = releaseManager.getLocalReleases(TMP_STORAGE_PATH); + Map releases = + releaseManager.getLocalReleases(TMP_STORAGE_PATH); assertEquals(3, releases.size()); - - releases - .keySet() - .forEach( - (version) -> { - assertTrue(versions.contains(version)); - }); + releases.forEach( + (version, release) -> { + assertTrue(versions.contains(version)); + assertNotNull(release.filePath); + assertNotNull(release.chartPath); + }); } @Test public void testGetLocalReleasesWithMultipleFilesValidPath() { List versions = ImmutableList.of("0.0.1", "0.0.2", "0.0.3"); createDummyReleases(versions, true, false); - Map releases = releaseManager.getLocalReleases(TMP_STORAGE_PATH); + Map releases = + releaseManager.getLocalReleases(TMP_STORAGE_PATH); assertEquals(3, releases.size()); - - releases - .keySet() - .forEach( - (version) -> { - assertTrue(versions.contains(version)); - }); + releases.forEach( + (version, release) -> { + assertTrue(versions.contains(version)); + assertNotNull(release.filePath); + assertNotNull(release.chartPath); + }); } @Test public void testGetLocalReleasesWithInvalidPath() { - Map releases = releaseManager.getLocalReleases("/foo/bar"); + Map releases = + releaseManager.getLocalReleases("/foo/bar"); assertTrue(releases.isEmpty()); } @@ -130,16 +137,20 @@ public void testLoadReleasesWithoutReleasePath() { Mockito.verify(configHelper, times(0)).loadConfigToDB(any(), anyMap()); } - private void assertReleases(Map expectedMap, HashMap releases) { - assertEquals(expectedMap.size(), releases.size()); - for (Object version : releases.keySet()) { - assertTrue(expectedMap.containsKey(version)); - Object expectedFile = expectedMap.get(version); - JsonNode releaseJson = Json.toJson(releases.get(version)); - assertValue(releaseJson, "filePath", expectedFile.toString()); - assertValue(releaseJson, "imageTag", version.toString()); - assertValue(releaseJson, "state", "ACTIVE"); - } + private void assertReleases( + Map expected, + Map actual) { + assertEquals(expected.size(), actual.size()); + expected.forEach( + (version, expectedRelease) -> { + assertTrue(actual.containsKey(version)); + ReleaseManager.ReleaseMetadata actualRelease = actual.get(version); + assertEquals(version, actualRelease.imageTag); + assertEquals(expectedRelease.imageTag, actualRelease.imageTag); + assertEquals(expectedRelease.state, actualRelease.state); + assertEquals(expectedRelease.filePath, actualRelease.filePath); + assertEquals(expectedRelease.chartPath, actualRelease.chartPath); + }); } @Test @@ -157,7 +168,32 @@ public void testLoadReleasesWithReleasePath() { .loadConfigToDB(configType.capture(), releaseMap.capture()); Map expectedMap = ImmutableMap.of( - "0.0.1", TMP_STORAGE_PATH + "/0.0.1/yugabyte-ee-0.0.1-centos-x86_64.tar.gz"); + "0.0.1", + ReleaseManager.ReleaseMetadata.create("0.0.1") + .withFilePath(TMP_STORAGE_PATH + "/0.0.1/yugabyte-ee-0.0.1-centos-x86_64.tar.gz") + .withChartPath(TMP_STORAGE_PATH + "/0.0.1/yugabyte-0.0.1-helm.tar.gz")); + assertReleases(expectedMap, releaseMap.getValue()); + } + + @Test + public void testLoadReleasesWithoutChart() { + when(appConfig.getString("yb.releases.path")).thenReturn(TMP_STORAGE_PATH); + List versions = ImmutableList.of("0.0.1"); + createDummyReleases(versions, false, false, true, false); + releaseManager.importLocalReleases(); + + ArgumentCaptor configType; + ArgumentCaptor releaseMap; + configType = ArgumentCaptor.forClass(ConfigHelper.ConfigType.class); + releaseMap = ArgumentCaptor.forClass(HashMap.class); + Mockito.verify(configHelper, times(1)) + .loadConfigToDB(configType.capture(), releaseMap.capture()); + Map expectedMap = + ImmutableMap.of( + "0.0.1", + ReleaseManager.ReleaseMetadata.create("0.0.1") + .withFilePath(TMP_STORAGE_PATH + "/0.0.1/yugabyte-ee-0.0.1-centos-x86_64.tar.gz") + .withChartPath("")); assertReleases(expectedMap, releaseMap.getValue()); } @@ -165,12 +201,13 @@ public void testLoadReleasesWithReleasePath() { public void testLoadReleasesWithReleaseAndDockerPath() { when(appConfig.getString("yb.releases.path")).thenReturn(TMP_STORAGE_PATH); when(appConfig.getString("yb.docker.release")).thenReturn(TMP_DOCKER_STORAGE_PATH); + when(appConfig.getString("yb.helm.packagePath")).thenReturn(TMP_DOCKER_STORAGE_PATH); List versions = ImmutableList.of("0.0.1"); createDummyReleases(versions, false, false); List dockerVersions = ImmutableList.of("0.0.2-b2"); createDummyReleases(dockerVersions, false, true); List dockerVersionsWithoutEe = ImmutableList.of("0.0.3-b3"); - createDummyReleases(dockerVersionsWithoutEe, false, true, false); + createDummyReleases(dockerVersionsWithoutEe, false, true, false, true); releaseManager.importLocalReleases(); ArgumentCaptor configType; ArgumentCaptor releaseMap; @@ -180,15 +217,24 @@ public void testLoadReleasesWithReleaseAndDockerPath() { .loadConfigToDB(configType.capture(), releaseMap.capture()); Map expectedMap = ImmutableMap.of( - "0.0.1", TMP_STORAGE_PATH + "/0.0.1/yugabyte-ee-0.0.1-centos-x86_64.tar.gz", - "0.0.2-b2", TMP_STORAGE_PATH + "/0.0.2-b2/yugabyte-ee-0.0.2-b2-centos-x86_64.tar.gz", - "0.0.3-b3", TMP_STORAGE_PATH + "/0.0.3-b3/yugabyte-0.0.3-b3-centos-x86_64.tar.gz"); + "0.0.1", + ReleaseManager.ReleaseMetadata.create("0.0.1") + .withFilePath( + TMP_STORAGE_PATH + "/0.0.1/yugabyte-ee-0.0.1-centos-x86_64.tar.gz") + .withChartPath(TMP_STORAGE_PATH + "/0.0.1/yugabyte-0.0.1-helm.tar.gz"), + "0.0.2-b2", + ReleaseManager.ReleaseMetadata.create("0.0.2-b2") + .withFilePath( + TMP_STORAGE_PATH + "/0.0.2-b2/yugabyte-ee-0.0.2-b2-centos-x86_64.tar.gz") + .withChartPath(TMP_STORAGE_PATH + "/0.0.2-b2/yugabyte-0.0.2-b2-helm.tar.gz"), + "0.0.3-b3", + ReleaseManager.ReleaseMetadata.create("0.0.3-b3") + .withFilePath( + TMP_STORAGE_PATH + "/0.0.3-b3/yugabyte-0.0.3-b3-centos-x86_64.tar.gz") + .withChartPath(TMP_STORAGE_PATH + "/0.0.3-b3/yugabyte-0.0.3-b3-helm.tar.gz")); assertEquals(SoftwareReleases, configType.getValue()); assertReleases(expectedMap, releaseMap.getValue()); - File dockerStoragePath = new File(TMP_DOCKER_STORAGE_PATH); - File[] files = dockerStoragePath.listFiles(); - assertEquals(0, files.length); } @Test @@ -208,13 +254,13 @@ public void testLoadReleasesWithReleaseAndDockerPathDuplicate() { .loadConfigToDB(configType.capture(), releaseMap.capture()); Map expectedMap = ImmutableMap.of( - "0.0.2-b2", TMP_STORAGE_PATH + "/0.0.2-b2/yugabyte-ee-0.0.2-b2-centos-x86_64.tar.gz"); + "0.0.2-b2", + ReleaseManager.ReleaseMetadata.create("0.0.2-b2") + .withFilePath( + TMP_STORAGE_PATH + "/0.0.2-b2/yugabyte-ee-0.0.2-b2-centos-x86_64.tar.gz") + .withChartPath(TMP_STORAGE_PATH + "/0.0.2-b2/yugabyte-0.0.2-b2-helm.tar.gz")); assertReleases(expectedMap, releaseMap.getValue()); assertEquals(SoftwareReleases, configType.getValue()); - - File dockerStoragePath = new File(TMP_DOCKER_STORAGE_PATH); - File[] files = dockerStoragePath.listFiles(); - assertEquals(0, files.length); } @Test diff --git a/managed/src/test/java/com/yugabyte/yw/controllers/UniverseControllerTestBase.java b/managed/src/test/java/com/yugabyte/yw/controllers/UniverseControllerTestBase.java index 2010221ca1da..eac5235fadc8 100644 --- a/managed/src/test/java/com/yugabyte/yw/controllers/UniverseControllerTestBase.java +++ b/managed/src/test/java/com/yugabyte/yw/controllers/UniverseControllerTestBase.java @@ -26,6 +26,7 @@ import com.yugabyte.yw.commissioner.HealthChecker; import com.yugabyte.yw.common.ApiHelper; import com.yugabyte.yw.common.ModelFactory; +import com.yugabyte.yw.common.ReleaseManager; import com.yugabyte.yw.common.ShellProcessHandler; import com.yugabyte.yw.common.YcqlQueryExecutor; import com.yugabyte.yw.common.YsqlQueryExecutor; @@ -97,6 +98,7 @@ public class UniverseControllerTestBase extends WithApplication { protected PlayCacheSessionStore mockSessionStore; protected AlertConfigurationWriter mockAlertConfigurationWriter; protected Config mockRuntimeConfig; + protected ReleaseManager mockReleaseManager; @Override protected Application provideApplication() { @@ -114,6 +116,7 @@ protected Application provideApplication() { mockSessionStore = mock(PlayCacheSessionStore.class); mockAlertConfigurationWriter = mock(AlertConfigurationWriter.class); mockRuntimeConfig = mock(Config.class); + mockReleaseManager = mock(ReleaseManager.class); healthChecker = mock(HealthChecker.class); when(mockRuntimeConfig.getBoolean("yb.cloud.enabled")).thenReturn(false); @@ -138,6 +141,7 @@ protected Application provideApplication() { .overrides( bind(RuntimeConfigFactory.class) .toInstance(new DummyRuntimeConfigFactoryImpl(mockRuntimeConfig))) + .overrides(bind(ReleaseManager.class).toInstance(mockReleaseManager)) .overrides(bind(HealthChecker.class).toInstance(healthChecker)) .build(); } diff --git a/managed/src/test/java/com/yugabyte/yw/controllers/UniverseCreateControllerTestBase.java b/managed/src/test/java/com/yugabyte/yw/controllers/UniverseCreateControllerTestBase.java index 821ba35374e7..880a6838f4b0 100644 --- a/managed/src/test/java/com/yugabyte/yw/controllers/UniverseCreateControllerTestBase.java +++ b/managed/src/test/java/com/yugabyte/yw/controllers/UniverseCreateControllerTestBase.java @@ -18,6 +18,7 @@ import static com.yugabyte.yw.common.AssertHelper.assertYWSE; import static com.yugabyte.yw.common.FakeApiHelper.doRequestWithAuthTokenAndBody; import static com.yugabyte.yw.common.PlacementInfoUtil.updateUniverseDefinition; +import static com.yugabyte.yw.common.TestHelper.createTempFile; import static com.yugabyte.yw.forms.UniverseConfigureTaskParams.ClusterOperationType.CREATE; import static junit.framework.TestCase.assertFalse; import static org.hamcrest.CoreMatchers.allOf; @@ -45,6 +46,7 @@ import com.yugabyte.yw.commissioner.Common; import com.yugabyte.yw.common.ModelFactory; import com.yugabyte.yw.common.PlacementInfoUtil; +import com.yugabyte.yw.common.ReleaseManager; import com.yugabyte.yw.forms.NodeInstanceFormData; import com.yugabyte.yw.forms.UniverseDefinitionTaskParams; import com.yugabyte.yw.forms.UniverseTaskParams; @@ -60,12 +62,16 @@ import com.yugabyte.yw.models.helpers.PlacementInfo.PlacementAZ; import com.yugabyte.yw.models.helpers.TaskType; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.UUID; import junitparams.JUnitParamsRunner; import junitparams.Parameters; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -75,6 +81,21 @@ @RunWith(JUnitParamsRunner.class) public abstract class UniverseCreateControllerTestBase extends UniverseControllerTestBase { + + static String TMP_CHART_PATH = "/tmp/yugaware_tests/UniverseUiOnlyControllerTest/charts"; + + @Before + public void setUp() { + super.setUp(); + new File(TMP_CHART_PATH).mkdirs(); + } + + @After + public void tearDown() throws IOException { + FileUtils.deleteDirectory(new File(TMP_CHART_PATH)); + super.tearDown(); + } + public abstract Result sendCreateRequest(ObjectNode bodyJson); public abstract Result sendPrimaryCreateConfigureRequest(ObjectNode topJson); @@ -396,6 +417,11 @@ public void testUniverseCreateDeviceInfoValidation( when(mockCommissioner.submit( Matchers.any(TaskType.class), Matchers.any(UniverseDefinitionTaskParams.class))) .thenReturn(fakeTaskUUID); + when(mockReleaseManager.getReleaseByVersion("1.0.0")) + .thenReturn( + ReleaseManager.ReleaseMetadata.create("1.0.0") + .withChartPath(TMP_CHART_PATH + "/yugabyte-1.0.0-helm.tar.gz")); + createTempFile(TMP_CHART_PATH, "yugabyte-1.0.0-helm.tar.gz", "Sample helm chart data"); Provider p; switch (cloudType) { @@ -436,7 +462,8 @@ public void testUniverseCreateDeviceInfoValidation( .put("replicationFactor", 3) .put("numNodes", 3) .put("provider", p.uuid.toString()) - .put("accessKeyCode", accessKeyCode); + .put("accessKeyCode", accessKeyCode) + .put("ybSoftwareVersion", "1.0.0"); ArrayNode regionList = Json.newArray().add(r.uuid.toString()); userIntentJson.set("regionList", regionList); ObjectNode deviceInfo = diff --git a/managed/src/test/resources/dev.expected.conf b/managed/src/test/resources/dev.expected.conf index 295c0887b818..fde0a48d30ad 100644 --- a/managed/src/test/resources/dev.expected.conf +++ b/managed/src/test/resources/dev.expected.conf @@ -119,7 +119,7 @@ yb.health.default_tls = false yb.health.ses_email_password = "RESOLVED_YB_ALERTS_PASSWORD" yb.health.ses_email_username = "RESOLVED_YB_ALERTS_USERNAME" yb.health.status_interval_ms = 43200000 -yb.helm.package = "RESOLVED_HELM_PACKAGE_PATH" +yb.helm.packagePath = "RESOLVED_HELM_PACKAGE_PATH" yb.helm.timeout_secs = 900 yb.log.logEnvVars = false yb.metrics.host = localhost diff --git a/managed/src/test/resources/helm.expected.conf b/managed/src/test/resources/helm.expected.conf index 33b10fa895a4..fef86648602d 100644 --- a/managed/src/test/resources/helm.expected.conf +++ b/managed/src/test/resources/helm.expected.conf @@ -120,7 +120,7 @@ yb.health.default_tls = false yb.health.ses_email_password = "helm#$password%" yb.health.ses_email_username = "alerts.helm.username" yb.health.status_interval_ms = 43200000 -yb.helm.package = "/opt/yugabyte/helm/yugabyte-latest.tgz" +yb.helm.packagePath = "/opt/yugabyte/helm" yb.helm.timeout_secs = 900 yb.metrics.host = localhost yb.metrics.url = "http://127.0.0.1:9090/api/v1" diff --git a/managed/src/test/resources/replicated.expected.conf b/managed/src/test/resources/replicated.expected.conf index bbe3c9c346db..13c53151583b 100644 --- a/managed/src/test/resources/replicated.expected.conf +++ b/managed/src/test/resources/replicated.expected.conf @@ -118,7 +118,7 @@ yb.health.default_tls = false yb.health.ses_email_password = "alerts#pass%" yb.health.ses_email_username = "alerts.username" yb.health.status_interval_ms = 43200000 -yb.helm.package = "/opt/yugabyte/helm/yugabyte-latest.tgz" +yb.helm.packagePath = "/opt/yugabyte/helm" yb.metrics.host = "5.6.7.8" yb.metrics.url = "http://5.6.7.8:9090/api/v1" yb.metrics.management.url = "http://5.6.7.8:9090/-" diff --git a/managed/src/test/resources/test.helm.params.conf b/managed/src/test/resources/test.helm.params.conf index cc7c24e3a7b9..0176aaa8b0e5 100644 --- a/managed/src/test/resources/test.helm.params.conf +++ b/managed/src/test/resources/test.helm.params.conf @@ -46,7 +46,7 @@ yb { docker.release = "/opt/yugabyte/release" # TODO(bogdan): need this extra level for installing from local... thirdparty.packagePath = /opt/third-party/third-party - helm.package = "/opt/yugabyte/helm/yugabyte-latest.tgz" + helm.packagePath = "/opt/yugabyte/helm" helm.timeout_secs = 900 health.check_interval_ms = 300000 health.status_interval_ms = 43200000 diff --git a/managed/src/test/resources/test.replicated.params.conf b/managed/src/test/resources/test.replicated.params.conf index 451f3f841bb3..9fca778a9580 100644 --- a/managed/src/test/resources/test.replicated.params.conf +++ b/managed/src/test/resources/test.replicated.params.conf @@ -72,7 +72,7 @@ yb { releases.path = "/opt/yugabyte/releases" docker.release = "/opt/yugabyte/release" thirdparty.packagePath = /opt/third-party - helm.package = "/opt/yugabyte/helm/yugabyte-latest.tgz" + helm.packagePath = "/opt/yugabyte/helm" health.check_interval_ms = 300000 health.status_interval_ms = 43200000 health.default_email = "repl.alerts@yugabyte.com" diff --git a/managed/ui/src/components/releases/ReleaseList/ReleaseList.js b/managed/ui/src/components/releases/ReleaseList/ReleaseList.js index 5297d50a0067..b37c2636be7a 100644 --- a/managed/ui/src/components/releases/ReleaseList/ReleaseList.js +++ b/managed/ui/src/components/releases/ReleaseList/ReleaseList.js @@ -1,16 +1,15 @@ // Copyright (c) YugaByte, Inc. -import React, { Component } from 'react'; -import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; -import { DropdownButton } from 'react-bootstrap'; +import React, {Component} from 'react'; +import {BootstrapTable, TableHeaderColumn} from 'react-bootstrap-table'; +import {DropdownButton} from 'react-bootstrap'; -import { YBPanelItem } from '../../../components/panels'; -import { YBButton, YBTextInput } from '../../../components/common/forms/fields'; -import { TableAction } from '../../../components/tables'; -import { YBLoadingCircleIcon } from '../../../components/common/indicators'; -import { getPromiseState } from '../../../utils/PromiseUtils'; -import { isAvailable } from '../../../utils/LayoutUtils'; -import { showOrRedirect } from '../../../utils/LayoutUtils'; +import {YBPanelItem} from '../../../components/panels'; +import {YBButton, YBTextInput} from '../../../components/common/forms/fields'; +import {TableAction} from '../../../components/tables'; +import {YBLoadingCircleIcon} from '../../../components/common/indicators'; +import {getPromiseState} from '../../../utils/PromiseUtils'; +import {isAvailable, showOrRedirect} from '../../../utils/LayoutUtils'; import './ReleaseList.scss'; @@ -218,10 +217,19 @@ export default class ReleaseList extends Component { tdStyle={{ whiteSpace: 'normal' }} columnClassName="no-border name-column" className="no-border" - width="550px" + width="225px" > File Path + + Chart Path +