From 3694e351a7a60ff0c7b0308c8c7b54d90ab1185a Mon Sep 17 00:00:00 2001 From: michaelkad <45772690+michaelkad@users.noreply.github.com> Date: Wed, 14 Feb 2024 07:21:30 -0600 Subject: [PATCH] VMRM (#4994) - Add VM no storage to deployment type - Add virtual optical device - Update err msg - Add missing change - Add isWaitForPIInstanceShutoff - Modify update vopt --- ibm/service/power/ibm_pi_constants.go | 2 + ibm/service/power/resource_ibm_pi_instance.go | 108 +++++++++++++++--- .../power/resource_ibm_pi_instance_test.go | 91 ++++----------- website/docs/r/pi_instance.html.markdown | 3 +- 4 files changed, 119 insertions(+), 85 deletions(-) diff --git a/ibm/service/power/ibm_pi_constants.go b/ibm/service/power/ibm_pi_constants.go index c7908514914..cd5d3ce9b36 100644 --- a/ibm/service/power/ibm_pi_constants.go +++ b/ibm/service/power/ibm_pi_constants.go @@ -401,4 +401,6 @@ const ( PIWorkspaceDatacenter = "pi_datacenter" PIWorkspaceResourceGroup = "pi_resource_group_id" PIWorkspacePlan = "pi_plan" + + PIVirtualOpticalDevice = "pi_virtual_optical_device" ) diff --git a/ibm/service/power/resource_ibm_pi_instance.go b/ibm/service/power/resource_ibm_pi_instance.go index 882583b071a..0d6c4e8590d 100644 --- a/ibm/service/power/resource_ibm_pi_instance.go +++ b/ibm/service/power/resource_ibm_pi_instance.go @@ -254,10 +254,11 @@ func ResourceIBMPIInstance() *schema.Resource { Description: "Memory size", }, PIInstanceDeploymentType: { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Description: "Custom Deployment Type Information", + Type: schema.TypeString, + ForceNew: true, + Optional: true, + ValidateFunc: validate.ValidateAllowedStringValues([]string{"EPIC", "VMNoStorage"}), + Description: "Custom Deployment Type Information", }, PISAPInstanceProfileID: { Type: schema.TypeString, @@ -271,6 +272,12 @@ func ResourceIBMPIInstance() *schema.Resource { Optional: true, Description: "Custom SAP Deployment Type Information", }, + PIVirtualOpticalDevice: { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.ValidateAllowedStringValues([]string{"attach"}), + Description: "Virtual Machine's Cloud Initialization Virtual Optical Device", + }, helpers.PIInstanceSystemType: { Type: schema.TypeString, ForceNew: true, @@ -379,10 +386,18 @@ func resourceIBMPIInstanceCreate(ctx context.Context, d *schema.ResourceData, me d.SetId(fmt.Sprintf("%s/%s", cloudInstanceID, *(*pvmList)[0].PvmInstanceID)) for _, s := range *pvmList { - _, err = isWaitForPIInstanceAvailable(ctx, client, *s.PvmInstanceID, instanceReadyStatus) - if err != nil { - return diag.FromErr(err) + if dt, ok := d.GetOk(PIInstanceDeploymentType); ok && dt.(string) == "VMNoStorage" { + _, err = isWaitForPIInstanceShutoff(ctx, client, *s.PvmInstanceID, instanceReadyStatus) + if err != nil { + return diag.FromErr(err) + } + } else { + _, err = isWaitForPIInstanceAvailable(ctx, client, *s.PvmInstanceID, instanceReadyStatus) + if err != nil { + return diag.FromErr(err) + } } + } // If Storage Pool Affinity is given as false we need to update the vm instance. @@ -401,6 +416,20 @@ func resourceIBMPIInstanceCreate(ctx context.Context, d *schema.ResourceData, me } } } + // If virtual optical device provided then update cloud initialization + if vod, ok := d.GetOk(PIVirtualOpticalDevice); ok { + for _, s := range *pvmList { + body := &models.PVMInstanceUpdate{ + CloudInitialization: &models.CloudInitialization{ + VirtualOpticalDevice: vod.(string), + }, + } + _, err = client.Update(*s.PvmInstanceID, body) + if err != nil { + return diag.FromErr(err) + } + } + } return resourceIBMPIInstanceRead(ctx, d, meta) @@ -431,7 +460,7 @@ func resourceIBMPIInstanceRead(ctx context.Context, d *schema.ResourceData, meta d.Set(helpers.PIInstanceProcType, powervmdata.ProcType) d.Set("min_processors", powervmdata.Minproc) d.Set(helpers.PIInstanceProgress, powervmdata.Progress) - if powervmdata.StorageType != nil { + if powervmdata.StorageType != nil && *powervmdata.StorageType != "" { d.Set(helpers.PIInstanceStorageType, powervmdata.StorageType) } d.Set(PIInstanceStoragePool, powervmdata.StoragePool) @@ -473,9 +502,7 @@ func resourceIBMPIInstanceRead(ctx context.Context, d *schema.ResourceData, meta d.Set("max_memory", powervmdata.Maxmem) d.Set("pin_policy", powervmdata.PinPolicy) d.Set("operating_system", powervmdata.OperatingSystem) - if powervmdata.OsType != nil { - d.Set("os_type", powervmdata.OsType) - } + d.Set("os_type", powervmdata.OsType) if powervmdata.Health != nil { d.Set("health_status", powervmdata.Health.Status) @@ -486,7 +513,7 @@ func resourceIBMPIInstanceRead(ctx context.Context, d *schema.ResourceData, meta d.Set("min_virtual_cores", powervmdata.VirtualCores.Min) } d.Set(helpers.PIInstanceLicenseRepositoryCapacity, powervmdata.LicenseRepositoryCapacity) - d.Set(PIInstanceDeploymentType, powervmdata.DeploymentType) + return nil } @@ -522,13 +549,17 @@ func resourceIBMPIInstanceUpdate(ctx context.Context, d *schema.ResourceData, me } cores_enabled := checkCloudInstanceCapability(cloudInstance, CUSTOM_VIRTUAL_CORES) - if d.HasChange(helpers.PIInstanceName) { - body := &models.PVMInstanceUpdate{ - ServerName: name, + if d.HasChanges(helpers.PIInstanceName, PIVirtualOpticalDevice) { + body := &models.PVMInstanceUpdate{} + if d.HasChange(helpers.PIInstanceName) { + body.ServerName = name + } + if d.HasChange(PIVirtualOpticalDevice) { + body.CloudInitialization.VirtualOpticalDevice = d.Get(PIVirtualOpticalDevice).(string) } _, err = client.Update(instanceID, body) if err != nil { - return diag.Errorf("failed to update the lpar with the change for name: %v", err) + return diag.Errorf("failed to update the lpar: %v", err) } _, err = isWaitForPIInstanceAvailable(ctx, client, instanceID, "OK") if err != nil { @@ -741,7 +772,6 @@ func resourceIBMPIInstanceUpdate(ctx context.Context, d *schema.ResourceData, me } } } - return resourceIBMPIInstanceRead(ctx, d, meta) } @@ -809,7 +839,7 @@ func isWaitForPIInstanceAvailable(ctx context.Context, client *st.IBMPIInstanceC stateConf := &resource.StateChangeConf{ Pending: []string{"PENDING", helpers.PIInstanceBuilding, helpers.PIInstanceHealthWarning}, - Target: []string{helpers.PIInstanceAvailable, helpers.PIInstanceHealthOk, "ERROR", ""}, + Target: []string{helpers.PIInstanceAvailable, helpers.PIInstanceHealthOk, "ERROR", "", "SHUTOFF"}, Refresh: isPIInstanceRefreshFunc(client, id, instanceReadyStatus), Delay: 30 * time.Second, MinTimeout: queryTimeOut, @@ -843,6 +873,48 @@ func isPIInstanceRefreshFunc(client *st.IBMPIInstanceClient, id, instanceReadySt } } +func isWaitForPIInstanceShutoff(ctx context.Context, client *st.IBMPIInstanceClient, id string, instanceReadyStatus string) (interface{}, error) { + log.Printf("Waiting for PIInstance (%s) to be shutoff and health active ", id) + + queryTimeOut := activeTimeOut + if instanceReadyStatus == helpers.PIInstanceHealthWarning { + queryTimeOut = warningTimeOut + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{StatusPending, helpers.PIInstanceBuilding, helpers.PIInstanceHealthWarning}, + Target: []string{helpers.PIInstanceHealthOk, StatusError, "", StatusShutoff}, + Refresh: isPIInstanceShutoffRefreshFunc(client, id, instanceReadyStatus), + Delay: 30 * time.Second, + MinTimeout: queryTimeOut, + Timeout: 120 * time.Minute, + } + + return stateConf.WaitForStateContext(ctx) +} +func isPIInstanceShutoffRefreshFunc(client *st.IBMPIInstanceClient, id, instanceReadyStatus string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + + pvm, err := client.Get(id) + if err != nil { + return nil, "", err + } + if *pvm.Status == StatusShutoff && (pvm.Health.Status == instanceReadyStatus || pvm.Health.Status == helpers.PIInstanceHealthOk) { + return pvm, StatusShutoff, nil + } + if *pvm.Status == StatusError { + if pvm.Fault != nil { + err = fmt.Errorf("failed to create the lpar: %s", pvm.Fault.Message) + } else { + err = fmt.Errorf("failed to create the lpar") + } + return pvm, *pvm.Status, err + } + + return pvm, helpers.PIInstanceBuilding, nil + } +} + // This function takes the input string and encodes into base64 if isn't already encoded func encodeBase64(userData string) string { _, err := base64.StdEncoding.DecodeString(userData) diff --git a/ibm/service/power/resource_ibm_pi_instance_test.go b/ibm/service/power/resource_ibm_pi_instance_test.go index d28e2943223..8c277feb071 100644 --- a/ibm/service/power/resource_ibm_pi_instance_test.go +++ b/ibm/service/power/resource_ibm_pi_instance_test.go @@ -64,48 +64,7 @@ func testAccCheckIBMPIInstanceConfig(name, instanceHealthStatus string) string { `, acc.Pi_cloud_instance_id, name, acc.Pi_image, acc.Pi_network_name, instanceHealthStatus, acc.PiStorageType) } -func testAccCheckIBMPIInstanceUserDataConfig(name, instanceHealthStatus string) string { - return fmt.Sprintf(` - resource "ibm_pi_key" "key" { - pi_cloud_instance_id = "%[1]s" - pi_key_name = "%[2]s" - pi_ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEArb2aK0mekAdbYdY9rwcmeNSxqVCwez3WZTYEq+1Nwju0x5/vQFPSD2Kp9LpKBbxx3OVLN4VffgGUJznz9DAr7veLkWaf3iwEil6U4rdrhBo32TuDtoBwiczkZ9gn1uJzfIaCJAJdnO80Kv9k0smbQFq5CSb9H+F5VGyFue/iVd5/b30MLYFAz6Jg1GGWgw8yzA4Gq+nO7HtyuA2FnvXdNA3yK/NmrTiPCdJAtEPZkGu9LcelkQ8y90ArlKfjtfzGzYDE4WhOufFxyWxciUePh425J2eZvElnXSdGha+FCfYjQcvqpCVoBAG70U4fJBGjB+HL/GpCXLyiYXPrSnzC9w==" - } - data "ibm_pi_image" "power_image" { - pi_image_name = "%[3]s" - pi_cloud_instance_id = "%[1]s" - } - data "ibm_pi_network" "power_networks" { - pi_cloud_instance_id = "%[1]s" - pi_network_name = "%[4]s" - } - resource "ibm_pi_volume" "power_volume" { - pi_volume_size = 20 - pi_volume_name = "%[2]s" - pi_volume_shareable = true - pi_volume_pool = data.ibm_pi_image.power_image.storage_pool - pi_cloud_instance_id = "%[1]s" - } - resource "ibm_pi_instance" "power_instance" { - pi_memory = "2" - pi_processors = "0.25" - pi_instance_name = "%[2]s" - pi_proc_type = "shared" - pi_image_id = data.ibm_pi_image.power_image.id - pi_sys_type = "s922" - pi_cloud_instance_id = "%[1]s" - pi_storage_pool = data.ibm_pi_image.power_image.storage_pool - pi_health_status = "%[5]s" - pi_volume_ids = [ibm_pi_volume.power_volume.volume_id] - pi_network { - network_id = data.ibm_pi_network.power_networks.id - } - pi_user_data = "this is test user data" - } - `, acc.Pi_cloud_instance_id, name, acc.Pi_image, acc.Pi_network_name, instanceHealthStatus) -} - -func testAccCheckIBMPIInstanceDeploymentTypeConfig(name, instanceHealthStatus string) string { +func testAccCheckIBMPIInstanceDeploymentTypeConfig(name, instanceHealthStatus, epic, systype string) string { return fmt.Sprintf(` resource "ibm_pi_key" "key" { pi_cloud_instance_id = "%[1]s" @@ -127,16 +86,16 @@ func testAccCheckIBMPIInstanceDeploymentTypeConfig(name, instanceHealthStatus st pi_proc_type = "dedicated" pi_image_id = data.ibm_pi_image.power_image.id pi_key_pair_name = ibm_pi_key.key.key_id - pi_sys_type = "e980" + pi_sys_type = "%[7]s" pi_cloud_instance_id = "%[1]s" - pi_storage_type = "tier1" + pi_storage_type = "%[8]s" pi_health_status = "%[5]s" pi_network { network_id = data.ibm_pi_network.power_networks.id } - pi_deployment_type = "EPIC" + pi_deployment_type = "%[6]s" } - `, acc.Pi_cloud_instance_id, name, acc.Pi_image, acc.Pi_network_name, instanceHealthStatus) + `, acc.Pi_cloud_instance_id, name, acc.Pi_image, acc.Pi_network_name, instanceHealthStatus, epic, systype, acc.PiStorageType) } func testAccIBMPIInstanceNetworkConfig(name, privateNetIP string) string { @@ -281,25 +240,6 @@ func TestAccIBMPIInstanceBasic(t *testing.T) { }) } -func TestAccIBMPIInstanceUserData(t *testing.T) { - instanceRes := "ibm_pi_instance.power_instance" - name := fmt.Sprintf("tf-pi-instance-%d", acctest.RandIntRange(10, 100)) - resource.Test(t, resource.TestCase{ - PreCheck: func() { acc.TestAccPreCheck(t) }, - Providers: acc.TestAccProviders, - CheckDestroy: testAccCheckIBMPIInstanceDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCheckIBMPIInstanceUserDataConfig(name, helpers.PIInstanceHealthWarning), - Check: resource.ComposeTestCheckFunc( - testAccCheckIBMPIInstanceExists(instanceRes), - resource.TestCheckResourceAttr(instanceRes, "pi_instance_name", name), - ), - }, - }, - }) -} - func TestAccIBMPIInstanceDeploymentType(t *testing.T) { instanceRes := "ibm_pi_instance.power_instance" name := fmt.Sprintf("tf-pi-instance-%d", acctest.RandIntRange(10, 100)) @@ -309,7 +249,7 @@ func TestAccIBMPIInstanceDeploymentType(t *testing.T) { CheckDestroy: testAccCheckIBMPIInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckIBMPIInstanceDeploymentTypeConfig(name, helpers.PIInstanceHealthWarning), + Config: testAccCheckIBMPIInstanceDeploymentTypeConfig(name, helpers.PIInstanceHealthOk, "EPIC", "e980"), Check: resource.ComposeTestCheckFunc( testAccCheckIBMPIInstanceExists(instanceRes), resource.TestCheckResourceAttr(instanceRes, "pi_instance_name", name), @@ -646,3 +586,22 @@ func testAccCheckIBMPIInstanceStatus(n, status string) resource.TestCheckFunc { return nil } } + +func TestAccIBMPIInstanceDeploymentTypeNoStorage(t *testing.T) { + instanceRes := "ibm_pi_instance.power_instance" + name := fmt.Sprintf("tf-pi-instance-%d", acctest.RandIntRange(10, 100)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMPIInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMPIInstanceDeploymentTypeConfig(name, helpers.PIInstanceHealthOk, "VMNoStorage", "s922"), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMPIInstanceExists(instanceRes), + resource.TestCheckResourceAttr(instanceRes, "pi_instance_name", name), + ), + }, + }, + }) +} diff --git a/website/docs/r/pi_instance.html.markdown b/website/docs/r/pi_instance.html.markdown index 90635be058d..0735e06e121 100644 --- a/website/docs/r/pi_instance.html.markdown +++ b/website/docs/r/pi_instance.html.markdown @@ -64,7 +64,7 @@ Review the argument references that you can specify for your resource. - `pi_anti_affinity_instances` - (Optional, String) List of pvmInstances to base storage anti-affinity policy against; required if requesting `anti-affinity` and `pi_anti_affinity_volumes` is not provided. - `pi_anti_affinity_volumes`- (Optional, String) List of volumes to base storage anti-affinity policy against; required if requesting `anti-affinity` and `pi_anti_affinity_instances` is not provided. - `pi_cloud_instance_id` - (Required, String) The GUID of the service instance associated with an account. -- `pi_deployment_type` - (Optional, String) Custom deployment type; Allowable value: `EPIC`. +- `pi_deployment_type` - (Optional, String) Custom deployment type; Allowable value: `EPIC` or `VMNoStorage`. - `pi_health_status` - (Optional, String) Specifies if Terraform should poll for the health status to be `OK` or `WARNING`. The default value is `OK`. - `pi_image_id` - (Required, String) The ID of the image that you want to use for your Power Systems Virtual Server instance. The image determines the operating system that is installed in your instance. To list available images, run the `ibmcloud pi images` command. - **Note**: only images belonging to your project can be used image for deploying a Power Systems Virtual Server instance. To import an images to your project, see [ibm_pi_image](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/pi_image). @@ -100,6 +100,7 @@ Review the argument references that you can specify for your resource. - Supported SAP system types are (e880/e980). - `pi_user_data` - (Optional, String) The user data `cloud-init` to pass to the instance during creation. It can be a base64 encoded or an unencoded string. If it is an unencoded string, the provider will encode it before it passing it down. - `pi_virtual_cores_assigned` - (Optional, Integer) Specify the number of virtual cores to be assigned. +- `pi_virtual_optical_device` - (Optional, String) Virtual Machine's Cloud Initialization Virtual Optical Device. - `pi_volume_ids` - (Optional, List of String) The list of volume IDs that you want to attach to the instance during creation. ## Attribute reference In addition to all argument reference list, you can access the following attribute reference after your resource is created.