From f2750abb057aa2fd21c7d98531f35854e4b53d68 Mon Sep 17 00:00:00 2001 From: michaelkad Date: Mon, 27 Nov 2023 10:57:51 -0600 Subject: [PATCH] VMRM - 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 | 106 +++++++++++++++--- .../power/resource_ibm_pi_instance_test.go | 91 +++++---------- website/docs/r/pi_instance.html.markdown | 3 +- 4 files changed, 118 insertions(+), 84 deletions(-) diff --git a/ibm/service/power/ibm_pi_constants.go b/ibm/service/power/ibm_pi_constants.go index b4f18b330cd..4ba0bef6fc2 100644 --- a/ibm/service/power/ibm_pi_constants.go +++ b/ibm/service/power/ibm_pi_constants.go @@ -180,4 +180,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 37236ed8aed..2603a5a8c5c 100644 --- a/ibm/service/power/resource_ibm_pi_instance.go +++ b/ibm/service/power/resource_ibm_pi_instance.go @@ -260,9 +260,10 @@ func ResourceIBMPIInstance() *schema.Resource { Description: "Memory size", }, PIInstanceDeploymentType: { - Type: schema.TypeString, - Optional: true, - Description: "Custom Deployment Type Information", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.ValidateAllowedStringValues([]string{"EPIC", "VMNoStorage"}), + Description: "Custom Deployment Type Information", }, PISAPInstanceProfileID: { Type: schema.TypeString, @@ -275,6 +276,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, Optional: true, @@ -385,10 +392,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. @@ -407,6 +422,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) @@ -440,7 +469,7 @@ func resourceIBMPIInstanceRead(ctx context.Context, d *schema.ResourceData, meta } 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) @@ -482,9 +511,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) @@ -495,7 +522,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 } @@ -531,13 +558,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 { @@ -754,7 +785,6 @@ func resourceIBMPIInstanceUpdate(ctx context.Context, d *schema.ResourceData, me } } } - return resourceIBMPIInstanceRead(ctx, d, meta) } @@ -822,7 +852,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, @@ -856,6 +886,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 429320d53d6..ca87736584b 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). @@ -101,6 +101,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.