From d457fc474c6100716f9d8c1b3d836646fa00413f Mon Sep 17 00:00:00 2001 From: Shubham Date: Thu, 21 Mar 2024 10:38:46 +0000 Subject: [PATCH] [PLAT-13150] Move sshUser/Port at bundle details level Summary: Previously, we were having sshUser/Port Override information at bundle->details->region->info level, which would make more sense at bundle->details level. This diff changes the same, also marking the previous behaviour as deprecated. Also, brings back old logic of saving arch at region level that will still be used in case `yb.provider.vm_os_patching` is not set. Will remove that bit once the above flag is turned on by default. Adds fine grained check while editing the in use bundles. We will allow only addition of new region AMI details in the bundle, otherwise information like sshUser/Port cannot be modified for in use bundle. Test Plan: Created a provider using old/new payload. Ensured that the details are stored as per the desired format. Performed OS patching. Performed Day2 operations. Tried modifying the used bundle (both via Provider PUT/ Image Bundle PUT) - verified that it failed. Took portal backup - ran migration against it. Reviewers: #yba-api-review, sneelakantan, amalyshev Reviewed By: #yba-api-review, amalyshev Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D33205 --- .../subtasks/cloud/CloudImageBundleSetup.java | 57 +++++++--- .../yw/common/CloudProviderHelper.java | 2 +- .../yugabyte/yw/common/CloudRegionHelper.java | 54 +++++++++- .../yugabyte/yw/common/ImageBundleUtil.java | 30 ++++-- .../yw/controllers/ImageBundleController.java | 17 ++- .../com/yugabyte/yw/models/ImageBundle.java | 36 ++++++- .../yw/models/ImageBundleDetails.java | 26 ++++- ...V335__Migrate_ImageBundle_Ssh_Details.java | 100 ++++++++++++++++++ .../src/main/resources/swagger-strict.json | 14 +-- managed/src/main/resources/swagger.json | 9 ++ .../tasks/CloudBootstrapTest.java | 4 +- .../CloudProviderApiControllerTest.java | 5 +- 12 files changed, 312 insertions(+), 42 deletions(-) create mode 100644 managed/src/main/java/db/migration/default_/postgres/V335__Migrate_ImageBundle_Ssh_Details.java diff --git a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/subtasks/cloud/CloudImageBundleSetup.java b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/subtasks/cloud/CloudImageBundleSetup.java index e9eaa77880ab..dca3bc06cce4 100644 --- a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/subtasks/cloud/CloudImageBundleSetup.java +++ b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/subtasks/cloud/CloudImageBundleSetup.java @@ -119,7 +119,8 @@ public static void generateYBADefaultImageBundle( CloudQueryHelper cloudQueryHelper, Architecture arch, boolean isDefault, - boolean forceFetchFromMetadata) { + boolean forceFetchFromMetadata, + boolean enableVMOSPatching) { List regions = provider.getRegions(); CloudType cloudType = provider.getCloudCode(); if (arch == null) { @@ -151,14 +152,16 @@ public static void generateYBADefaultImageBundle( // We are doing this as, once the bundle is configured with the custom image, // we should get rid of the image in the region object, so that during bundle // update it does not pick the image from region. - r.setYbImage(null); - r.save(); + if (enableVMOSPatching) { + r.setYbImage(null); + r.save(); + } } bundleInfo.setYbImage(ybImage); if (isCustomImage) { - bundleInfo.setSshUserOverride(provider.getDetails().getSshUser()); + details.setSshUser(provider.getDetails().getSshUser()); } else { - bundleInfo.setSshUserOverride(cloudType.getSshUser()); + details.setSshUser(cloudType.getSshUser()); } regionsImageInfo.put(r.getCode(), bundleInfo); } @@ -187,14 +190,22 @@ public static void generateYBADefaultImageBundle( // We are doing this as, once the bundle is configured with the custom image, // we should get rid of the image in the region object, so that during bundle // update it does not pick the image from region. - regions.stream() - .forEach( - r -> { - r.setYbImage(null); - r.save(); - }); + if (enableVMOSPatching) { + regions.stream() + .forEach( + r -> { + r.setYbImage(null); + r.save(); + }); + } } details.setGlobalYbImage(ybImage); + details.setSshUser(provider.getDetails().getSshUser()); + } + if (provider.getDetails().getSshPort() != null) { + details.setSshPort(provider.getDetails().getSshPort()); + } else { + details.setSshPort(22); } // If the bundle is not specified we will create YBA default with the type // YBA_ACTIVE. @@ -217,6 +228,7 @@ public void run() { List regions = provider.getRegions(); List imageBundles = taskParams().imageBundles; + boolean enableVMOSPatching = confGetter.getGlobalConf(GlobalConfKeys.enableVMOSPatching); if ((imageBundles == null || imageBundles.size() == 0) && provider.getImageBundles().size() == 0 && !taskParams().updateBundleRequest) { @@ -240,7 +252,8 @@ public void run() { } } } - generateYBADefaultImageBundle(provider, cloudQueryHelper, arch, true, false); + generateYBADefaultImageBundle( + provider, cloudQueryHelper, arch, true, false, enableVMOSPatching); } else if (imageBundles != null) { Map existingImageBundles = provider.getImageBundles().stream() @@ -252,7 +265,6 @@ public void run() { // creation failed in first try mid-way, we will delete up existing Bundles existingImageBundles.forEach((bundleUUID, bundle) -> bundle.delete()); } - boolean enableVMOSPatching = confGetter.getGlobalConf(GlobalConfKeys.enableVMOSPatching); for (ImageBundle bundle : imageBundles) { if (taskParams().updateBundleRequest && bundle.getUuid() != null) { updateBundles( @@ -317,7 +329,7 @@ private void updateYBAActiveImageBundles( String defaultRegionImage = cloudQueryHelper.getDefaultImage(region, bundle.getDetails().getArch().toString()); info.setYbImage(defaultRegionImage); - info.setSshUserOverride(provider.getDetails().getSshUser()); + details.setSshUser(provider.getDetails().getSshUser()); regionBundleInfo.put(region.getCode(), info); details.setRegions(regionBundleInfo); @@ -357,12 +369,21 @@ private void createBundle(Provider provider, List regions, ImageBundle b bundle.setName(getDefaultImageBundleName(provider.getCode())); String defaultImage = cloudQueryHelper.getDefaultImage(region, arch.toString()); bundleInfo.setYbImage(defaultImage); - bundleInfo.setSshUserOverride(cloudType.getSshUser()); + details.setSshUser(cloudType.getSshUser()); + details.setSshPort(22); // If we are populating the ybImage, bundle will be YBA_DEFAULT. metadata.setType(ImageBundleType.YBA_ACTIVE); metadata.setVersion(CLOUD_OS_MAP.get(provider.getCode()).getVersion()); } else { // In case user specified the AMI ids bundle will be CUSTOM. + if (StringUtils.isNotBlank(bundleInfo.getSshUserOverride())) { + details.setSshUser(bundleInfo.getSshUserOverride()); + bundleInfo.setSshUserOverride(null); + } + if (bundleInfo.getSshPortOverride() != null) { + details.setSshPort(bundleInfo.getSshPortOverride()); + bundleInfo.setSshPortOverride(null); + } metadata.setType(ImageBundleType.CUSTOM); } @@ -386,6 +407,12 @@ private void createBundle(Provider provider, List regions, ImageBundle b // In case user specified the image id bundle will be CUSTOM. metadata.setType(ImageBundleType.CUSTOM); } + if (details.getSshPort() == null) { + details.setSshPort(22); + } + if (StringUtils.isBlank(details.getSshUser())) { + details.setSshUser(cloudType.getSshUser()); + } } if (bundle.getUseAsDefault()) { // Check for the existence of no other default image bundle for the provider. diff --git a/managed/src/main/java/com/yugabyte/yw/common/CloudProviderHelper.java b/managed/src/main/java/com/yugabyte/yw/common/CloudProviderHelper.java index 9a7864cebc7e..e7c153b249dd 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/CloudProviderHelper.java +++ b/managed/src/main/java/com/yugabyte/yw/common/CloudProviderHelper.java @@ -931,7 +931,7 @@ private void validateProviderEditPayload(Provider provider, Provider editProvide } ImageBundle currentImageBundle = currentImageBundles.get(uuid); if (imageBundle.getUniverseCount() > 0 - && currentImageBundle.isUpdateNeeded(imageBundle)) { + && !currentImageBundle.allowUpdateDuringUniverseAssociation(imageBundle)) { throw new PlatformServiceException( BAD_REQUEST, String.format( diff --git a/managed/src/main/java/com/yugabyte/yw/common/CloudRegionHelper.java b/managed/src/main/java/com/yugabyte/yw/common/CloudRegionHelper.java index 159f466a4bb0..0c73db4c43bc 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/CloudRegionHelper.java +++ b/managed/src/main/java/com/yugabyte/yw/common/CloudRegionHelper.java @@ -3,8 +3,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.inject.Inject; +import com.yugabyte.yw.cloud.PublicCloudConstants.Architecture; import com.yugabyte.yw.commissioner.Common.CloudType; import com.yugabyte.yw.commissioner.tasks.CloudBootstrap.Params.PerRegionMetadata; +import com.yugabyte.yw.common.config.GlobalConfKeys; +import com.yugabyte.yw.common.config.RuntimeConfGetter; import com.yugabyte.yw.models.AvailabilityZone; import com.yugabyte.yw.models.Provider; import com.yugabyte.yw.models.Region; @@ -23,12 +26,15 @@ public class CloudRegionHelper { private final CloudQueryHelper queryHelper; + private final RuntimeConfGetter confGetter; private final ConfigHelper configHelper; @Inject - public CloudRegionHelper(CloudQueryHelper queryHelper, ConfigHelper configHelper) { + public CloudRegionHelper( + CloudQueryHelper queryHelper, ConfigHelper configHelper, RuntimeConfGetter confGetter) { this.queryHelper = queryHelper; this.configHelper = configHelper; + this.confGetter = confGetter; } public Region createRegion( @@ -77,6 +83,52 @@ public Region createRegion( // image reference. region.setYbImage(customImageId); region.update(); + } else if (!confGetter.getGlobalConf(GlobalConfKeys.enableVMOSPatching)) { + /* + * Todo: Remove this once flag `yb.provider.vm_os_pathcing` turned on by default, else + * the instances will not be correctly configured for the region b/c of missing arch + * in the region object. + */ + switch (CloudType.valueOf(provider.getCode())) { + // Intentional fallthrough for AWS, Azure & GCP should be covered the same way. + case aws: + case gcp: + case azu: + // Setup default image, if no custom one was specified. + String defaultImage = queryHelper.getDefaultImage(region, architecture); + if (defaultImage == null || defaultImage.isEmpty()) { + throw new RuntimeException("Could not get default image for region: " + regionCode); + } + region.setYbImage(defaultImage); + region.update(); + break; + } + + // Attempt to find architecture for AWS providers. + if (provider.getCode().equals(CloudType.aws.toString()) + && (region.getArchitecture() == null + || (customImageId != null && !customImageId.isEmpty()))) { + String arch = queryHelper.getImageArchitecture(region); + if (arch == null || arch.isEmpty()) { + log.warn( + "Could not get architecture for image {} in region {}.", + region.getYbImage(), + region.getCode()); + + } else { + try { + // explicitly overriding arch name to maintain equivalent type of architecture. + if (arch.equals("arm64")) { + arch = Architecture.aarch64.name(); + } + region.setArchitecture(Architecture.valueOf(arch)); + region.update(); + } catch (IllegalArgumentException e) { + log.warn( + "{} not a valid architecture. Skipping for region {}.", arch, region.getCode()); + } + } + } } String customSecurityGroupId = metadata.customSecurityGroupId; if (customSecurityGroupId != null && !customSecurityGroupId.isEmpty()) { diff --git a/managed/src/main/java/com/yugabyte/yw/common/ImageBundleUtil.java b/managed/src/main/java/com/yugabyte/yw/common/ImageBundleUtil.java index 8d3e5d0d3d49..d0d7f3709712 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/ImageBundleUtil.java +++ b/managed/src/main/java/com/yugabyte/yw/common/ImageBundleUtil.java @@ -27,6 +27,7 @@ import java.util.UUID; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; @Slf4j public class ImageBundleUtil { @@ -44,6 +45,7 @@ public ImageBundle.NodeProperties getNodePropertiesOrFail( ImageBundle.NodeProperties properties = new ImageBundle.NodeProperties(); ImageBundle bundle = ImageBundle.getOrBadRequest(imageBundleUUID); ProviderDetails providerDetails = bundle.getProvider().getDetails(); + ImageBundleDetails bundleDetails = bundle.getDetails(); if (Common.CloudType.aws.toString().equals(cloudCode)) { Map regionsBundleInfo = bundle.getDetails().getRegions(); @@ -53,8 +55,8 @@ public ImageBundle.NodeProperties getNodePropertiesOrFail( if (regionsBundleInfo.containsKey(region)) { bundleInfo = regionsBundleInfo.get(region); properties.setMachineImage(bundleInfo.getYbImage()); - properties.setSshPort(bundleInfo.getSshPortOverride()); - properties.setSshUser(bundleInfo.getSshUserOverride()); + properties.setSshPort(bundleDetails.getSshPort()); + properties.setSshUser(bundleDetails.getSshUser()); } else if (imageBundleValidationDisabled) { // In case the region object is not present in the imageBundle, & we have // disabled imageBundleValidation, add the empty BundleInfo object for that region. @@ -88,8 +90,16 @@ public ImageBundle.NodeProperties getNodePropertiesOrFail( } } else { properties.setMachineImage(bundle.getDetails().getGlobalYbImage()); - properties.setSshUser(providerDetails.getSshUser()); - properties.setSshPort(providerDetails.getSshPort()); + String sshUser = bundleDetails.getSshUser(); + if (StringUtils.isBlank(sshUser)) { + sshUser = providerDetails.getSshUser(); + } + Integer sshPort = bundleDetails.getSshPort(); + if (sshPort == null) { + sshPort = providerDetails.getSshPort(); + } + properties.setSshUser(sshUser); + properties.setSshPort(sshPort); } return properties; @@ -114,7 +124,6 @@ public void updateImageBundleIfRequired( } if (ybImage != null) { info.setYbImage(ybImage); - info.setSshUserOverride(provider.getDetails().getSshUser()); } bundle.getDetails().setRegions(bundleInfo); bundle.update(); @@ -161,6 +170,7 @@ public static ImageBundle getDefaultBundleForUniverse( } public void migrateImageBundlesForProviders(Provider provider) { + boolean enableVMOSPatching = runtimeConfGetter.getGlobalConf(GlobalConfKeys.enableVMOSPatching); List bundles = provider.getImageBundles(); if (bundles.size() == 0) { return; @@ -211,14 +221,20 @@ public void migrateImageBundlesForProviders(Provider provider) { // Populate the new YBA_ACTIVE bundle for x86 arch. CloudImageBundleSetup.generateYBADefaultImageBundle( - provider, cloudQueryHelper, Architecture.x86_64, x86YBADefaultBundleMarkedDefault, true); + provider, + cloudQueryHelper, + Architecture.x86_64, + x86YBADefaultBundleMarkedDefault, + true, + enableVMOSPatching); // Populate the new YBA_ACTIVE bundle for aarch64 arch. CloudImageBundleSetup.generateYBADefaultImageBundle( provider, cloudQueryHelper, Architecture.aarch64, aarch64YBADefaultBundleMarkedDefault, - true); + true, + enableVMOSPatching); } public Map collectUniversesImageBundles() { diff --git a/managed/src/main/java/com/yugabyte/yw/controllers/ImageBundleController.java b/managed/src/main/java/com/yugabyte/yw/controllers/ImageBundleController.java index 48b9f9e218e5..e203b202bd8e 100644 --- a/managed/src/main/java/com/yugabyte/yw/controllers/ImageBundleController.java +++ b/managed/src/main/java/com/yugabyte/yw/controllers/ImageBundleController.java @@ -144,9 +144,9 @@ public Result index(UUID customerUUID, UUID providerUUID, UUID imageBundleUUID) @YbaApi(visibility = YbaApiVisibility.PREVIEW, sinceYBAVersion = "2.20.0.0") public Result edit(UUID customerUUID, UUID providerUUID, UUID iBUUID, Http.Request request) { final Provider provider = Provider.getOrBadRequest(customerUUID, providerUUID); - checkImageBundleUsageInUniverses(providerUUID, iBUUID); - ImageBundle bundle = parseJsonAndValidate(request, ImageBundle.class); + checkImageBundleUsageInUniverses(providerUUID, iBUUID, bundle); + ImageBundle cBundle = imageBundleHandler.edit(provider, iBUUID, bundle); auditService() .createAuditEntryWithReqBody( @@ -182,10 +182,21 @@ public Result delete(UUID customerUUID, UUID providerUUID, UUID iBUUID, Http.Req } private void checkImageBundleUsageInUniverses(UUID providerUUID, UUID imageBundleUUID) { + checkImageBundleUsageInUniverses(providerUUID, imageBundleUUID, null); + } + + private void checkImageBundleUsageInUniverses( + UUID providerUUID, UUID imageBundleUUID, ImageBundle bundle) { ImageBundle iBundle = ImageBundle.getOrBadRequest(providerUUID, imageBundleUUID); long universeCount = iBundle.getUniverseCount(); - if (universeCount > 0) { + if (universeCount > 0 && bundle == null) { + throw new PlatformServiceException( + FORBIDDEN, + String.format( + "There %s %d universe%s using this imageBundle, cannot delete", + universeCount > 1 ? "are" : "is", universeCount, universeCount > 1 ? "s" : "")); + } else if (universeCount > 0 && !bundle.allowUpdateDuringUniverseAssociation(iBundle)) { throw new PlatformServiceException( FORBIDDEN, String.format( diff --git a/managed/src/main/java/com/yugabyte/yw/models/ImageBundle.java b/managed/src/main/java/com/yugabyte/yw/models/ImageBundle.java index 15ba11124762..b01a5db3d58b 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/ImageBundle.java +++ b/managed/src/main/java/com/yugabyte/yw/models/ImageBundle.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnore; import com.yugabyte.yw.cloud.PublicCloudConstants.Architecture; +import com.yugabyte.yw.commissioner.Common.CloudType; import com.yugabyte.yw.common.ImageBundleUtil; import com.yugabyte.yw.common.PlatformServiceException; import com.yugabyte.yw.forms.UniverseDefinitionTaskParams.Cluster; @@ -19,6 +20,7 @@ import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -202,7 +204,39 @@ public static List getYBADefaultBundles(UUID providerUUID) { @JsonIgnore public boolean isUpdateNeeded(ImageBundle bundle) { return !Objects.equals(this.getUseAsDefault(), bundle.getUseAsDefault()) - || !Objects.equals(this.getDetails(), bundle.getDetails()); + || !Objects.equals(this.getDetails(), bundle.getDetails()) + || (this.getDetails().getRegions() != null + && bundle.getDetails().getRegions() != null + && !this.getDetails().getRegions().equals(bundle.getDetails().getRegions())); + } + + @JsonIgnore + public boolean allowUpdateDuringUniverseAssociation(ImageBundle existingBundle) { + ImageBundleDetails existingDetails = existingBundle.getDetails(); + ImageBundleDetails details = this.getDetails(); + // We will allow fine grain edit in case the bundle is associated with + // the universe. We will allow addition of new AMI in the newly added + // region but will not allow any other edit. + if (existingBundle.getProvider().getCloudCode() == CloudType.aws) { + // Compare that AMI is not removed for any region in used bundle for AWS. + Map infoExistingBundle = existingDetails.getRegions(); + Map info = details.getRegions(); + + // We will not allow any region AMI information to be removed from the used bundle. + boolean allMatch = + infoExistingBundle.entrySet().stream() + .allMatch( + entry -> { + String key = entry.getKey(); + ImageBundleDetails.BundleInfo bundleInfo = entry.getValue(); + return info.containsKey(key) && info.get(key).equals(bundleInfo); + }); + + if (!allMatch) { + return false; + } + } + return details.equals(existingDetails); } @JsonIgnore diff --git a/managed/src/main/java/com/yugabyte/yw/models/ImageBundleDetails.java b/managed/src/main/java/com/yugabyte/yw/models/ImageBundleDetails.java index df0ad524a65a..d3f2a6454695 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/ImageBundleDetails.java +++ b/managed/src/main/java/com/yugabyte/yw/models/ImageBundleDetails.java @@ -2,22 +2,39 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.yugabyte.yw.cloud.PublicCloudConstants.Architecture; +import com.yugabyte.yw.models.common.YbaApi; +import com.yugabyte.yw.models.common.YbaApi.YbaApiVisibility; import io.swagger.annotations.ApiModelProperty; import java.util.Map; import lombok.Data; import lombok.EqualsAndHashCode; @Data -@EqualsAndHashCode(callSuper = false) +@EqualsAndHashCode( + callSuper = false, + exclude = {"regions"}) @JsonInclude(JsonInclude.Include.NON_NULL) public class ImageBundleDetails { @Data @JsonInclude(JsonInclude.Include.NON_NULL) + @EqualsAndHashCode(callSuper = false) public static class BundleInfo { @ApiModelProperty private String ybImage; - @ApiModelProperty private String sshUserOverride; - @ApiModelProperty private Integer sshPortOverride; + + @ApiModelProperty( + value = + "sshUserOverride for the bundle. Deprecated since " + + "YBA version 2.20.3.0. Use imageBundles.details.sshUser instead.") + @YbaApi(visibility = YbaApiVisibility.DEPRECATED, sinceYBAVersion = "2.20.3.0") + private String sshUserOverride; + + @ApiModelProperty( + value = + "sshPortOverride for the bundle. Deprecated since " + + "YBA version 2.20.3.0. Use imageBundles.details.sshUser instead.") + @YbaApi(visibility = YbaApiVisibility.DEPRECATED, sinceYBAVersion = "2.20.3.0") + private Integer sshPortOverride; } @ApiModelProperty(value = "Global YB image for the bundle") @@ -28,4 +45,7 @@ public static class BundleInfo { @ApiModelProperty(value = "Regions override for image bundle") private Map regions; + + @ApiModelProperty private String sshUser; + @ApiModelProperty private Integer sshPort; } diff --git a/managed/src/main/java/db/migration/default_/postgres/V335__Migrate_ImageBundle_Ssh_Details.java b/managed/src/main/java/db/migration/default_/postgres/V335__Migrate_ImageBundle_Ssh_Details.java new file mode 100644 index 000000000000..b1e36586e404 --- /dev/null +++ b/managed/src/main/java/db/migration/default_/postgres/V335__Migrate_ImageBundle_Ssh_Details.java @@ -0,0 +1,100 @@ +// Copyright (c) YugaByte, Inc. + +package db.migration.default_.postgres; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.yugabyte.yw.commissioner.Common; +import com.yugabyte.yw.models.ImageBundleDetails; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Map; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import play.libs.Json; + +@Slf4j +public class V335__Migrate_ImageBundle_Ssh_Details extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + migrate(context.getConnection()); + } + + public void migrate(Connection connection) throws Exception { + ResultSet providerRes = + connection + .createStatement() + .executeQuery( + "SELECT pgp_sym_decrypt(details, 'provider::details') as data, uuid, name, code" + + " from provider"); + PreparedStatement updateImageDetailsStmt = + connection.prepareStatement("UPDATE image_bundle set details = ? where uuid = ?"); + while (providerRes.next()) { + String providerUUID = providerRes.getString("uuid"); + byte[] providerDetailsByte = providerRes.getBytes("data"); + String providerCode = providerRes.getString("code"); + ObjectMapper objectMapper = Json.mapper(); + try { + JsonNode providerDetails = objectMapper.readTree(providerDetailsByte); + String query = + String.format( + "select details,uuid from image_bundle where provider_uuid='%s'", providerUUID); + ResultSet imageBundleRs = connection.createStatement().executeQuery(query); + while (imageBundleRs.next()) { + // update image bundle details + UUID imageBundleUUID = UUID.fromString(imageBundleRs.getString("uuid")); + JsonNode imageDetails = objectMapper.readTree(imageBundleRs.getString("details")); + ImageBundleDetails ibDetails = Json.fromJson(imageDetails, ImageBundleDetails.class); + String sshUser = ""; + Integer sshPort = null; + if (imageDetails != null) { + if (providerCode.equals("aws")) { + Map regions = ibDetails.getRegions(); + for (String region : regions.keySet()) { + ImageBundleDetails.BundleInfo info = regions.get(region); + sshUser = info.getSshUserOverride(); + sshPort = info.getSshPortOverride(); + ObjectNode regionsNode = (ObjectNode) ((ObjectNode) imageDetails).get("regions"); + ObjectNode regionNode = (ObjectNode) regionsNode.get(region); + regionNode.set("sshUserOverride", null); + regionNode.set("sshPortOverride", null); + regionsNode.put(region, regionNode); + ((ObjectNode) imageDetails).put("regions", regionsNode); + } + } + } + if (StringUtils.isBlank(sshUser)) { + if (providerDetails.has("sshUser")) { + sshUser = providerDetails.get("sshUser").asText(); + } else { + sshUser = Common.CloudType.valueOf(providerCode).getSshUser(); + } + } + if (sshPort == null) { + if (providerDetails.has("sshPort")) { + sshPort = providerDetails.get("sshPort").asInt(); + } else { + sshPort = 22; + } + } + ((ObjectNode) imageDetails).put("sshUser", sshUser); + ((ObjectNode) imageDetails).put("sshPort", sshPort); + + updateImageDetailsStmt.setObject(1, imageDetails, java.sql.Types.OTHER); + updateImageDetailsStmt.setObject(2, imageBundleUUID); + updateImageDetailsStmt.execute(); + } + } catch (Exception e) { + log.error("Migration V331 failed with error: ", e.getLocalizedMessage()); + throw e; + } + } + updateImageDetailsStmt.close(); + } +} diff --git a/managed/src/main/resources/swagger-strict.json b/managed/src/main/resources/swagger-strict.json index 3bda5032d69d..52d6f47592d8 100644 --- a/managed/src/main/resources/swagger-strict.json +++ b/managed/src/main/resources/swagger-strict.json @@ -2998,13 +2998,6 @@ }, "BundleInfo" : { "properties" : { - "sshPortOverride" : { - "format" : "int32", - "type" : "integer" - }, - "sshUserOverride" : { - "type" : "string" - }, "ybImage" : { "type" : "string" } @@ -5788,6 +5781,13 @@ }, "description" : "Regions override for image bundle", "type" : "object" + }, + "sshPort" : { + "format" : "int32", + "type" : "integer" + }, + "sshUser" : { + "type" : "string" } }, "type" : "object" diff --git a/managed/src/main/resources/swagger.json b/managed/src/main/resources/swagger.json index a017e3e0d52d..703110b74b39 100644 --- a/managed/src/main/resources/swagger.json +++ b/managed/src/main/resources/swagger.json @@ -3012,10 +3012,12 @@ "BundleInfo" : { "properties" : { "sshPortOverride" : { + "description" : "sshPortOverride for the bundle. Deprecated since YBA version 2.20.3.0. Use imageBundles.details.sshUser instead.", "format" : "int32", "type" : "integer" }, "sshUserOverride" : { + "description" : "sshUserOverride for the bundle. Deprecated since YBA version 2.20.3.0. Use imageBundles.details.sshUser instead.", "type" : "string" }, "ybImage" : { @@ -5815,6 +5817,13 @@ }, "description" : "Regions override for image bundle", "type" : "object" + }, + "sshPort" : { + "format" : "int32", + "type" : "integer" + }, + "sshUser" : { + "type" : "string" } }, "type" : "object" diff --git a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/CloudBootstrapTest.java b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/CloudBootstrapTest.java index 843e2ae02af1..2b1c1fb757f2 100644 --- a/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/CloudBootstrapTest.java +++ b/managed/src/test/java/com/yugabyte/yw/commissioner/tasks/CloudBootstrapTest.java @@ -558,13 +558,13 @@ public void testCloudBootstrapWithInstanceTemplate() throws InterruptedException } @Test - public void testCloudBootstrapSuccessAwsArchitecture() throws InterruptedException { + public void testCloudBootstrapSuccessAwsAarchArchitecture() throws InterruptedException { JsonNode zoneInfo = Json.parse("{\"us-west-1\": {\"zone-1\": \"subnet-1\"}}"); CloudBootstrap.Params taskParams = getBaseTaskParams(); CloudBootstrap.Params.PerRegionMetadata perRegionMetadata = new CloudBootstrap.Params.PerRegionMetadata(); perRegionMetadata.vpcId = "test-vpc"; - perRegionMetadata.architecture = Architecture.valueOf("x86_64"); + perRegionMetadata.architecture = Architecture.valueOf("aarch64"); taskParams.perRegionMetadata.put("us-west-1", perRegionMetadata); validateCloudBootstrapSuccess( taskParams, diff --git a/managed/src/test/java/com/yugabyte/yw/controllers/CloudProviderApiControllerTest.java b/managed/src/test/java/com/yugabyte/yw/controllers/CloudProviderApiControllerTest.java index 0a0e30c231de..50fc87f0c602 100644 --- a/managed/src/test/java/com/yugabyte/yw/controllers/CloudProviderApiControllerTest.java +++ b/managed/src/test/java/com/yugabyte/yw/controllers/CloudProviderApiControllerTest.java @@ -1418,14 +1418,15 @@ public void testProviderEditInUniverse() { p.setImageBundles(ImmutableList.of()); result = assertPlatformException(() -> editProvider(Json.toJson(p), p.getUuid(), false)); assertBadRequest(result, "Image Bundle ib-1 is associated with some universes. Cannot delete!"); - - ib.setUseAsDefault(false); + ib.getDetails().setSshUser("centos"); p.setImageBundles(ImmutableList.of(ib)); result = assertPlatformException(() -> editProvider(Json.toJson(p), p.getUuid(), false)); assertBadRequest(result, "Image Bundle ib-1 is associated with some universes. Cannot modify!"); result = getProvider(p.getUuid()); Provider provider = Json.fromJson(Json.parse(contentAsString(result)), Provider.class); + // Default for the bundle can be changed in case it is associated to the universe. + provider.getImageBundles().get(0).setUseAsDefault(false); JsonNode providerJson = Json.toJson(provider); JsonNode regionJson = providerJson.get("regions"); ObjectMapper objectMapper = new ObjectMapper();