From a8c07e0c752fa94ff9896e54c514136b5e0ee3b4 Mon Sep 17 00:00:00 2001 From: hkantare Date: Tue, 28 Sep 2021 17:28:49 +0530 Subject: [PATCH] Support for classic reserved capacity and instance --- ...ta_source_ibm_compute_reserved_capacity.go | 130 ++++++++ ...urce_ibm_compute_reserved_capacity_test.go | 60 ++++ ibm/provider.go | 2 + ibm/resource_ibm_compute_autoscale_group.go | 3 + ibm/resource_ibm_compute_reserved_capacity.go | 286 ++++++++++++++++++ ...urce_ibm_compute_reserved_capacity_test.go | 143 +++++++++ ibm/resource_ibm_compute_vm_instance.go | 126 +++++++- .../r/compute_reserved_capacity.html.markdown | 57 ++++ .../docs/r/compute_vm_instance.html.markdown | 5 +- 9 files changed, 808 insertions(+), 4 deletions(-) create mode 100644 ibm/data_source_ibm_compute_reserved_capacity.go create mode 100644 ibm/data_source_ibm_compute_reserved_capacity_test.go create mode 100644 ibm/resource_ibm_compute_reserved_capacity.go create mode 100644 ibm/resource_ibm_compute_reserved_capacity_test.go create mode 100644 website/docs/r/compute_reserved_capacity.html.markdown diff --git a/ibm/data_source_ibm_compute_reserved_capacity.go b/ibm/data_source_ibm_compute_reserved_capacity.go new file mode 100644 index 00000000000..2e96a210e12 --- /dev/null +++ b/ibm/data_source_ibm_compute_reserved_capacity.go @@ -0,0 +1,130 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "regexp" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/softlayer/softlayer-go/datatypes" + "github.com/softlayer/softlayer-go/filter" + "github.com/softlayer/softlayer-go/services" +) + +func dataSourceIBMComputeReservedCapacity() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIBMComputeReservedCapacityRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of reserved instance", + }, + + "datacenter": { + Type: schema.TypeString, + Computed: true, + Description: "Dataceneter name", + }, + + "pod": { + Type: schema.TypeString, + Computed: true, + Description: "Pod name", + }, + + "instances": { + Type: schema.TypeString, + Computed: true, + Description: "no of the instances", + }, + + "flavor": { + Type: schema.TypeString, + Computed: true, + Description: "flavor of the reserved group", + }, + + "virtual_guests": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeInt, + Computed: true, + }, + "domain": { + Type: schema.TypeString, + Computed: true, + }, + "hostname": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceIBMComputeReservedCapacityRead(d *schema.ResourceData, meta interface{}) error { + sess := meta.(ClientSession).SoftLayerSession() + service := services.GetAccountService(sess) + + name := d.Get("name").(string) + + groups, err := service. + Filter(filter.Build(filter.Path("reservedCapacityGroups.name").Eq(name))). + Mask("id,name,instancesCount,backendRouter[hostname,datacenter[name]],occupiedInstances[guest[id,domain,hostname]],instances[billingItem[item[keyName]]]").GetReservedCapacityGroups() + + if err != nil { + return fmt.Errorf("Error retrieving placement group: %s", err) + } + + grps := []datatypes.Virtual_ReservedCapacityGroup{} + for _, g := range groups { + if name == *g.Name { + grps = append(grps, g) + + } + } + + if len(grps) == 0 { + return fmt.Errorf("No reserved capacity found with name [%s]", name) + } + + var grp datatypes.Virtual_ReservedCapacityGroup + + grp = grps[0] + + d.SetId(fmt.Sprintf("%d", *grp.Id)) + d.Set("name", grp.Name) + d.Set("datacenter", grp.BackendRouter.Datacenter.Name) + pod := strings.SplitAfter(*grp.BackendRouter.Hostname, ".")[0] + r, _ := regexp.Compile("[0-9]{2}") + pod = "pod" + r.FindString(pod) + d.Set("pod", pod) + d.Set("instances", grp.ModifyDate) + d.Set("flavor", grp.Instances[0].BillingItem.Item.KeyName) + + vgs := make([]map[string]interface{}, len(grp.OccupiedInstances)) + for i, vg := range grp.OccupiedInstances { + if vg.Guest != nil { + v := make(map[string]interface{}) + v["id"] = *vg.Guest.Id + v["domain"] = *vg.Guest.Domain + v["hostname"] = *vg.Guest.Hostname + vgs[i] = v + } + + } + d.Set("virtual_guests", vgs) + + return nil +} diff --git a/ibm/data_source_ibm_compute_reserved_capacity_test.go b/ibm/data_source_ibm_compute_reserved_capacity_test.go new file mode 100644 index 00000000000..62090eb3739 --- /dev/null +++ b/ibm/data_source_ibm_compute_reserved_capacity_test.go @@ -0,0 +1,60 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIBMComputeReservedCapacityDataSource_Basic(t *testing.T) { + + group1 := fmt.Sprintf("%s%s", "tfuatreservedcapacity", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + //CheckDestroy: testAccCheckIBMComputeReservedCapacityDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckIBMComputeReservedCapacitydsConfig(group1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "ibm_compute_reserved_capacity.reservedcapacity", "name", group1), + resource.TestCheckResourceAttr( + "ibm_compute_reserved_capacity.reservedcapacity", "flavor", "B1_4X16_1_YEAR_TERM"), + resource.TestCheckResourceAttr( + "data.ibm_compute_reserved_capacity.reservedcapacityds", "name", group1), + resource.TestCheckResourceAttr( + "data.ibm_compute_reserved_capacity.reservedcapacityds", "flavor", "B1_4X16_1_YEAR_TERM"), + resource.TestCheckResourceAttr( + "data.ibm_compute_reserved_capacity.reservedcapacityds", "datacenter", "dal05"), + resource.TestCheckResourceAttr( + "data.ibm_compute_reserved_capacity.reservedcapacityds", "pod", "pod01"), + resource.TestCheckResourceAttr( + "data.ibm_compute_reserved_capacity.reservedcapacityds", "instances", "1"), + ), + }, + }, + }) +} + +func testAccCheckIBMComputeReservedCapacitydsConfig(name string) string { + return fmt.Sprintf(` +resource "ibm_compute_reserved_capacity" "reservedcapacity" { + name = "%s" + datacenter = "dal05" + pod = "pod01" + flavor = "B1_4X16_1_YEAR_TERM" + instances = "1" +} + +data "ibm_compute_reserved_capacity" "reservedcapacityds" { + name = "${ibm_compute_reserved_capacity.ReservedCapacity.name}" +} +`, name) +} diff --git a/ibm/provider.go b/ibm/provider.go index 55dff1e7e57..d6932420a9b 100644 --- a/ibm/provider.go +++ b/ibm/provider.go @@ -232,6 +232,7 @@ func Provider() *schema.Provider { "ibm_compute_bare_metal": dataSourceIBMComputeBareMetal(), "ibm_compute_image_template": dataSourceIBMComputeImageTemplate(), "ibm_compute_placement_group": dataSourceIBMComputePlacementGroup(), + "ibm_compute_reserved_capacity": dataSourceIBMComputeReservedCapacity(), "ibm_compute_ssh_key": dataSourceIBMComputeSSHKey(), "ibm_compute_vm_instance": dataSourceIBMComputeVmInstance(), "ibm_container_addons": datasourceIBMContainerAddOns(), @@ -536,6 +537,7 @@ func Provider() *schema.Provider { "ibm_compute_dedicated_host": resourceIBMComputeDedicatedHost(), "ibm_compute_monitor": resourceIBMComputeMonitor(), "ibm_compute_placement_group": resourceIBMComputePlacementGroup(), + "ibm_compute_reserved_capacity": resourceIBMComputeReservedCapacity(), "ibm_compute_provisioning_hook": resourceIBMComputeProvisioningHook(), "ibm_compute_ssh_key": resourceIBMComputeSSHKey(), "ibm_compute_ssl_certificate": resourceIBMComputeSSLCertificate(), diff --git a/ibm/resource_ibm_compute_autoscale_group.go b/ibm/resource_ibm_compute_autoscale_group.go index 9ea0944aafa..f5783dac483 100644 --- a/ibm/resource_ibm_compute_autoscale_group.go +++ b/ibm/resource_ibm_compute_autoscale_group.go @@ -138,6 +138,9 @@ func getModifiedVirtualGuestResource() *schema.Resource { r := resourceIBMComputeVmInstance() // wait_time_minutes is only used in virtual_guest resource. delete(r.Schema, "wait_time_minutes") + delete(r.Schema, "reserved_capacity_id") + delete(r.Schema, "reserved_capacity_name") + delete(r.Schema, "reserved_instance_primary_disk") for _, elem := range r.Schema { elem.ForceNew = false diff --git a/ibm/resource_ibm_compute_reserved_capacity.go b/ibm/resource_ibm_compute_reserved_capacity.go new file mode 100644 index 00000000000..ac1a7990e80 --- /dev/null +++ b/ibm/resource_ibm_compute_reserved_capacity.go @@ -0,0 +1,286 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "log" + "net/http" + "regexp" + "strconv" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/softlayer/softlayer-go/filter" + "github.com/softlayer/softlayer-go/helpers/location" + "github.com/softlayer/softlayer-go/helpers/product" + + "github.com/softlayer/softlayer-go/datatypes" + "github.com/softlayer/softlayer-go/services" + "github.com/softlayer/softlayer-go/sl" +) + +func resourceIBMComputeReservedCapacity() *schema.Resource { + return &schema.Resource{ + Create: resourceIBMComputeReservedCapacityCreate, + Read: resourceIBMComputeReservedCapacityRead, + Update: resourceIBMComputeReservedCapacityUpdate, + Delete: resourceIBMComputeReservedCapacityDelete, + Exists: resourceIBMComputeReservedCapacityExists, + Importer: &schema.ResourceImporter{}, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "datacenter": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Dataceneter name", + }, + + "pod": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.TrimSpace(old) == strings.TrimSpace(new) + }, + Description: "Pod name", + }, + + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name", + }, + + "instances": { + Type: schema.TypeInt, + Required: true, + Description: "no of the instances", + }, + + "flavor": { + Type: schema.TypeString, + Required: true, + Description: "flavor of the reserved group", + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Description: "List of tags", + }, + }, + } +} + +func resourceIBMComputeReservedCapacityCreate(d *schema.ResourceData, meta interface{}) error { + sess := meta.(ClientSession).SoftLayerSession() + name := d.Get("name").(string) + datacenter := d.Get("datacenter").(string) + pod := d.Get("pod").(string) + podName := datacenter + "." + pod + PodService := services.GetNetworkPodService(sess) + podMask := `backendRouterId,name` + instances := d.Get("instances").(int) + flavor := d.Get("flavor").(string) + + // 1.Getting the router ID + routerids, err := PodService.Filter(filter.Path("datacenterName").Eq(datacenter).Build()).Mask(podMask).GetAllObjects() + if err != nil { + return fmt.Errorf("Encountered problem trying to get the router ID: %s", err) + } + var routerid int + for _, iterate := range routerids { + if *iterate.Name == podName { + routerid = *iterate.BackendRouterId + } + } + + pkg, err := product.GetPackageByType(sess, "RESERVED_CAPACITY") + if err != nil { + return err + } + + // 2. Get all prices for the package + productItems, err := product.GetPackageProducts(sess, *pkg.Id, productItemMaskWithPriceLocationGroupID) + if err != nil { + return err + } + + priceItems := []datatypes.Product_Item_Price{} + for _, item := range productItems { + if *item.KeyName == flavor { + for _, price := range item.Prices { + if price.LocationGroupId == nil { + priceItem := datatypes.Product_Item_Price{ + Id: price.Id, + } + priceItems = append(priceItems, priceItem) + break + } + } + + } + + } + + // Lookup the data center ID + dc, err := location.GetDatacenterByName(sess, datacenter) + if err != nil { + return fmt.Errorf("No data centers matching %s could be found", datacenter) + } + + productOrderContainer := datatypes.Container_Product_Order_Virtual_ReservedCapacity{ + Container_Product_Order: datatypes.Container_Product_Order{ + PackageId: pkg.Id, + Location: sl.String(strconv.Itoa(*dc.Id)), + Prices: priceItems, + ComplexType: sl.String("SoftLayer_Container_Product_Order_Virtual_ReservedCapacity"), + Quantity: &instances, + }, + Name: sl.String(name), + BackendRouterId: &routerid, + } + log.Println("[INFO] Creating dedicated host") + + //verify order + _, err = services.GetProductOrderService(sess.SetRetries(0)). + VerifyOrder(&productOrderContainer) + if err != nil { + return fmt.Errorf("Error during creation of dedicated host: %s", err) + } + //place order + _, err = services.GetProductOrderService(sess.SetRetries(0)). + PlaceOrder(&productOrderContainer, sl.Bool(false)) + if err != nil { + return fmt.Errorf("Error during creation of dedicated host: %s", err) + } + + // wait for machine availability + reservedCapacity, err := findReservedCapacityByOrderID(name, d, meta) + if err != nil { + return fmt.Errorf( + "Error waiting for dedicated host (%s) to become ready: %s", d.Id(), err) + } + + id := *reservedCapacity.(datatypes.Virtual_ReservedCapacityGroup).Id + d.SetId(fmt.Sprintf("%d", id)) + return resourceIBMComputeReservedCapacityRead(d, meta) +} +func findReservedCapacityByOrderID(name string, r *schema.ResourceData, meta interface{}) (interface{}, error) { + + log.Printf("Waiting for reserved capacity (%s) to have to be provisioned", name) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"retry", "pending"}, + Target: []string{"provisioned"}, + Refresh: func() (interface{}, string, error) { + service := services.GetAccountService(meta.(ClientSession).SoftLayerSession()) + reservedCapacitys, err := service.Filter( + filter.Build( + filter.Path("reservedCapacityGroups.name").Eq(name), + ), + ).Mask("id,createDate").GetReservedCapacityGroups() + if err != nil { + return false, "retry", nil + } + + if len(reservedCapacitys) == 0 || reservedCapacitys[0].CreateDate == nil { + return datatypes.Virtual_ReservedCapacityGroup{}, "pending", nil + } + return reservedCapacitys[0], "provisioned", nil + + }, + Timeout: r.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 1 * time.Minute, + } + + return stateConf.WaitForState() +} + +func resourceIBMComputeReservedCapacityRead(d *schema.ResourceData, meta interface{}) error { + sess := meta.(ClientSession).SoftLayerSession() + service := services.GetVirtualReservedCapacityGroupService(sess) + + rgrpID, _ := strconv.Atoi(d.Id()) + + rgrp, err := service.Id(rgrpID).Mask("id,name,instancesCount,backendRouter[hostname,datacenter[name]],instances[billingItem[item[keyName]]]").GetObject() + if err != nil { + if err, ok := err.(sl.Error); ok { + if err.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + } + return fmt.Errorf("Error retrieving reserved Group: %s", err) + } + + d.Set("name", rgrp.Name) + d.Set("datacenter", rgrp.BackendRouter.Datacenter.Name) + pod := strings.SplitAfter(*rgrp.BackendRouter.Hostname, ".")[0] + r, _ := regexp.Compile("[0-9]{2}") + pod = "pod" + r.FindString(pod) + d.Set("pod", pod) + d.Set("instances", rgrp.InstancesCount) + d.Set("instances", rgrp.Instances[0].BillingItem.Item.KeyName) + + return nil +} + +func resourceIBMComputeReservedCapacityUpdate(d *schema.ResourceData, meta interface{}) error { + sess := meta.(ClientSession).SoftLayerSession() + service := services.GetVirtualReservedCapacityGroupService(sess) + + rgrpID, _ := strconv.Atoi(d.Id()) + + opts := datatypes.Virtual_ReservedCapacityGroup{} + + if d.HasChange("name") { + opts.Name = sl.String(d.Get("name").(string)) + _, err := service.Id(rgrpID).EditObject(&opts) + + if err != nil { + return fmt.Errorf("Error editing Reserved Group: %s", err) + } + } + + return nil +} + +func resourceIBMComputeReservedCapacityExists(d *schema.ResourceData, meta interface{}) (bool, error) { + + sess := meta.(ClientSession).SoftLayerSession() + service := services.GetVirtualReservedCapacityGroupService(sess) + + rgrpID, err := strconv.Atoi(d.Id()) + if err != nil { + return false, fmt.Errorf("Not a valid ID, must be an integer: %s", err) + } + + result, err := service.Id(rgrpID).GetObject() + if err != nil { + if apiErr, ok := err.(sl.Error); ok { + if apiErr.StatusCode == 404 { + return false, nil + } + } + return false, fmt.Errorf("Error communicating with the API: %s", err) + } + return result.Id != nil && *result.Id == rgrpID, nil +} + +func resourceIBMComputeReservedCapacityDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} diff --git a/ibm/resource_ibm_compute_reserved_capacity_test.go b/ibm/resource_ibm_compute_reserved_capacity_test.go new file mode 100644 index 00000000000..d4fcfe96e73 --- /dev/null +++ b/ibm/resource_ibm_compute_reserved_capacity_test.go @@ -0,0 +1,143 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/softlayer/softlayer-go/datatypes" + "github.com/softlayer/softlayer-go/services" +) + +func TestAccIBMComputeReservedCapacity_Basic(t *testing.T) { + var group datatypes.Virtual_ReservedCapacityGroup + + group1 := fmt.Sprintf("%s%s", "tfuatreservedcapacity", acctest.RandString(10)) + group2 := fmt.Sprintf("%s%s", "tfuatreservedcapacity", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIBMComputeReservedCapacityDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckIBMComputeReservedCapacityConfig(group1), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMComputeReservedCapacityExists("ibm_compute_placement_group.placementGroup", &group), + resource.TestCheckResourceAttr( + "ibm_compute_placement_group.placementGroup", "name", group1), + resource.TestCheckResourceAttr( + "ibm_compute_placement_group.placementGroup", "flavor", "B1_4X16_1_YEAR_TERM"), + resource.TestCheckResourceAttr( + "ibm_compute_placement_group.placementGroup", "datacenter", "lon02"), + resource.TestCheckResourceAttr( + "ibm_compute_placement_group.placementGroup", "pod", "pod01"), + ), + }, + + resource.TestStep{ + Config: testAccCheckIBMComputeReservedCapacityUpdate(group2), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMComputeReservedCapacityExists("ibm_compute_placement_group.placementGroup", &group), + resource.TestCheckResourceAttr( + "ibm_compute_placement_group.placementGroup", "name", group2), + resource.TestCheckResourceAttr( + "ibm_compute_placement_group.placementGroup", "flavor", "B1_4X16_1_YEAR_TERM"), + resource.TestCheckResourceAttr( + "ibm_compute_placement_group.placementGroup", "datacenter", "lon02"), + resource.TestCheckResourceAttr( + "ibm_compute_placement_group.placementGroup", "pod", "pod01"), + ), + }, + + resource.TestStep{ + ResourceName: "ibm_compute_placement_group.placementGroup", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckIBMComputeReservedCapacityDestroy(s *terraform.State) error { + service := services.GetVirtualReservedCapacityGroupService(testAccProvider.Meta().(ClientSession).SoftLayerSession()) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "ibm_compute_reserved_capacity" { + continue + } + + reservedcapacityId, _ := strconv.Atoi(rs.Primary.ID) + + // Try to find the provisioning reservedcapacity + _, err := service.Id(reservedcapacityId).GetObject() + + if err == nil { + return fmt.Errorf("Reserved Capacity still exists: %s", rs.Primary.ID) + } else if !strings.Contains(err.Error(), "404") { + return fmt.Errorf("Error waiting for reserved capacity (%s) to be destroyed: %s", rs.Primary.ID, err) + } + } + + return nil +} + +func testAccCheckIBMComputeReservedCapacityExists(n string, reservedcapacity *datatypes.Virtual_ReservedCapacityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + + reservedcapacityId, _ := strconv.Atoi(rs.Primary.ID) + + service := services.GetVirtualReservedCapacityGroupService(testAccProvider.Meta().(ClientSession).SoftLayerSession()) + foundreservedcapacity, err := service.Id(reservedcapacityId).GetObject() + + if err != nil { + return err + } + + if strconv.Itoa(int(*foundreservedcapacity.Id)) != rs.Primary.ID { + return fmt.Errorf("Record not found") + } + + *reservedcapacity = foundreservedcapacity + + return nil + } +} + +func testAccCheckIBMComputeReservedCapacityConfig(name string) string { + return fmt.Sprintf(` +resource "ibm_compute_placement_group" "placementGroup" { + datacenter = "lon02" + pod = "pod01" + instances = 6 + name = "%s" + flavor = "B1_4X16_1_YEAR_TERM" +}`, name) +} + +func testAccCheckIBMComputeReservedCapacityUpdate(name string) string { + return fmt.Sprintf(` +resource "ibm_compute_placement_group" "placementGroup" { + datacenter = "lon02" + pod = "pod01" + instances = 6 + name = "%s" + flavor = "B1_4X16_1_YEAR_TERM" +}`, name) +} diff --git a/ibm/resource_ibm_compute_vm_instance.go b/ibm/resource_ibm_compute_vm_instance.go index dcc33f56a8f..05a9adc5e35 100644 --- a/ibm/resource_ibm_compute_vm_instance.go +++ b/ibm/resource_ibm_compute_vm_instance.go @@ -11,6 +11,7 @@ import ( "fmt" "log" "math" + "regexp" "strconv" "strings" "time" @@ -176,7 +177,7 @@ func resourceIBMComputeVmInstance() *schema.Resource { Type: schema.TypeList, Description: "The user provided datacenter options", Optional: true, - ConflictsWith: []string{"datacenter", "public_vlan_id", "private_vlan_id", "placement_group_name", "placement_group_id"}, + ConflictsWith: []string{"datacenter", "public_vlan_id", "private_vlan_id", "placement_group_name", "placement_group_id", "reserved_capacity_id", "reserved_capacity_name"}, Elem: &schema.Schema{Type: schema.TypeMap}, }, @@ -185,7 +186,7 @@ func resourceIBMComputeVmInstance() *schema.Resource { Description: "The placement group name", Optional: true, ForceNew: true, - ConflictsWith: []string{"datacenter_choice", "dedicated_acct_host_only", "dedicated_host_name", "dedicated_host_id", "placement_group_id"}, + ConflictsWith: []string{"datacenter_choice", "dedicated_acct_host_only", "dedicated_host_name", "dedicated_host_id", "placement_group_id", "reserved_capacity_id", "reserved_capacity_name"}, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { _, ok := d.GetOk("placement_group_id") return new == "" && ok @@ -197,13 +198,47 @@ func resourceIBMComputeVmInstance() *schema.Resource { Description: "The placement group id", Optional: true, ForceNew: true, - ConflictsWith: []string{"datacenter_choice", "dedicated_acct_host_only", "dedicated_host_name", "dedicated_host_id", "placement_group_name"}, + ConflictsWith: []string{"datacenter_choice", "dedicated_acct_host_only", "dedicated_host_name", "dedicated_host_id", "placement_group_name", "reserved_capacity_id", "reserved_capacity_name"}, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { _, ok := d.GetOk("placement_group_name") return new == "0" && ok }, }, + "reserved_instance_primary_disk": { + Type: schema.TypeInt, + Description: "The primary disk of reserved instance", + Optional: true, + ForceNew: true, + }, + + "reserved_capacity_id": { + Type: schema.TypeInt, + Description: "The reserved group id", + Optional: true, + ForceNew: true, + RequiredWith: []string{"reserved_instance_primary_disk"}, + ConflictsWith: []string{"datacenter_choice", "dedicated_acct_host_only", "dedicated_host_name", "dedicated_host_id", "placement_group_name", "placement_group_id", "reserved_capacity_name", "flavor_key_name", "cores", "memory"}, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + _, ok := d.GetOk("reserved_capacity_name") + return new == "0" && ok + }, + }, + + "reserved_capacity_name": { + Type: schema.TypeString, + Description: "The reserved group id", + Optional: true, + //Computed: true, + ForceNew: true, + RequiredWith: []string{"reserved_instance_primary_disk"}, + ConflictsWith: []string{"datacenter_choice", "dedicated_acct_host_only", "dedicated_host_name", "dedicated_host_id", "placement_group_name", "placement_group_id", "reserved_capacity_id", "flavor_key_name", "cores", "memory"}, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + _, ok := d.GetOk("reserved_capacity_id") + return new == "" && ok + }, + }, + "flavor_key_name": { Type: schema.TypeString, Optional: true, @@ -714,6 +749,76 @@ func getVirtualGuestTemplateFromResourceData(d *schema.ResourceData, meta interf PostInstallScriptUri: sl.String(d.Get("post_install_script_uri").(string)), } + if reservedGroupID, ok := d.GetOk("reserved_capacity_id"); ok { + if *opts.HourlyBillingFlag || *opts.LocalDiskFlag { + return vms, fmt.Errorf("Unable to provision a reserved instance with a hourly_billing false or local_disk true") + } + grpID := reservedGroupID.(int) + opts.ReservedCapacityGroup = &datatypes.Virtual_ReservedCapacityGroup{ + Id: sl.Int(grpID), + } + service := services.GetVirtualReservedCapacityGroupService(meta.(ClientSession).SoftLayerSession()) + grp, err := service.Id(grpID).Mask("id,instances[id, billingItem[id, item[id,keyName]]]").GetObject() + if err != nil { + return vms, fmt.Errorf("Error looking up reserved capacity: %s", err) + } + capacityFlavor := *grp.Instances[0].BillingItem.Item.KeyName + primaryDisks := d.Get("reserved_instance_primary_disk").(int) + + re := regexp.MustCompile("_\\d_YEAR_TERM") + + split := re.Split(capacityFlavor, -1) + + flavorComponenet := datatypes.Virtual_Guest_SupplementalCreateObjectOptions{ + FlavorKeyName: sl.String(fmt.Sprintf("%sX%s", split[0], strconv.Itoa(primaryDisks))), + } + opts.SupplementalCreateObjectOptions = &flavorComponenet + + } + + if reservedGroupName, ok := d.GetOk("reserved_capacity_name"); ok { + if *opts.HourlyBillingFlag || *opts.LocalDiskFlag { + return vms, fmt.Errorf("Unable to provision a reserved instance with a hourly_billing false or local_disk true") + } + grpName := reservedGroupName.(string) + service := services.GetAccountService(meta.(ClientSession).SoftLayerSession()) + groups, err := service. + Mask("id,name,instances[id,billingItem[id,item[id,keyName]]]"). + Filter(filter.Path("reservedCapacityGroups.name").Eq(grpName).Build()). + GetReservedCapacityGroups() + + if err != nil { + return vms, fmt.Errorf("Error looking up reserved capacity '%s': %s", grpName, err) + } + grps := []datatypes.Virtual_ReservedCapacityGroup{} + for _, g := range groups { + if grpName == *g.Name { + grps = append(grps, g) + + } + } + if len(grps) == 0 { + return vms, fmt.Errorf("Error looking up reserved capacity '%s'", grpName) + } + grp := grps[0] + + opts.ReservedCapacityGroup = &datatypes.Virtual_ReservedCapacityGroup{ + Id: grp.Id, + } + capacityFlavor := *grp.Instances[0].BillingItem.Item.KeyName + primaryDisks := d.Get("reserved_instance_primary_disk").(int) + + re := regexp.MustCompile("_\\d_YEAR_TERM") + + split := re.Split(capacityFlavor, -1) + + flavorComponenet := datatypes.Virtual_Guest_SupplementalCreateObjectOptions{ + FlavorKeyName: sl.String(fmt.Sprintf("%sX%s", split[0], strconv.Itoa(primaryDisks))), + } + opts.SupplementalCreateObjectOptions = &flavorComponenet + + } + if placementGroupID, ok := d.GetOk("placement_group_id"); ok { grpID := placementGroupID.(int) service := services.GetVirtualPlacementGroupService(meta.(ClientSession).SoftLayerSession()) @@ -1096,6 +1201,9 @@ func resourceIBMComputeVmInstanceRead(d *schema.ResourceData, meta interface{}) "notes,userData[value],tagReferences[id,tag[name]]," + "datacenter[id,name,longName]," + "sshKeys,status[keyName,name]," + + "reservedCapacityGroup[id,name]," + + "dedicatedHost[id,name]," + + "placementGroup[id,name]," + "primaryNetworkComponent[networkVlan[id],subnets," + "primaryVersion6IpAddressRecord[subnet,guestNetworkComponentBinding[ipAddressId]]," + "primaryIpAddressRecord[subnet,guestNetworkComponentBinding[ipAddressId]]," + @@ -1133,6 +1241,11 @@ func resourceIBMComputeVmInstanceRead(d *schema.ResourceData, meta interface{}) keyName, ok := sl.GrabOk(result, "BillingItem.OrderItem.Preset.KeyName") if ok { d.Set("flavor_key_name", keyName) + if result.ReservedCapacityGroup != nil { + diskSize := strings.Split(keyName.(string), "X") + size, _ := strconv.Atoi(diskSize[len(diskSize)-1]) + d.Set("reserved_instance_primary_disk", size) + } } if result.BlockDeviceTemplateGroup != nil { @@ -1156,6 +1269,10 @@ func resourceIBMComputeVmInstanceRead(d *schema.ResourceData, meta interface{}) d.Set("placement_group_id", *result.PlacementGroup.Id) d.Set("placement_group_name", *result.PlacementGroup.Name) } + if result.ReservedCapacityGroup != nil { + d.Set("reserved_capacity_id", *result.ReservedCapacityGroup.Id) + d.Set("reserved_capacity_name", *result.ReservedCapacityGroup.Name) + } d.Set( "network_speed", @@ -2073,6 +2190,9 @@ func placeOrder(d *schema.ResourceData, meta interface{}, name string, publicVla if opts.DedicatedHost != nil { template.HostId = opts.DedicatedHost.Id } + if opts.ReservedCapacityGroup != nil { + template.ReservedCapacityId = opts.ReservedCapacityGroup.Id + } guestOrders = append(guestOrders, template) } diff --git a/website/docs/r/compute_reserved_capacity.html.markdown b/website/docs/r/compute_reserved_capacity.html.markdown new file mode 100644 index 00000000000..8813b5c5b1b --- /dev/null +++ b/website/docs/r/compute_reserved_capacity.html.markdown @@ -0,0 +1,57 @@ +--- + +subcategory: "Classic infrastructure" +layout: "ibm" +page_title: "IBM : compute_reserved_capacity" +description: |- + Manages IBM Cloud compute reserved capacity +--- + + +# ibm_compute_reserved_capacity +Create, or updatea reserved capacity for your virtual server instance. With reserved capacity, you can control the physical host your virtual server instance is deployed to. For more information, about the reserved capacity, see [reserved groups](https://cloud.ibm.com/docs/virtual-servers?topic=virtual-servers-about-reserved-virtual-servers). + +**Note** + +For more information, see the [IBM Cloud Classic Infrastructure (SoftLayer) API docs](https://softlayer.github.io/reference/datatypes/SoftLayer_Virtual_PlacementGroup). + +## Example usage + +```terraform +resource "ibm_compute_reserved_capacity" "reserved" { + datacenter = "lon02" + pod = "pod01" + instances = 6 + name = "reservedinstanceterraformupdate" + flavor = "B1_4X16_1_YEAR_TERM" +} +``` + +## Timeouts +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/language/resources/syntax.html) for certain actions: + +- `create`- (Defaults to 10 mins) Used when you create the reserved capacity. + +## Argument reference +Review the argument references that you can specify for your resource. + +- `datacenter` - (Required, Forces new resource, String)The datacenter in which you want to provision the reserved capacity. +- `flavor`- (Required, String) Capacity keyname (C1_2X2_1_YEAR_TERM for example). +- `instances`- (Required, Forces new resource, Integer) Number of VSI instances this capacity reservation can support. +- `name` - (Required, String) The descriptive that is used to identify a reserved capacity. +- `pod` - (Required, Forces new resource, String) The data center pod where you want to create the reserved capacity. + +## Attribute reference +In addition to all argument reference list, you can access the following attribute reference after your resource is created. + +- `id`- (String) The unique identifier of the new reserved capacity. + +## Import + +The `ibm_compute_vm_instance` resource can be imported by using instance ID. + +**Example** + +``` +$ terraform import ibm_compute_vm_instance.example 88205074 +``` \ No newline at end of file diff --git a/website/docs/r/compute_vm_instance.html.markdown b/website/docs/r/compute_vm_instance.html.markdown index c1f6d802a14..cdf793ca56e 100644 --- a/website/docs/r/compute_vm_instance.html.markdown +++ b/website/docs/r/compute_vm_instance.html.markdown @@ -249,7 +249,10 @@ Review the argument references that you can specify for your resource. - `public_subnet` - (Optional, Forces new resource, String) The public subnet for the public network interface of the instance. Accepted values are primary public networks. You can find accepted values in the [subnets doc](https://cloud.ibm.com/classic/network/subnets). - `private_subnet` - (Optional, Forces new resource, String) The private subnet for the private network interface of the instance. Accepted values are primary private networks. You can find accepted values in the [subnets doc](https://cloud.ibm.com/classic/network/subnets). - `public_bandwidth_limited` - (Optional, Forces new resource, Integer) Allowed public network traffic in GB per month. It can be greater than 0 when the server is a monthly based server. Defaults to the smallest available capacity for the public bandwidth are used. **Note** Conflicts with `private_network_only` and `public_bandwidth_unlimited`. -- `public_bandwidth_unlimited` - (Optional, Forces new resource, Bool) Allowed unlimited public network traffic in GB per month for a monthly based server. The `network_speed` should be 100 Mbps. Default value is **false**. **Note** Conflicts with `private_network_only` and `public_bandwidth_limited`. +- `public_bandwidth_unlimited` - (Optional, Forces new resource, Bool) Allowed unlimited public network traffic in GB per month for a monthly based server. The `network_speed` should be 100 Mbps. Default value is **false**. **Note** Conflicts with `private_network_only` and `public_bandwidth_limited`. +- `reserved_capacity_id` - (Optional, Forces new resource, Integer) Reserve capacity Id to provision this instance into. +- `reserved_capacity_name` - (Optional, Forces new resource, String) Reserve capacity name to provision this instance into. +- `reserved_instance_primary_disk` - (Optional, Forces new resource, Integer) Size of the main drive. - `secondary_ip_count` - (Optional, Forces new resource, Integer) Specifies secondary public IPv4 addresses. Accepted values are `4` and `8`. - `ssh_key_ids`- (Optional, Array of integers) The SSH key IDs to install on the computing instance when the instance provisions. **Note** If you don't know the ID(s) for your SSH keys, you can reference your SSH keys by their labels. - `tags` (Optional, Array of Strings) Tags associated with the VM instance. Permitted characters include: A-Z, 0-9, whitespace, `_` (underscore), `- ` (hyphen), `.` (period), and `:` (colon). All other characters are removed.