Skip to content

Commit 6c7a340

Browse files
authored
Merge pull request #46589 from oracle-community/vmc-gi-version-tag
Vmc gi version tag
2 parents 00eec28 + 172968b commit 6c7a340

4 files changed

Lines changed: 336 additions & 11 deletions

File tree

.changelog/46589.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
resource/aws_odb_cloud_vm_cluster: Attempt to read GI Version from resource tags to avoid failures due to new API response values
3+
```

internal/service/odb/cloud_vm_cluster.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func newResourceCloudVmCluster(_ context.Context) (resource.ResourceWithConfigur
6161
const (
6262
ResNameCloudVmCluster = "Cloud Vm Cluster"
6363
MajorGiVersionPattern = `^\d+\.0\.0\.0$`
64+
GiVersionSystemTag = "odb:input_gi_version"
6465
)
6566

6667
var ResourceCloudVmCluster = newResourceCloudVmCluster
@@ -173,7 +174,7 @@ func (r *resourceCloudVmCluster) Schema(ctx context.Context, req resource.Schema
173174
Validators: giVersionValidator,
174175
Description: "A valid software version of Oracle Grid Infrastructure (GI). To get the list of valid values, use the ListGiVersions operation and specify the shape of the Exadata infrastructure. Example: 19.0.0.0 This member is required. Changing this will create a new resource.",
175176
},
176-
//Underlying API returns complete gi version. For example if gi_version 23.0.0.0 then underlying api returns a version starting with 23
177+
//Underlying API returns complete gi version. For example if gi_version 23.0.0.0 then underlying api returns a version starting with 23 or could be completely a different
177178
"gi_version_computed": schema.StringAttribute{
178179
Computed: true,
179180
PlanModifiers: []planmodifier.String{
@@ -510,6 +511,18 @@ func (r *resourceCloudVmCluster) ValidateConfig(ctx context.Context, req resourc
510511
)
511512
return
512513
}
514+
vmcTagAsMap := data.Tags.Elements()
515+
v, ok := vmcTagAsMap[GiVersionSystemTag]
516+
if ok {
517+
if v.String() != data.GiVersion.String() {
518+
err := errors.New(GiVersionSystemTag + " tag value must be equals to GiVersion value")
519+
resp.Diagnostics.AddError(
520+
create.ProblemStandardMessage(names.ODB, create.ErrActionCreating, ResNameCloudVmCluster, data.DisplayName.String(), err),
521+
err.Error(),
522+
)
523+
return
524+
}
525+
}
513526
}
514527

515528
func (r *resourceCloudVmCluster) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
@@ -569,7 +582,7 @@ func (r *resourceCloudVmCluster) Create(ctx context.Context, req resource.Create
569582
//scan listener port not returned by API directly
570583
plan.ScanListenerPortTcp = flex.Int32ToFramework(ctx, createdVmCluster.ListenerPort)
571584
plan.GiVersionComputed = flex.StringToFramework(ctx, createdVmCluster.GiVersion)
572-
giVersionMajor, err := getMajorGiVersion(createdVmCluster.GiVersion)
585+
giVersionMajor, err := getMajorGiVersion(ctx, conn, createdVmCluster.CloudVmClusterArn, createdVmCluster.GiVersion)
573586
if err != nil {
574587
resp.Diagnostics.AddError(
575588
create.ProblemStandardMessage(names.ODB, create.ErrActionWaitingForCreation, ResNameCloudVmCluster, plan.DisplayName.ValueString(), err),
@@ -615,7 +628,7 @@ func (r *resourceCloudVmCluster) Read(ctx context.Context, req resource.ReadRequ
615628
//scan listener port not returned by API directly
616629
state.ScanListenerPortTcp = flex.Int32ToFramework(ctx, out.ListenerPort)
617630
state.GiVersionComputed = flex.StringToFramework(ctx, out.GiVersion)
618-
giVersionMajor, err := getMajorGiVersion(out.GiVersion)
631+
giVersionMajor, err := getMajorGiVersion(ctx, conn, out.CloudVmClusterArn, out.GiVersion)
619632
if err != nil {
620633
resp.Diagnostics.AddError(
621634
create.ProblemStandardMessage(names.ODB, create.ErrActionWaitingForCreation, ResNameCloudVmCluster, state.CloudVmClusterId.ValueString(), err),
@@ -748,15 +761,29 @@ func findCloudVmClusterForResourceByID(ctx context.Context, conn *odb.Client, id
748761
}
749762
return out.CloudVmCluster, nil
750763
}
751-
func getMajorGiVersion(giVersionComputed *string) (*string, error) {
752-
giVersionMajor := strings.Split(*giVersionComputed, ".")[0]
753-
giVersionMajor = giVersionMajor + ".0.0.0"
754-
regxGiVersionMajor := regexache.MustCompile(MajorGiVersionPattern)
755-
if !regxGiVersionMajor.MatchString(giVersionMajor) {
756-
err := errors.New("gi_version major retrieved from gi_version_computed does not match the pattern 19.0.0.0")
764+
765+
// Here we will go through tag to find out whether we can find the input gi_version or not. If not found we will get the version from
766+
// computed gi version to ensure backward compatibility.
767+
func getMajorGiVersion(ctx context.Context, conn *odb.Client, arn *string, giVersionComputed *string) (*string, error) {
768+
tagsRead, err := listTags(ctx, conn, *arn)
769+
if err != nil {
757770
return nil, err
758771
}
759-
return &giVersionMajor, nil
772+
var inputGiVersion *string
773+
if tagsRead.KeyExists(GiVersionSystemTag) {
774+
inputGiVersion = tagsRead.KeyValue(GiVersionSystemTag)
775+
return inputGiVersion, nil
776+
} else {
777+
// This regexp based approach is for backward compatibility
778+
giVersionMajor := strings.Split(*giVersionComputed, ".")[0]
779+
giVersionMajor = giVersionMajor + ".0.0.0"
780+
regxGiVersionMajor := regexache.MustCompile(MajorGiVersionPattern)
781+
if !regxGiVersionMajor.MatchString(giVersionMajor) {
782+
err := errors.New("gi_version major retrieved from gi_version_computed does not match the pattern 19.0.0.0")
783+
return nil, err
784+
}
785+
return &giVersionMajor, nil
786+
}
760787
}
761788

762789
type cloudVmClusterResourceModel struct {

internal/service/odb/cloud_vm_cluster_test.go

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,49 @@ func TestAccODBCloudVmCluster_taggingTest(t *testing.T) {
173173
})
174174
}
175175

176+
func TestAccODBCloudVmCluster_giVersionTag(t *testing.T) {
177+
ctx := acctest.Context(t)
178+
if testing.Short() {
179+
t.Skip("skipping long-running test in short mode")
180+
}
181+
var cloudvmcluster1 odbtypes.CloudVmCluster
182+
vmcDisplayName := sdkacctest.RandomWithPrefix(vmClusterTestEntity.vmClusterDisplayNamePrefix)
183+
resourceName := "aws_odb_cloud_vm_cluster.test"
184+
185+
publicKey, _, err := sdkacctest.RandSSHKeyPair(acctest.DefaultEmailAddress)
186+
if err != nil {
187+
t.Fatal(err)
188+
return
189+
}
190+
vmcWithGiVersionTag := vmClusterTestEntity.cloudVmClusterConfigWithGiVersionTag(vmcDisplayName, publicKey)
191+
resource.ParallelTest(t, resource.TestCase{
192+
PreCheck: func() {
193+
acctest.PreCheck(ctx, t)
194+
vmClusterTestEntity.testAccPreCheck(ctx, t)
195+
},
196+
ErrorCheck: acctest.ErrorCheck(t, names.ODBServiceID),
197+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
198+
CheckDestroy: vmClusterTestEntity.testAccCheckCloudVmClusterDestroy(ctx),
199+
Steps: []resource.TestStep{
200+
{
201+
Config: vmcWithGiVersionTag,
202+
Check: resource.ComposeAggregateTestCheckFunc(
203+
resource.ComposeTestCheckFunc(func(state *terraform.State) error {
204+
return nil
205+
}),
206+
resource.TestCheckResourceAttr(resourceName, "gi_version_computed", "26.0.0.0"),
207+
vmClusterTestEntity.testAccCheckCloudVmClusterExists(ctx, resourceName, &cloudvmcluster1),
208+
),
209+
},
210+
{
211+
ResourceName: resourceName,
212+
ImportState: true,
213+
ImportStateVerify: true,
214+
},
215+
},
216+
})
217+
}
218+
176219
func TestAccODBCloudVmCluster_real(t *testing.T) {
177220
ctx := acctest.Context(t)
178221
if testing.Short() {
@@ -772,3 +815,220 @@ resource "aws_odb_cloud_vm_cluster" "test" {
772815

773816
return vmClusterResourceNoTag, vmClusterResourceWithTag
774817
}
818+
819+
func (cloudVmClusterResourceTest) cloudVmClusterConfigWithGiVersionTag(vmClusterDisplayName, sshKey string) string {
820+
exaInfraDisplayName := sdkacctest.RandomWithPrefix(vmClusterTestEntity.exaInfraDisplayNamePrefix)
821+
odbNetDisplayName := sdkacctest.RandomWithPrefix(vmClusterTestEntity.odbNetDisplayNamePrefix)
822+
exaInfra := vmClusterTestEntity.exaInfra(exaInfraDisplayName)
823+
odbNet := vmClusterTestEntity.oracleDBNetwork(odbNetDisplayName)
824+
vmcWithGiVersionTag := fmt.Sprintf(`
825+
826+
827+
%s
828+
829+
%s
830+
831+
832+
833+
data "aws_odb_db_servers" "test" {
834+
cloud_exadata_infrastructure_id = aws_odb_cloud_exadata_infrastructure.test.id
835+
}
836+
837+
resource "aws_odb_cloud_vm_cluster" "test" {
838+
display_name = %[3]q
839+
cloud_exadata_infrastructure_id = aws_odb_cloud_exadata_infrastructure.test.id
840+
cpu_core_count = 16
841+
gi_version = "23.0.0.0"
842+
hostname_prefix = "apollo-12"
843+
ssh_public_keys = ["%[4]s"]
844+
odb_network_id = aws_odb_network.test.id
845+
is_local_backup_enabled = true
846+
is_sparse_diskgroup_enabled = true
847+
license_model = "LICENSE_INCLUDED"
848+
data_storage_size_in_tbs = 20.0
849+
db_servers = [for db_server in data.aws_odb_db_servers.test.db_servers : db_server.id]
850+
db_node_storage_size_in_gbs = 120.0
851+
memory_size_in_gbs = 60
852+
data_collection_options {
853+
is_diagnostics_events_enabled = false
854+
is_health_monitoring_enabled = false
855+
is_incident_logs_enabled = false
856+
}
857+
tags = {
858+
"odb:input_gi_version" = "23.0.0.0"
859+
"foo" = "bar"
860+
}
861+
862+
}
863+
`, exaInfra, odbNet, vmClusterDisplayName, sshKey)
864+
865+
return vmcWithGiVersionTag
866+
}
867+
868+
/*
869+
To ensure fallback works as expected, an additional acceptance tests which creates the cluster with a previous version
870+
that does not have the GI version tag, then upgrade to the latest version.
871+
We are using an ExternalProviders block to pin to a previous version of the provider for the first test step.
872+
*/
873+
func TestAccODBCloudVmCluster_previousProviderVersion(t *testing.T) {
874+
ctx := acctest.Context(t)
875+
if testing.Short() {
876+
t.Skip("skipping long-running test in short mode")
877+
}
878+
var cloudvmcluster1 odbtypes.CloudVmCluster
879+
var cloudvmcluster2 odbtypes.CloudVmCluster
880+
var cloudvmcluster3 odbtypes.CloudVmCluster
881+
vmcDisplayName := sdkacctest.RandomWithPrefix(vmClusterTestEntity.vmClusterDisplayNamePrefix)
882+
resourceName := "aws_odb_cloud_vm_cluster.test"
883+
884+
publicKey, _, err := sdkacctest.RandSSHKeyPair(acctest.DefaultEmailAddress)
885+
if err != nil {
886+
t.Fatal(err)
887+
return
888+
}
889+
890+
vmcWithoutGiVersionTag, vmcWithGiVersionTag := vmClusterTestEntity.cloudVmClusterConfigWithOlderTfProviderAndUpgradeToLatest(vmcDisplayName, publicKey)
891+
resource.ParallelTest(t, resource.TestCase{
892+
PreCheck: func() {
893+
acctest.PreCheck(ctx, t)
894+
vmClusterTestEntity.testAccPreCheck(ctx, t)
895+
},
896+
ErrorCheck: acctest.ErrorCheck(t, names.ODBServiceID),
897+
CheckDestroy: vmClusterTestEntity.testAccCheckCloudVmClusterDestroy(ctx),
898+
Steps: []resource.TestStep{
899+
{
900+
ExternalProviders: map[string]resource.ExternalProvider{
901+
"aws": {
902+
Source: "hashicorp/aws",
903+
VersionConstraint: "6.43.0",
904+
},
905+
},
906+
Config: vmcWithoutGiVersionTag,
907+
Check: resource.ComposeAggregateTestCheckFunc(
908+
vmClusterTestEntity.testAccCheckCloudVmClusterExists(ctx, resourceName, &cloudvmcluster1),
909+
resource.TestCheckResourceAttrSet(resourceName, names.AttrID),
910+
resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"),
911+
),
912+
},
913+
{
914+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
915+
Config: vmcWithoutGiVersionTag,
916+
Check: resource.ComposeAggregateTestCheckFunc(
917+
vmClusterTestEntity.testAccCheckCloudVmClusterExists(ctx, resourceName, &cloudvmcluster2),
918+
resource.TestCheckResourceAttrSet(resourceName, names.AttrID),
919+
resource.ComposeTestCheckFunc(func(state *terraform.State) error {
920+
if strings.Compare(*(cloudvmcluster1.CloudVmClusterId), *(cloudvmcluster2.CloudVmClusterId)) != 0 {
921+
return errors.New("Should not create a new cloud vm cluster")
922+
}
923+
return nil
924+
}),
925+
),
926+
},
927+
{
928+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
929+
Config: vmcWithGiVersionTag,
930+
Check: resource.ComposeAggregateTestCheckFunc(
931+
vmClusterTestEntity.testAccCheckCloudVmClusterExists(ctx, resourceName, &cloudvmcluster3),
932+
resource.TestCheckResourceAttrSet(resourceName, names.AttrID),
933+
resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"),
934+
resource.TestCheckResourceAttr(resourceName, "tags.odb:input_gi_version", "26.0.0.0"),
935+
resource.ComposeTestCheckFunc(func(state *terraform.State) error {
936+
if strings.Compare(*(cloudvmcluster1.CloudVmClusterId), *(cloudvmcluster3.CloudVmClusterId)) != 0 {
937+
return errors.New("Should not create a new cloud vm cluster")
938+
}
939+
return nil
940+
}),
941+
),
942+
},
943+
},
944+
})
945+
}
946+
947+
func (cloudVmClusterResourceTest) cloudVmClusterConfigWithOlderTfProviderAndUpgradeToLatest(vmClusterDisplayName, sshKey string) (string, string) {
948+
exaInfraDisplayName := sdkacctest.RandomWithPrefix(vmClusterTestEntity.exaInfraDisplayNamePrefix)
949+
odbNetDisplayName := sdkacctest.RandomWithPrefix(vmClusterTestEntity.odbNetDisplayNamePrefix)
950+
exaInfra := vmClusterTestEntity.exaInfra(exaInfraDisplayName)
951+
odbNet := vmClusterTestEntity.oracleDBNetwork(odbNetDisplayName)
952+
vmcWithoutGiVersionTag := fmt.Sprintf(`
953+
954+
955+
%s
956+
957+
%s
958+
959+
960+
961+
data "aws_odb_db_servers" "test" {
962+
cloud_exadata_infrastructure_id = aws_odb_cloud_exadata_infrastructure.test.id
963+
}
964+
965+
resource "aws_odb_cloud_vm_cluster" "test" {
966+
display_name = %[3]q
967+
cloud_exadata_infrastructure_id = aws_odb_cloud_exadata_infrastructure.test.id
968+
cpu_core_count = 16
969+
gi_version = "26.0.0.0"
970+
hostname_prefix = "apollo-12"
971+
ssh_public_keys = ["%[4]s"]
972+
odb_network_id = aws_odb_network.test.id
973+
is_local_backup_enabled = true
974+
is_sparse_diskgroup_enabled = true
975+
license_model = "LICENSE_INCLUDED"
976+
data_storage_size_in_tbs = 20.0
977+
db_servers = [for db_server in data.aws_odb_db_servers.test.db_servers : db_server.id]
978+
db_node_storage_size_in_gbs = 120.0
979+
memory_size_in_gbs = 60
980+
data_collection_options {
981+
is_diagnostics_events_enabled = false
982+
is_health_monitoring_enabled = false
983+
is_incident_logs_enabled = false
984+
}
985+
tags = {
986+
"foo" = "bar"
987+
}
988+
989+
}
990+
`, exaInfra, odbNet, vmClusterDisplayName, sshKey)
991+
992+
vmcWithGiVersionTag := fmt.Sprintf(`
993+
994+
995+
%s
996+
997+
%s
998+
999+
1000+
1001+
data "aws_odb_db_servers" "test" {
1002+
cloud_exadata_infrastructure_id = aws_odb_cloud_exadata_infrastructure.test.id
1003+
}
1004+
1005+
resource "aws_odb_cloud_vm_cluster" "test" {
1006+
display_name = %[3]q
1007+
cloud_exadata_infrastructure_id = aws_odb_cloud_exadata_infrastructure.test.id
1008+
cpu_core_count = 16
1009+
gi_version = "26.0.0.0"
1010+
hostname_prefix = "apollo-12"
1011+
ssh_public_keys = ["%[4]s"]
1012+
odb_network_id = aws_odb_network.test.id
1013+
is_local_backup_enabled = true
1014+
is_sparse_diskgroup_enabled = true
1015+
license_model = "LICENSE_INCLUDED"
1016+
data_storage_size_in_tbs = 20.0
1017+
db_servers = [for db_server in data.aws_odb_db_servers.test.db_servers : db_server.id]
1018+
db_node_storage_size_in_gbs = 120.0
1019+
memory_size_in_gbs = 60
1020+
data_collection_options {
1021+
is_diagnostics_events_enabled = false
1022+
is_health_monitoring_enabled = false
1023+
is_incident_logs_enabled = false
1024+
}
1025+
tags = {
1026+
"odb:input_gi_version" = "26.0.0.0"
1027+
"foo" = "bar"
1028+
}
1029+
1030+
}
1031+
`, exaInfra, odbNet, vmClusterDisplayName, sshKey)
1032+
1033+
return vmcWithoutGiVersionTag, vmcWithGiVersionTag
1034+
}

0 commit comments

Comments
 (0)