diff --git a/ibm/service/power/data_source_ibm_pi_instance.go b/ibm/service/power/data_source_ibm_pi_instance.go index 31317b561f..0c77d7a78a 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 cd5d3ce9b3..c252378050 100644 --- a/ibm/service/power/ibm_pi_constants.go +++ b/ibm/service/power/ibm_pi_constants.go @@ -277,9 +277,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 0d6c4e8590..5fb5f876fd 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 8c277feb07..4711fd423b 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 bfef179346..590cc12fbd 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 0735e06e12..1b79b56f62 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`.