From 1490c543defb65a10435ca4311adfd62eafa02b3 Mon Sep 17 00:00:00 2001 From: Sergey Shubin svs1370 Date: Mon, 8 Feb 2021 20:17:59 +0300 Subject: [PATCH] End of day commit, interim, no testing yet --- decort/data_source_compute.go | 96 +++++++----- decort/data_source_rg.go | 63 ++++++-- decort/disk_subresource.go | 28 +++- decort/models_api.go | 20 ++- decort/quota_subresource.go | 58 ++++--- decort/resource_resgroup.go | 281 +++++++++++++++++++++++----------- decort/utility_resgroup.go | 8 +- 7 files changed, 374 insertions(+), 180 deletions(-) diff --git a/decort/data_source_compute.go b/decort/data_source_compute.go index d4edb35..c91d382 100644 --- a/decort/data_source_compute.go +++ b/decort/data_source_compute.go @@ -38,7 +38,7 @@ import ( func flattenCompute(d *schema.ResourceData, comp_facts string) error { // NOTE: this function modifies ResourceData argument - as such it should never be called // from resourceComputeExists(...) method - model := MachinesGetResp{} + model := ComputeGetResp{} log.Printf("flattenCompute: ready to unmarshal string %q", comp_facts) err := json.Unmarshal([]byte(comp_facts), &model) if err != nil { @@ -50,11 +50,17 @@ func flattenCompute(d *schema.ResourceData, comp_facts string) error { d.SetId(fmt.Sprintf("%d", model.ID)) d.Set("name", model.Name) d.Set("rgid", model.ResGroupID) + d.Set("rg_name", model.ResGroupName) + d.Set("account_id", model.AccountID) + d.Set("account_name", model.AccountName) + d.Set("arch", model.Arch) d.Set("cpu", model.Cpu) d.Set("ram", model.Ram) - // d.Set("boot_disk", model.BootDisk) + d.Set("boot_disk_size", model.BootDiskSize) d.Set("image_id", model.ImageID) - d.Set("description", model.Description) + d.Set("description", model.Desc) + d.Set("status", model.Status) + d.Set("tech_status", model.TechStatus) bootdisk_map := make(map[string]interface{}) bootdisk_map["size"] = model.BootDisk @@ -131,76 +137,86 @@ func dataSourceCompute() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - Description: "Name of this virtual machine. This parameter is case sensitive.", + Description: "Name of this compute instance. NOTE: this parameter is case sensitive.", }, "rgid": { Type: schema.TypeInt, Required: true, ValidateFunc: validation.IntAtLeast(1), - Description: "ID of the resource group where this virtual machine is located.", + Description: "ID of the resource group where this compute instance is located.", }, - /* - "internal_ip": { + "rg_name": { Type: schema.TypeString, Computed: true, - Description: "Internal IP address of this Compute.", + Description: "Name of the resource group where this compute instance is located.", + }, + + "account_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the account this compute instance belongs to.", + }, + + "account_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the account this compute instance belongs to.", + }, + + "arch": { + Type: schema.TypeString, + Computed: true, + Description: "Hardware architecture of this compute instance.", }, - */ "cpu": { Type: schema.TypeInt, Computed: true, - Description: "Number of CPUs allocated for this virtual machine.", + Description: "Number of CPUs allocated for this compute instance.", }, "ram": { Type: schema.TypeInt, Computed: true, - Description: "Amount of RAM in MB allocated for this virtual machine.", + Description: "Amount of RAM in MB allocated for this compute instance.", }, "image_id": { Type: schema.TypeInt, Computed: true, - Description: "ID of the OS image this virtual machine is based on.", + Description: "ID of the OS image this compute instance is based on.", }, - /* "image_name": { Type: schema.TypeString, Computed: true, - Description: "Name of the OS image this virtual machine is based on.", + Description: "Name of the OS image this compute instance is based on.", }, - */ - "boot_disk": { - Type: schema.TypeList, + "boot_disk_size": { + Type: schema.TypeInt, Computed: true, - MinItems: 1, - Elem: &schema.Resource { - Schema: diskSubresourceSchema(), - }, - Description: "Specification for a boot disk on this virtual machine.", + Description: "This compute instance boot disk size in GB.", }, - "data_disks": { + "disks": { Type: schema.TypeList, Computed: true, Elem: &schema.Resource { - Schema: diskSubresourceSchema(), + Schema: diskSubresourceSchema(), // ID, type, name, size, account ID, SEP ID, SEP type, pool, status, tech status, compute ID, image ID }, - Description: "Specification for data disks on this virtual machine.", + Description: "Detailed specification for all disks attached to this compute instance (including bood disk).", }, "guest_logins": { Type: schema.TypeList, Computed: true, Elem: &schema.Resource { - Schema: loginsSubresourceSchema(), + Schema: guestLoginsSubresourceSchema(), }, - Description: "Specification for guest logins on this virtual machine.", + Description: "Details about the guest OS users provisioned together with this compute instance.", }, "networks": { @@ -224,22 +240,28 @@ func dataSourceCompute() *schema.Resource { "description": { Type: schema.TypeString, Computed: true, - Description: "Description of this virtual machine.", + Description: "User-defined text description of this compute instance.", }, - "user": { - Type: schema.TypeString, - Computed: true, - Description: "Default login name for the guest OS on this virtual machine.", + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current model status of this compute instance.", }, - "password": { - Type: schema.TypeString, - Computed: true, - Sensitive: true, - Description: "Default password for the guest OS login on this virtual machine.", + "tech_status": { + Type: schema.TypeString, + Computed: true, + Description: "Current technical status of this compute instance.", }, + /* + "internal_ip": { + Type: schema.TypeString, + Computed: true, + Description: "Internal IP address of this Compute.", + }, + */ }, } } \ No newline at end of file diff --git a/decort/data_source_rg.go b/decort/data_source_rg.go index 45ef3df..72463e9 100644 --- a/decort/data_source_rg.go +++ b/decort/data_source_rg.go @@ -54,6 +54,12 @@ func flattenResgroup(d *schema.ResourceData, rg_facts string) error { d.Set("name", details.Name) d.Set("account_id", details.AccountID) d.Set("grid_id", details.GridID) + d.Set("desc", details.Description) + d.Set("status", details.Status) + d.Set("def_net", details.DefaultNetType) + d.Set("def_net_id", details.DefaultNetID) + d.Set("vins", details.Vins) + d.Set("computes", details.Computes) log.Debugf("flattenResgroup: calling flattenQuota()") if err = d.Set("quotas", flattenQuota(details.Quotas)); err != nil { @@ -91,19 +97,25 @@ func dataSourceResgroup() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - Description: "Name of this resource group. Names are case sensitive and unique within the context of a tenant.", + Description: "Name of this resource group. Names are case sensitive and unique within the context of an account.", }, "account": &schema.Schema { Type: schema.TypeString, Required: true, - Description: "Name of the tenant, which this resource group belongs to.", + Description: "Name of the account, which this resource group belongs to.", }, "account_id": &schema.Schema { Type: schema.TypeInt, Computed: true, - Description: "Unique ID of the tenant, which this resource group belongs to.", + Description: "Unique ID of the account, which this resource group belongs to.", + }, + + "desc": &schema.Schema { + Type: schema.TypeString, + Computed: true, + Description: "User-defined text description of this resource group.", }, "grid_id": &schema.Schema { @@ -112,21 +124,52 @@ func dataSourceResgroup() *schema.Resource { Description: "Unique ID of the grid, where this resource group is deployed.", }, - "public_ip": { // this may be obsoleted as new network segments and true resource groups are implemented - Type: schema.TypeString, - Computed: true, - Description: "Public IP address of this resource group (if any).", - }, - "quotas": { Type: schema.TypeList, Optional: true, MaxItems: 1, Elem: &schema.Resource { - Schema: quotasSubresourceSchema(), + Schema: quotaRgSubresourceSchema(), // this is a dictionary }, Description: "Quotas on the resources for this resource group.", }, + + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current status of this resource group.", + }, + + "def_net": &schema.Schema { + Type: schema.TypeString, + Computed: true, + Description: "Type of the default network for this resource group.", + }, + + "def_net_id": &schema.Schema { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the default network for this resource group (if any).", + }, + + "vins": { + Type: schema.TypeList, // this is a list of ints + Computed: true, + MaxItems: LimitMaxVinsPerResgroup, + Elem: &schema.Schema { + Type: schema.TypeInt, + }, + Description: "List of VINs deployed in this resource group.", + }, + + "computes": { + Type: schema.TypeList, //t his is a list of ints + Computed: true, + Elem: &schema.Schema { + Type: schema.TypeInt, + }, + Description: "List of computes deployed in this resource group." + }, }, } } diff --git a/decort/disk_subresource.go b/decort/disk_subresource.go index 07f4bd5..69651f2 100644 --- a/decort/disk_subresource.go +++ b/decort/disk_subresource.go @@ -89,12 +89,13 @@ func makeDataDisksArgString(disks []DiskConfig) string { } */ +// ID, type, name, size, account ID, SEP ID, SEP type, pool, status, tech status, compute ID, image ID func diskSubresourceSchema() map[string]*schema.Schema { rets := map[string]*schema.Schema { - "label": { + "name": { Type: schema.TypeString, Required: true, - Description: "Unique label to identify this disk among other disks connected to this VM.", + Description: "Name of this disk resource.", }, "size": { @@ -104,24 +105,37 @@ func diskSubresourceSchema() map[string]*schema.Schema { Description: "Size of the disk in GB.", }, - "pool": { + "account_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the account this disk resource belongs to.", + }, + + "sep_id": { Type: schema.TypeString, Optional: true, Default: "default", - Description: "Pool from which this disk should be provisioned.", + Description: "Storage provider (storage technology type) by which this disk should be served.", }, - "provider": { + "sep_type": { Type: schema.TypeString, Optional: true, Default: "default", Description: "Storage provider (storage technology type) by which this disk should be served.", }, - "disk_id": { + "pool": { + Type: schema.TypeString, + Optional: true, + Default: "default", + Description: "Pool from which this disk should be provisioned.", + }, + + "image_id": { Type: schema.TypeInt, Computed: true, - Description: "ID of this disk resource.", + Description: "ID of the binary Image this disk resource is cloned from (if any).", }, } diff --git a/decort/models_api.go b/decort/models_api.go index 8ee4968..f3bf083 100644 --- a/decort/models_api.go +++ b/decort/models_api.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2019-2020 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. Author: Sergey Shubin, , Licensed under the Apache License, Version 2.0 (the "License"); @@ -104,7 +104,16 @@ type ResgroupUpdateParam struct { // // structures related to /cloudapi/rg/get API call // -type ResourceRecord struct { +type QuotaRecord struct { // this is how quota is reported by /api/.../rg/get + Cpu int `json:"CU_C"` // CPU count in pcs + Ram int `json:"CU_M"` // RAM volume in MB + Disk int `json:"CU_D"` // Disk capacity in GB + ExtIPs int `json:"CU_I"` // Ext IPs count + ExtTraffic int `json:"CU_NP"` // Ext network traffic + GpuUnits int `json:"gpu_units"` // GPU count +} + +type ResourceRecord struct { // this is how actual usage is reported by /api/.../rg/get Cpu int `json:"cpu"` Disk int `json:"disksize"` ExtIPs int `json:"extips"` @@ -135,7 +144,7 @@ type ResgroupGetResp struct { ID uint `json:"id"` LockStatus string `json:"lockStatus"` Name string `json:"name"` - Quotas QuotaRecord `json:"resourceLimits"` + Quota QuotaRecord `json:"resourceLimits"` Status string `json:"status"` UpdatedBy string `json:"updatedBy"` UpdatedTime uint64 `json:"updatedTime"` @@ -171,7 +180,7 @@ const ResgroupDeleteAPI = "/restmachine/cloudapi/rg/delete" // const KvmX86CreateAPI = "/restmachine/cloudapi/kvmx86/create" const KvmPPCCreateAPI = "/restmachine/cloudapi/kvmppc/create" -type KvmXXXCreateParam struct { // this is unified structure for both x86 and PPC based VMs creation +type KvmVmCreateParam struct { // this is unified structure for both x86 and PPC based KVM VMs creation RgID uint `json:"rgId"` Name string `json:"name"` Cpu int `json:"cpu"` @@ -320,9 +329,6 @@ type OsUserRecord struct { } const ComputeGetAPI = "/restmachine/cloudapi/compute/get" -type ComputeGetParam struct { - ComputeID int `json:"computeId"` -} type ComputeGetResp struct { // ACLs `json:"ACL"` - it is a dictionary, special parsing required AccountID int `json:"accountId"` diff --git a/decort/quota_subresource.go b/decort/quota_subresource.go index c173074..076dd97 100644 --- a/decort/quota_subresource.go +++ b/decort/quota_subresource.go @@ -29,13 +29,14 @@ import ( ) -func makeQuotaConfig(arg_list []interface{}) (ResgroupQuotaConfig, int) { - quota := ResgroupQuotaConfig{ +func makeQuotaRecord(arg_list []interface{}) (QuotaRecord, int) { + quota := QuotaRecord{ Cpu: -1, Ram: -1, Disk: -1, NetTraffic: -1, ExtIPs: -1, + GpuUnits: -1, } subres_data := arg_list[0].(map[string]interface{}) @@ -44,75 +45,86 @@ func makeQuotaConfig(arg_list []interface{}) (ResgroupQuotaConfig, int) { } if subres_data["disk"].(int) > 0 { - quota.Disk = subres_data["disk"].(int) + quota.Disk = subres_data["disk"].(int) // Disk capacity ib GB } if subres_data["ram"].(int) > 0 { - ram_limit := subres_data["ram"].(int) - quota.Ram = float32(ram_limit) // /1024 // legacy fix - this can be obsoleted once redmine FR #1465 is implemented + quota.Ram= subres_data["ram"].(int) // RAM volume in MB } - if subres_data["net_traffic"].(int) > 0 { - quota.NetTraffic = subres_data["net_traffic"].(int) + if subres_data["ext_traffic"].(int) > 0 { + quota.ExtTraffic = subres_data["ext_traffic"].(int) } if subres_data["ext_ips"].(int) > 0 { quota.ExtIPs = subres_data["ext_ips"].(int) } + if subres_data["gpu_units"].(int) > 0 { + quota.GpuUnits = subres_data["gpu_units"].(int) + } + return quota, 1 } -func flattenQuota(quotas QuotaRecord) []interface{} { - quotas_map := make(map[string]interface{}) +func flattenQuota(quota QuotaRecord) []interface{} { + quota_map := make(map[string]interface{}) - quotas_map["cpu"] = quotas.Cpu - quotas_map["ram"] = int(quotas.Ram) - quotas_map["disk"] = quotas.Disk - quotas_map["net_traffic"] = quotas.NetTraffic - quotas_map["ext_ips"] = quotas.ExtIPs + quota_map["cpu"] = quota.Cpu + quota_map["ram"] = quota.Ram + quota_map["disk"] = quota.Disk + quota_map["ext_traffic"] = quota.ExtTraffic + quota_map["ext_ips"] = quota.ExtIPs + quota_map["gpu_units"] = quota.GpuUnits result := make([]interface{}, 1) - result[0] = quotas_map + result[0] = quota_map return result } -func quotasSubresourceSchema() map[string]*schema.Schema { +func quotaRgSubresourceSchema() map[string]*schema.Schema { rets := map[string]*schema.Schema { "cpu": &schema.Schema { Type: schema.TypeInt, Optional: true, Default: -1, - Description: "The quota on the total number of CPUs in this resource group.", + Description: "Limit on the total number of CPUs in this resource group.", }, "ram": &schema.Schema { - Type: schema.TypeInt, // NB: API expects and returns this as float! This may be changed in the future. + Type: schema.TypeInt, // NB: old API expects and returns this as float! This may be changed in the future. Optional: true, Default: -1, - Description: "The quota on the total amount of RAM in this resource group, specified in GB (Gigabytes!).", + Description: "Limit on the total amount of RAM in this resource group, specified in MB.", }, "disk": &schema.Schema { Type: schema.TypeInt, Optional: true, Default: -1, - Description: "The quota on the total volume of storage resources in this resource group, specified in GB.", + Description: "Limit on the total volume of storage resources in this resource group, specified in GB.", }, - "net_traffic": &schema.Schema { + "ext_traffic": &schema.Schema { Type: schema.TypeInt, Optional: true, Default: -1, - Description: "The quota on the total ingress network traffic for this resource group, specified in GB.", + Description: "Limit on the total ingress network traffic for this resource group, specified in GB.", }, "ext_ips": &schema.Schema { Type: schema.TypeInt, Optional: true, Default: -1, - Description: "The quota on the total number of external IP addresses this resource group can use.", + Description: "Limit on the total number of external IP addresses this resource group can use.", + }, + + "gpu_units": &schema.Schema { + Type: schema.TypeInt, + Optional: true, + Default: -1, + Description: "Limit on the total number of virtual GPUs this resource group can use.", }, } return rets diff --git a/decort/resource_resgroup.go b/decort/resource_resgroup.go index e41751b..3ee4eff 100644 --- a/decort/resource_resgroup.go +++ b/decort/resource_resgroup.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2019-2020 Digital Energy Cloud Solutions. All Rights Reserved. +Copyright (c) 2019-2021 Digital Energy Cloud Solutions. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,49 +29,54 @@ import ( "log" "net/url" "strconv" + "strings" "github.com/hashicorp/terraform/helper/schema" ) func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { - log.Debugf("resourceResgroupCreate: called for res group name %q, account name %q", - d.Get("name").(string), d.Get("account").(string)) - - rg := &ResgroupConfig{ - Name: d.Get("name").(string), - AccountName: d.Get("account").(string), + // First validate that we have all parameters required to create the new Resource Group + arg_set := false + account_name, arg_set := d.GetOk("account") + if !arg_set { + return fmt.Errorf("Cannot create new RG: missing account.") } - - // validate that we have all parameters required to create the new Resource Group - // location code is required to create new resource group - arg_value, arg_set := d.GetOk("location") - if arg_set { - rg.Location = arg_value.(string) - } else { - return fmt.Errorf("Cannot create new RG %q for account %q: missing location parameter.", - rg.Name, rg.AccountName) + rg_name, arg_set := d.GetOk("name") + if !arg_set { + return fmt.Errorf("Cannot create new RG: missing name.") } - // account ID is required to create new resource group + grid_id, arg_set := d.GetOk("grid_id") + if !arg_set { + return fmt.Errorf("Cannot create new RG %q for account %q: missing Grid ID.", + rg_name.(string), account_name.(string)) + } + + // all required parameters are set in the schema - we can continue with RG creation + log.Debugf("resourceResgroupCreate: called for RG name %q, account name %q", + account_name.(string), rg_name.(string)) + + // Valid account ID is required to create new resource group // obtain Account ID by account name - it should not be zero on success - account_id, err := utilityGetAccountIdByName(rg.AccountName, m) + validated_account_id, err := utilityGetAccountIdByName(account_name.(string), m) if err != nil { return err } - rg.AccountID = account_id - set_quotas := false - arg_value, arg_set = d.GetOk("quotas") + // quota settings are optional + set_quota := false + var quota_record QuotaRecord + arg_value, arg_set = d.GetOk("quota") if arg_set { - log.Debugf("resourceResgroupCreate: calling makeQuotaConfig") - rg.Quota, _ = makeQuotaConfig(arg_value.([]interface{})) - set_quotas = true + log.Debugf("resourceResgroupCreate: setting Quota on RG requested") + quota_record, _ = makeQuotaRecord(arg_value.([]interface{})) + set_quota = true } controller := m.(*ControllerCfg) - log.Debugf("resourceResgroupCreate: called by user %q for RG name %q, for account %q / ID %d, location %q", + log.Debugf("resourceResgroupCreate: called by user %q for RG name %q, account %q / ID %d, Grid ID %d", controller.getdecortUsername(), - rg.Name, d.Get("account").(string), rg.AccountID, rg.Location) + rg_name.(string), account_name.(string), validated_account_id, gird_id.(int)) /* type ResgroupCreateParam struct { AccountID int `json:"accountId"` @@ -93,23 +98,40 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { */ url_values := &url.Values{} - url_values.Add("accountId", fmt.Sprintf("%d", rg.AccountID)) - url_values.Add("name", rg.Name) - url_values.Add("gid", rg.Location) + url_values.Add("accountId", fmt.Sprintf("%d", validated_account_id)) + url_values.Add("name", rg_name.(string)) + url_values.Add("gid", fmt.Sprintf("%d", grid_id.(int))) url_values.Add("owner", controller.getdecortUsername()) - url_values.Add("def_net", "NONE") + // pass quota values as set - if set_quotas { - url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", rg.Quota.Cpu)) - url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", rg.Quota.Disk)) - url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", rg.Quota.Ram)) - url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", rg.Quota.NetTraffic)) - url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", rg.Quota.ExtIPs)) + if set_quota { + url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quota_record.Cpu)) + url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", quota_record.Disk)) + url_values.Add("maxMemoryCapacity", fmt.Sprintf("%d", quota_record.Ram)) + url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quota_record.ExtTraffic)) + url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", quota_record.ExtIPs)) + // url_values.Add("???", fmt.Sprintf("%d", quota_record.GpuUnits)) + } + + // parse and handle network settings + def_net_type, arg_set = d.GetOk("def_net_type") + if arg_set { + ulr_values.Add("def_net", def_net_type.(string)) } - // pass externalnetworkid if set - arg_value, arg_set = d.GetOk("extnet_id") + + ipcidr, arg_set = d.GetOk("ipcidr") + if arg_set { + ulr_values.Add("ipcidr", ipcidr.(string)) + } + + ext_net_id, arg_set = d.GetOk("ext_net_id") if arg_set { - url_values.Add("extNetId", fmt.Sprintf("%d", arg_value)) + ulr_values.Add("extNetId", ext_net_id.(int)) + } + + ext_ip, arg_set = d.GetOk("ext_ip") + if arg_set { + ulr_values.Add("extIp", ext_ip.(string)) } api_resp, err := controller.decortAPICall("POST", ResgroupCreateAPI, url_values) @@ -120,6 +142,7 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { d.SetId(api_resp) // rg/create API returns ID of the newly creted resource group on success rg.ID, _ = strconv.Atoi(api_resp) + // re-read newly created RG to make sure schema contains complete and up to date set of specifications return resourceResgroupRead(d, m) } @@ -138,66 +161,81 @@ func resourceResgroupRead(d *schema.ResourceData, m interface{}) error { } func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error { - // this method will only update quotas, if any are set log.Debugf("resourceResgroupUpdate: called for RG name %q, account name %q", d.Get("name").(string), d.Get("account").(string)) - quota_value, arg_set := d.GetOk("quotas") - if !arg_set { - // if there are no quotas set explicitly in the resource configuration - no change will be done - log.Debugf("resourceResgroupUpdate: quotas are not set in the resource config - no update on this resource will be done") - return resourceResgroupRead(d, m) - } - quotaconfig_new, _ := makeQuotaConfig(quota_value.([]interface{})) - - quota_value, _ = d.GetChange("quotas") // returns old as 1st, new as 2nd argument - quotaconfig_old, _ := makeQuotaConfig(quota_value.([]interface{})) + do_update := false controller := m.(*ControllerCfg) url_values := &url.Values{} - url_values.Add("cloudspaceId", d.Id()) - url_values.Add("name", d.Get("name").(string)) - - do_update := false - - if quotaconfig_new.Cpu != quotaconfig_old.Cpu { - do_update = true - log.Debugf("resourceResgroupUpdate: Cpu diff %d <- %d", quotaconfig_new.Cpu, quotaconfig_old.Cpu) - url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quotaconfig_new.Cpu)) - } - - if quotaconfig_new.Disk != quotaconfig_old.Disk { - do_update = true - log.Debugf("resourceResgroupUpdate: Disk diff %d <- %d", quotaconfig_new.Disk, quotaconfig_old.Disk) - url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", quotaconfig_new.Disk)) - } - - if quotaconfig_new.Ram != quotaconfig_old.Ram { - do_update = true - log.Debugf("resourceResgroupUpdate: Ram diff %f <- %f", quotaconfig_new.Ram, quotaconfig_old.Ram) - url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", quotaconfig_new.Ram)) + url_values.Add("rgId", d.Id()) + + name_new, name_set := d.GetOk("name") + if name_set { + log.Debugf("resourceResgroupUpdate: name specified - looking for deltas from the old settings.") + name_old, _ := d.GetChange("name") + if name_old.(string) != name_new.(string) { + do_update := true + url_values.Add("name", name_new.(string)) + } } - - if quotaconfig_new.NetTraffic != quotaconfig_old.NetTraffic { - do_update = true - log.Debugf("resourceResgroupUpdate: NetTraffic diff %d <- %d", quotaconfig_new.NetTraffic, quotaconfig_old.NetTraffic) - url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quotaconfig_new.NetTraffic)) + + quota_value, quota_set := d.GetOk("quota") + if quota_set { + log.Debugf("resourceResgroupUpdate: quota specified - looking for deltas from the old quota.") + quotarecord_new, _ := makeQuotaRecord(quota_value.([]interface{})) + quota_value_old, _ = d.GetChange("quota") // returns old as 1st, new as 2nd return value + quotarecord_old, _ := makeQuotaRecord(quota_value_old.([]interface{})) + + if quotarecord_new.Cpu != quotarecord_old.Cpu { + do_update = true + log.Debugf("resourceResgroupUpdate: Cpu diff %d <- %d", quotarecord_new.Cpu, quotarecord_old.Cpu) + url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quotarecord_new.Cpu)) + } + + if quotarecord_new.Disk != quotarecord_old.Disk { + do_update = true + log.Debugf("resourceResgroupUpdate: Disk diff %d <- %d", quotarecord_new.Disk, quotarecord_old.Disk) + url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", quotarecord_new.Disk)) + } + + if quotarecord_new.Ram != quotarecord_old.Ram { + do_update = true + log.Debugf("resourceResgroupUpdate: Ram diff %f <- %f", quotarecord_new.Ram, quotarecord_old.Ram) + url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", quotarecord_new.Ram)) + } + + if quotarecord_new.ExtTraffic != quotarecord_old.ExtTraffic { + do_update = true + log.Debugf("resourceResgroupUpdate: NetTraffic diff %d <- %d", quotarecord_new.ExtTraffic, quotarecord_old.ExtTraffic) + url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quotarecord_new.NetTraffic)) + } + + if quotarecord_new.ExtIPs != quotarecord_old.ExtIPs { + do_update = true + log.Debugf("resourceResgroupUpdate: ExtIPs diff %d <- %d", quotarecord_new.ExtIPs, quotarecord_old.ExtIPs) + url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", quotarecord_new.ExtIPs)) + } } - if quotaconfig_new.ExtIPs != quotaconfig_old.ExtIPs { - do_update = true - log.Debugf("resourceResgroupUpdate: ExtIPs diff %d <- %d", quotaconfig_new.ExtIPs, quotaconfig_old.ExtIPs) - url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", quotaconfig_new.ExtIPs)) + desc_new, desc_set := d.GetOk("desc") + if desc_set { + log.Debugf("resourceResgroupUpdate: description specified - looking for deltas from the old settings.") + desc_old, _ := d.GetChange("desc") + if desc_old.(string) != desc_new.(string) { + do_update := true + url_values.Add("desc", desc_new.(string)) + } } if do_update { - log.Debugf("resourceResgroupUpdate: some new quotas are set - updating the resource") + log.Debugf("resourceResgroupUpdate: detected delta between new and old RG specs - updating the RG") _, err := controller.decortAPICall("POST", ResgroupUpdateAPI, url_values) if err != nil { return err } } else { - log.Debugf("resourceResgroupUpdate: no difference in quotas between old and new state - no update on this resource will be done") + log.Debugf("resourceResgroupUpdate: no difference between old and new state - no update on the RG will be done") } return resourceResgroupRead(d, m) @@ -216,14 +254,14 @@ func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error { return nil } - params := &url.Values{} - params.Add("rgId", d.Id()) - params.Add("force", "true") - params.Add("permanently", "true") - params.Add("reason", "Destroyed by DECORT Terraform provider") + url_values := &url.Values{} + url_values.Add("rgId", d.Id()) + url_values.Add("force", "true") + url_values.Add("permanently", "true") + url_values.Add("reason", "Destroyed by DECORT Terraform provider") controller := m.(*ControllerCfg) - vm_facts, err = controller.decortAPICall("POST", ResgroupDeleteAPI, params) + _, err = controller.decortAPICall("POST", ResgroupDeleteAPI, url_values) if err != nil { return err } @@ -232,7 +270,7 @@ func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error { } func resourceResgroupExists(d *schema.ResourceData, m interface{}) (bool, error) { - // Reminder: according to Terraform rules, this function should not modify ResourceData argument + // Reminder: according to Terraform rules, this function should NOT modify ResourceData argument rg_facts, err := utilityResgroupCheckPresence(d, m) if rg_facts == "" { if err != nil { @@ -274,10 +312,30 @@ func resourceResgroup() *schema.Resource { Description: "Name of the account, which this resource group belongs to.", }, - "extnet_id": &schema.Schema { + "def_net": &schema.Schema { + Type: schema.TypeString, + Optional: true, + Default: "PRIVATE" + Description: "Type of the network, which this resource group will use as default for its computes - PRIVATE or PUBLIC or NONE.", + }, + + "ipcidr": &schema.Schema { + Type: schema.TypeString, + Optional: true, + Description: "Address of the netowrk inside the private network segment (aka ViNS) if def_net=PRIVATE", + }, + + "ext_net_id": &schema.Schema { Type: schema.TypeInt, Optional: true, - Description: "ID of the external network, which this resource group will be connected to by default.", + Default: 0, + Description: "ID of the external network, which this resource group will use as default for its computes if def_net=PUBLIC", + }, + + "ext_ip": &schema.Schema { + Type: schema.TypeString, + Optional: true, + Description: "IP address on the external netowrk to request, if def_net=PUBLIC", }, "account_id": &schema.Schema { @@ -292,14 +350,51 @@ func resourceResgroup() *schema.Resource { Description: "Unique ID of the grid, where this resource group is deployed.", }, - "quotas": { + "quota": { Type: schema.TypeList, Optional: true, MaxItems: 1, Elem: &schema.Resource { Schema: quotasSubresourceSchema(), }, - Description: "Quotas on the resources for this resource group.", + Description: "Quota settings for this resource group.", + }, + + "desc": { + Type: schema.TypeString, + Optional: true, + Description: "User-defined text description of this resource group." + }, + + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current status of this resource group.", + }, + + "def_net_id": &schema.Schema { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the default network for this resource group (if any).", + }, + + "vins": { + Type: schema.TypeList, + Computed: true, + MaxItems: LimitMaxVinsPerResgroup, + Elem: &schema.Resource { + Schema: vinsRgSubresourceSchema() // this is a list of ints + }, + Description: "List of VINs deployed in this resource group.", + }, + + "computes": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource { + Schema: computesRgSubresourceSchema() //this is a list of ints + }, + Description: "List of computes deployed in this resource group." }, }, } diff --git a/decort/utility_resgroup.go b/decort/utility_resgroup.go index c26ddf5..480a3eb 100644 --- a/decort/utility_resgroup.go +++ b/decort/utility_resgroup.go @@ -68,6 +68,8 @@ func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp, return model, nil } +// On success this function returns a string, as returned by API rg/get, which could be unmarshalled +// into ResgroupGetResp structure func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string, error) { // This function tries to locate resource group by its name and account name. // If succeeded, it returns non empty string that contains JSON formatted facts about the @@ -79,7 +81,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string // .../rg/list API with includedeleted=false // // This function does not modify its ResourceData argument, so it is safe to use it as core - // method for the resource's Exists method. + // method for the Terraform resource Exists method. // name := d.Get("name").(string) account_name := d.Get("account").(string) @@ -94,7 +96,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string log.Debugf("%s", body_string) log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %q", ResgroupListAPI) - model := CloudspacesListResp{} + model := ResgroupListResp{} err = json.Unmarshal([]byte(body_string), &model) if err != nil { return "", err @@ -148,5 +150,5 @@ func utilityGetAccountIdByName(account_name string, m interface{}) (int, error) } } - return 0, fmt.Errorf("Cannot find account %q for the current user. Check account value and your access rights", account_name) + return 0, fmt.Errorf("Cannot find account %q for the current user. Check account name and your access rights", account_name) } \ No newline at end of file