From 3c07f0b11440b8b9b1242ed7ec1a088c6cf305b5 Mon Sep 17 00:00:00 2001 From: ismirlia <90468712+ismirlia@users.noreply.github.com> Date: Wed, 28 Feb 2024 09:32:39 -0600 Subject: [PATCH] Add IBMi Software License field to instance data source and resource (#5082) * Initial ibmi * ibmi initial, and sync with master * sync go.mod and go.sum * sync go.mod and go.sum * Update instance data source with IBMi licenses * Update instance resource with IBMi licenses * Add instance resource IBMi license acceptance test * Add active check for instance update * Fix updating licenses * Update IBMi terraform documentation * Add wait software licenses update function * Update IBMi license test * Update instance data source * Update instance update license markdown * Remove duplicate markdown definitions * Refactor ibmi license code * Refactor ibmi license code * Update IBMi documentation * Fix duplicate constants * Fix formatting * Fix formatting * Fix typo from merge conflict * Refactor variable names * Fix ibmi documentation * Separate ibmi attribute in data source form ibmi argument in resource --------- Co-authored-by: michaelkad Co-authored-by: michael kad --- .../power/data_source_ibm_pi_instance.go | 31 ++++ ibm/service/power/ibm_pi_constants.go | 15 +- ibm/service/power/resource_ibm_pi_instance.go | 163 ++++++++++++++++-- .../power/resource_ibm_pi_instance_test.go | 69 ++++++++ website/docs/d/pi_instance.html.markdown | 7 + website/docs/r/pi_instance.html.markdown | 9 +- 6 files changed, 275 insertions(+), 19 deletions(-) diff --git a/ibm/service/power/data_source_ibm_pi_instance.go b/ibm/service/power/data_source_ibm_pi_instance.go index 31317b561f2..0c77d7a78a0 100644 --- a/ibm/service/power/data_source_ibm_pi_instance.go +++ b/ibm/service/power/data_source_ibm_pi_instance.go @@ -162,6 +162,26 @@ func DataSourceIBMPIInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, + Attr_IBMiCSS: { + Type: schema.TypeBool, + Computed: true, + Description: "IBMi Cloud Storage Solution", + }, + Attr_IBMiPHA: { + Type: schema.TypeBool, + Computed: true, + Description: "IBMi Power High Availability", + }, + Attr_IBMiRDS: { + Type: schema.TypeBool, + Computed: true, + Description: "IBMi Rational Dev Studio", + }, + Attr_IBMiRDSUsers: { + Type: schema.TypeInt, + Computed: true, + Description: "IBMi Rational Dev Studio Number of User Licenses", + }, }, } } @@ -214,5 +234,16 @@ func dataSourceIBMPIInstancesRead(ctx context.Context, d *schema.ResourceData, m d.Set("health_status", powervmdata.Health.Status) } + if powervmdata.SoftwareLicenses != nil { + d.Set(Attr_IBMiCSS, powervmdata.SoftwareLicenses.IbmiCSS) + d.Set(Attr_IBMiPHA, powervmdata.SoftwareLicenses.IbmiPHA) + d.Set(Attr_IBMiRDS, powervmdata.SoftwareLicenses.IbmiRDS) + if *powervmdata.SoftwareLicenses.IbmiRDS { + d.Set(Attr_IBMiRDSUsers, powervmdata.SoftwareLicenses.IbmiRDSUsers) + } else { + d.Set(Attr_IBMiRDSUsers, 0) + } + } + return nil } diff --git a/ibm/service/power/ibm_pi_constants.go b/ibm/service/power/ibm_pi_constants.go index e8c354ef909..11f7a890dc2 100644 --- a/ibm/service/power/ibm_pi_constants.go +++ b/ibm/service/power/ibm_pi_constants.go @@ -278,9 +278,18 @@ const ( Attr_DhcpStatus = "status" // Instance - Arg_PVMInstanceId = "pi_instance_id" - Arg_PVMInstanceActionType = "pi_action" - Arg_PVMInstanceHealthStatus = "pi_health_status" + Arg_PVMInstanceId = "pi_instance_id" + Arg_PVMInstanceActionType = "pi_action" + Arg_PVMInstanceHealthStatus = "pi_health_status" + Arg_IBMiCSS = "pi_ibmi_css" + Arg_IBMiPHA = "pi_ibmi_pha" + Arg_IBMiRDSUsers = "pi_ibmi_rds_users" + Attr_IBMiCSS = "ibmi_css" + Attr_IBMiPHA = "ibmi_pha" + Attr_IBMiRDS = "ibmi_rds" + Attr_IBMiRDSUsers = "ibmi_rds_users" + OS_IBMI = "ibmi" + Arg_PIInstanceSharedProcessorPool = "pi_shared_processor_pool" PVMInstanceHealthOk = "OK" diff --git a/ibm/service/power/resource_ibm_pi_instance.go b/ibm/service/power/resource_ibm_pi_instance.go index 0d6c4e8590d..5fb5f876fd9 100644 --- a/ibm/service/power/resource_ibm_pi_instance.go +++ b/ibm/service/power/resource_ibm_pi_instance.go @@ -353,6 +353,28 @@ func ResourceIBMPIInstance() *schema.Resource { Computed: true, Description: "Minimum Virtual Cores Assigned to the PVMInstance", }, + Arg_IBMiCSS: { + Type: schema.TypeBool, + Optional: true, + Description: "IBMi Cloud Storage Solution", + }, + Arg_IBMiPHA: { + Type: schema.TypeBool, + Optional: true, + Description: "IBMi Power High Availability", + }, + Attr_IBMiRDS: { + Type: schema.TypeBool, + Optional: false, + Required: false, + Computed: true, + Description: "IBMi Rational Dev Studio", + }, + Arg_IBMiRDSUsers: { + Type: schema.TypeInt, + Optional: true, + Description: "IBMi Rational Dev Studio Number of User Licenses", + }, }, } } @@ -513,7 +535,17 @@ 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) + if powervmdata.SoftwareLicenses != nil { + d.Set(Arg_IBMiCSS, powervmdata.SoftwareLicenses.IbmiCSS) + d.Set(Arg_IBMiPHA, powervmdata.SoftwareLicenses.IbmiPHA) + d.Set(Attr_IBMiRDS, powervmdata.SoftwareLicenses.IbmiRDS) + if *powervmdata.SoftwareLicenses.IbmiRDS { + d.Set(Arg_IBMiRDSUsers, powervmdata.SoftwareLicenses.IbmiRDSUsers) + } else { + d.Set(Arg_IBMiRDSUsers, 0) + } + } return nil } @@ -568,7 +600,6 @@ func resourceIBMPIInstanceUpdate(ctx context.Context, d *schema.ResourceData, me } if d.HasChange(helpers.PIInstanceProcType) { - // Stop the lpar if d.Get("status") == "SHUTOFF" { log.Printf("the lpar is in the shutoff state. Nothing to do . Moving on ") @@ -677,7 +708,6 @@ func resourceIBMPIInstanceUpdate(ctx context.Context, d *schema.ResourceData, me // License repository capacity will be updated only if service instance is a vtl instance // might need to check if lrc was set if d.HasChange(helpers.PIInstanceLicenseRepositoryCapacity) { - lrc := d.Get(helpers.PIInstanceLicenseRepositoryCapacity).(int64) body := &models.PVMInstanceUpdate{ LicenseRepositoryCapacity: lrc, @@ -738,7 +768,6 @@ func resourceIBMPIInstanceUpdate(ctx context.Context, d *schema.ResourceData, me } if d.HasChange(helpers.PIPlacementGroupID) { - pgClient := st.NewIBMPIPlacementGroupClient(ctx, sess, cloudInstanceID) oldRaw, newRaw := d.GetChange(helpers.PIPlacementGroupID) @@ -772,8 +801,37 @@ func resourceIBMPIInstanceUpdate(ctx context.Context, d *schema.ResourceData, me } } } - return resourceIBMPIInstanceRead(ctx, d, meta) + if d.HasChanges(Arg_IBMiCSS, Arg_IBMiPHA, Arg_IBMiRDSUsers) { + if d.Get("status") == "ACTIVE" { + log.Printf("the lpar is in the Active state, continuing with update") + } else { + _, err = isWaitForPIInstanceAvailable(ctx, client, instanceID, "OK") + if err != nil { + return diag.FromErr(err) + } + } + + sl := &models.SoftwareLicenses{} + sl.IbmiCSS = flex.PtrToBool(d.Get(Arg_IBMiCSS).(bool)) + sl.IbmiPHA = flex.PtrToBool(d.Get(Arg_IBMiPHA).(bool)) + ibmrdsUsers := d.Get(Arg_IBMiRDSUsers).(int) + if ibmrdsUsers < 0 { + return diag.Errorf("request with IBMi Rational Dev Studio property requires IBMi Rational Dev Studio number of users") + } + sl.IbmiRDS = flex.PtrToBool(ibmrdsUsers > 0) + sl.IbmiRDSUsers = int64(ibmrdsUsers) + updatebody := &models.PVMInstanceUpdate{SoftwareLicenses: sl} + _, err = client.Update(instanceID, updatebody) + if err != nil { + return diag.FromErr(err) + } + _, err = isWaitForPIInstanceSoftwareLicenses(ctx, client, instanceID, sl) + if err != nil { + return diag.FromErr(err) + } + } + return resourceIBMPIInstanceRead(ctx, d, meta) } func resourceIBMPIInstanceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -873,6 +931,59 @@ func isPIInstanceRefreshFunc(client *st.IBMPIInstanceClient, id, instanceReadySt } } +func isWaitForPIInstanceSoftwareLicenses(ctx context.Context, client *st.IBMPIInstanceClient, id string, softwareLicenses *models.SoftwareLicenses) (interface{}, error) { + log.Printf("Waiting for PIInstance Software Licenses (%s) to be updated ", id) + + queryTimeOut := activeTimeOut + + stateConf := &resource.StateChangeConf{ + Pending: []string{"notdone"}, + Target: []string{"done"}, + Refresh: isPIInstanceSoftwareLicensesRefreshFunc(client, id, softwareLicenses), + Delay: 90 * time.Second, + MinTimeout: queryTimeOut, + Timeout: 120 * time.Minute, + } + + return stateConf.WaitForStateContext(ctx) +} + +func isPIInstanceSoftwareLicensesRefreshFunc(client *st.IBMPIInstanceClient, id string, softwareLicenses *models.SoftwareLicenses) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + + pvm, err := client.Get(id) + if err != nil { + return nil, "", err + } + + // Check that each software license we modified has been updated + if softwareLicenses.IbmiCSS != nil { + if *softwareLicenses.IbmiCSS != *pvm.SoftwareLicenses.IbmiCSS { + return pvm, "notdone", nil + } + } + + if softwareLicenses.IbmiPHA != nil { + if *softwareLicenses.IbmiPHA != *pvm.SoftwareLicenses.IbmiPHA { + return pvm, "notdone", nil + } + } + + if softwareLicenses.IbmiRDS != nil { + // If the update set IBMiRDS to false, don't check IBMiRDSUsers as it will be updated on the terraform side on the read + if !*softwareLicenses.IbmiRDS { + if *softwareLicenses.IbmiRDS != *pvm.SoftwareLicenses.IbmiRDS { + return pvm, "notdone", nil + } + } else if (*softwareLicenses.IbmiRDS != *pvm.SoftwareLicenses.IbmiRDS) || (softwareLicenses.IbmiRDSUsers != pvm.SoftwareLicenses.IbmiRDSUsers) { + return pvm, "notdone", nil + } + } + + return pvm, "done", nil + } +} + 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) @@ -1336,18 +1447,15 @@ func createPVMInstance(d *schema.ResourceData, client *st.IBMPIInstanceClient, i if spp, ok := d.GetOk(Arg_PIInstanceSharedProcessorPool); ok { body.SharedProcessorPool = spp.(string) } - - if lrc, ok := d.GetOk(helpers.PIInstanceLicenseRepositoryCapacity); ok { - // check if using vtl image - // check if vtl image is stock image - imageData, err := imageClient.GetStockImage(imageid) + imageData, err := imageClient.GetStockImage(imageid) + if err != nil { + // check if vtl image is cloud instance image + imageData, err = imageClient.Get(imageid) if err != nil { - // check if vtl image is cloud instance image - imageData, err = imageClient.Get(imageid) - if err != nil { - return nil, fmt.Errorf("image doesn't exist. %e", err) - } + return nil, fmt.Errorf("image doesn't exist. %e", err) } + } + if lrc, ok := d.GetOk(helpers.PIInstanceLicenseRepositoryCapacity); ok { if imageData.Specifications.ImageType == "stock-vtl" { body.LicenseRepositoryCapacity = int64(lrc.(int)) @@ -1356,6 +1464,31 @@ func createPVMInstance(d *schema.ResourceData, client *st.IBMPIInstanceClient, i } } + if imageData.Specifications.OperatingSystem == OS_IBMI { + // Default value + falseBool := false + sl := &models.SoftwareLicenses{ + IbmiCSS: &falseBool, + IbmiPHA: &falseBool, + IbmiRDS: &falseBool, + IbmiRDSUsers: 0, + } + if ibmiCSS, ok := d.GetOk(Arg_IBMiCSS); ok { + sl.IbmiCSS = flex.PtrToBool(ibmiCSS.(bool)) + } + if ibmiPHA, ok := d.GetOk(Arg_IBMiPHA); ok { + sl.IbmiPHA = flex.PtrToBool(ibmiPHA.(bool)) + } + if ibmrdsUsers, ok := d.GetOk(Arg_IBMiRDSUsers); ok { + if ibmrdsUsers.(int) < 0 { + return nil, fmt.Errorf("request with IBMi Rational Dev Studio property requires IBMi Rational Dev Studio number of users") + } + sl.IbmiRDS = flex.PtrToBool(ibmrdsUsers.(int) > 0) + sl.IbmiRDSUsers = int64(ibmrdsUsers.(int)) + } + body.SoftwareLicenses = sl + } + pvmList, err := client.Create(body) if err != nil { diff --git a/ibm/service/power/resource_ibm_pi_instance_test.go b/ibm/service/power/resource_ibm_pi_instance_test.go index 8c277feb071..4711fd423b3 100644 --- a/ibm/service/power/resource_ibm_pi_instance_test.go +++ b/ibm/service/power/resource_ibm_pi_instance_test.go @@ -98,6 +98,41 @@ func testAccCheckIBMPIInstanceDeploymentTypeConfig(name, instanceHealthStatus, e `, acc.Pi_cloud_instance_id, name, acc.Pi_image, acc.Pi_network_name, instanceHealthStatus, epic, systype, acc.PiStorageType) } +func testAccCheckIBMPIInstanceIBMiLicense(name, instanceHealthStatus string, IBMiCSS bool, IBMiRDSUsers int) string { + return fmt.Sprintf(` + data "ibm_pi_image" "power_image" { + pi_cloud_instance_id = "%[1]s" + pi_image_name = "%[3]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_cloud_instance_id = "%[1]s" + pi_volume_size = 1 + pi_volume_name = "%[2]s" + pi_volume_type = "tier3" + } + 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_ibmi_css = %[6]t + pi_ibmi_rds_users = %[7]d + }`, acc.Pi_cloud_instance_id, name, acc.Pi_image, acc.Pi_network_name, instanceHealthStatus, IBMiCSS, IBMiRDSUsers) +} + func testAccIBMPIInstanceNetworkConfig(name, privateNetIP string) string { return fmt.Sprintf(` resource "ibm_pi_key" "key" { @@ -259,6 +294,40 @@ func TestAccIBMPIInstanceDeploymentType(t *testing.T) { }) } +func TestAccIBMPIInstanceIBMiLicense(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: testAccCheckIBMPIInstanceIBMiLicense(name, helpers.PIInstanceHealthOk, true, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMPIInstanceExists(instanceRes), + resource.TestCheckResourceAttr(instanceRes, "pi_instance_name", name), + resource.TestCheckResourceAttr(instanceRes, "status", "ACTIVE"), + resource.TestCheckResourceAttr(instanceRes, "pi_ibmi_css", "true"), + resource.TestCheckResourceAttr(instanceRes, "pi_ibmi_rds", "true"), + resource.TestCheckResourceAttr(instanceRes, "pi_ibmi_rds_users", "2"), + ), + }, + { + Config: testAccCheckIBMPIInstanceIBMiLicense(name, helpers.PIInstanceHealthOk, false, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMPIInstanceExists(instanceRes), + testAccCheckIBMPIInstanceStatus(instanceRes, "ACTIVE"), + resource.TestCheckResourceAttr(instanceRes, "pi_instance_name", name), + resource.TestCheckResourceAttr(instanceRes, "pi_ibmi_css", "false"), + resource.TestCheckResourceAttr(instanceRes, "pi_ibmi_rds", "false"), + resource.TestCheckResourceAttr(instanceRes, "pi_ibmi_rds_users", "0"), + ), + }, + }, + }) +} + func TestAccIBMPIInstanceNetwork(t *testing.T) { instanceRes := "ibm_pi_instance.power_instance" name := fmt.Sprintf("tf-pi-instance-%d", acctest.RandIntRange(10, 100)) diff --git a/website/docs/d/pi_instance.html.markdown b/website/docs/d/pi_instance.html.markdown index bfef1793469..590cc12fbd7 100644 --- a/website/docs/d/pi_instance.html.markdown +++ b/website/docs/d/pi_instance.html.markdown @@ -46,6 +46,12 @@ In addition to all argument reference list, you can access the following attribu - `deployment_type` - (String) The custom deployment type. - `health_status` - (String) The health of the instance. + +**Notes** IBMi software licenses for IBMi virtual server instances -- only for IBMi instances +- `ibmi_css` - (Boolean) IBMi Cloud Storage Solution. +- `ibmi_pha` - (Boolean) IBMi Power High Availability. +- `ibmi_rds` - (Boolean) IBMi Rational Dev Studio. +- `ibmi_rds_users` - (Integer) IBMi Rational Dev Studio Number of User Licenses. - `id` - (String) The unique identifier of the instance. - `license_repository_capacity` - The VTL license repository capacity TB value. Only available with VTL instances. - `memory` - (Float) The amount of memory that is allocated to the instance. @@ -64,6 +70,7 @@ In addition to all argument reference list, you can access the following attribu - `network_id` - (String) The network ID of the instance. - `network_name` - (String) The network name of the instance. - `type` - (String) The type of the network. + - `placement_group_id`- (String) The ID of the placement group that the instance is a member. - `processors` - (Float) The number of processors that are allocated to the instance. - `proctype` - (String) The procurement type of the instance. Supported values are `shared` and `dedicated`. diff --git a/website/docs/r/pi_instance.html.markdown b/website/docs/r/pi_instance.html.markdown index 0735e06e121..1b79b56f623 100644 --- a/website/docs/r/pi_instance.html.markdown +++ b/website/docs/r/pi_instance.html.markdown @@ -66,6 +66,11 @@ Review the argument references that you can specify for your resource. - `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` 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`. + +**Notes** Ibmi software licenses for IBMi virtual server instances -- only for IBMi instances. Default to `false` and `0` if no values provided +- `pi_ibmi_css` - (Optional, Boolean) IBMi Cloud Storage Solution. +- `pi_ibmi_pha` - (Optional, Boolean) IBMi Power High Availability. +- `pi_ibmi_rds_users` - (Optional, Integer) IBMi Rational Dev Studio Number of User Licenses. - `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). - `pi_instance_name` - (Required, String) The name of the Power Systems Virtual Server instance. @@ -93,7 +98,7 @@ Review the argument references that you can specify for your resource. - `pi_sap_deployment_type` - (Optional, String) Custom SAP deployment type information (For Internal Use Only). - `pi_shared_processor_pool` - (Optional, String) The shared processor pool for instance deployment. Conflicts with `pi_sap_profile_id`. - `pi_storage_pool` - (Optional, String) Storage Pool for server deployment; if provided then `pi_affinity_policy` will be ignored; Only valid when you deploy one of the IBM supplied stock images. Storage pool for a custom image (an imported image or an image that is created from a VM capture) defaults to the storage pool the image was created in. -- `pi_storage_pool_affinity` - (Optional, Bool) Indicates if all volumes attached to the server must reside in the same storage pool. The default value is `true`. To attach data volumes from a different storage pool (mixed storage) set to `false` and use `pi_volume_attach` resource. Once set to `false`, cannot be set back to `true` unless all volumes attached reside in the same storage type and pool. +- `pi_storage_pool_affinity` - (Optional, Boolean) Indicates if all volumes attached to the server must reside in the same storage pool. The default value is `true`. To attach data volumes from a different storage pool (mixed storage) set to `false` and use `pi_volume_attach` resource. Once set to `false`, cannot be set back to `true` unless all volumes attached reside in the same storage type and pool. - `pi_storage_type` - (Optional, String) - Storage type for server deployment; If storage type is not provided the storage type will default to `tier3`. - `pi_storage_connection` - (Optional, String) - Storage Connectivity Group (SCG) for server deployment. Only supported value is `vSCSI`. - `pi_sys_type` - (Optional, String) The type of system on which to create the VM (s922/e880/e980/s1022). @@ -102,10 +107,12 @@ Review the argument references that you can specify for your resource. - `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. - `health_status` - (String) The health status of the VM. +- `ibmi_rds` - (Boolean) IBMi Rational Dev Studio. - `id` - (String) The unique identifier of the instance. The ID is composed of `/`. - `instance_id` - (String) The unique identifier of the instance. - `max_processors`- (Float) The maximum number of processors that can be allocated to the instance with shutting down or rebooting the `LPAR`.