From 5db588e5dc42e2d4454f48df1a29ffcf60d6204f Mon Sep 17 00:00:00 2001 From: kjubybot Date: Wed, 29 Dec 2021 14:43:33 +0300 Subject: [PATCH] driver fix; start/stop compute --- decort/data_source_compute.go | 138 +++++++++++---------- decort/models_api.go | 198 +++++++++++++++--------------- decort/resource_compute.go | 220 +++++++++++++++++++--------------- 3 files changed, 298 insertions(+), 258 deletions(-) diff --git a/decort/data_source_compute.go b/decort/data_source_compute.go index 1492200..d9ac7c3 100644 --- a/decort/data_source_compute.go +++ b/decort/data_source_compute.go @@ -27,6 +27,7 @@ package decort import ( "encoding/json" "fmt" + // "net/url" log "github.com/sirupsen/logrus" @@ -36,20 +37,20 @@ import ( ) // Parse list of all disks from API compute/get into a list of "extra disks" attached to this compute -// Extra disks are all compute disks but a boot disk. +// Extra disks are all compute disks but a boot disk. func parseComputeDisksToExtraDisks(disks []DiskRecord) []interface{} { - // this return value will be used to d.Set("extra_disks",) item of dataSourceCompute schema, + // this return value will be used to d.Set("extra_disks",) item of dataSourceCompute schema, // which is a simple list of integer disk IDs excluding boot disk ID length := len(disks) log.Debugf("parseComputeDisksToExtraDisks: called for %d disks", length) - - if length == 0 || ( length == 1 && disks[0].Type == "B" ) { + + if length == 0 || (length == 1 && disks[0].Type == "B") { // the disk list is empty (which is kind of strange - diskless compute?), or // there is only one disk in the list and it is a boot disk; // as we skip boot disks, the result will be of 0 length anyway return make([]interface{}, 0) } - + result := make([]interface{}, length-1) idx := 0 for _, value := range disks { @@ -62,7 +63,7 @@ func parseComputeDisksToExtraDisks(disks []DiskRecord) []interface{} { idx++ } - return result + return result } // NOTE: this is a legacy function, which is not used as of rc-1.10 @@ -70,18 +71,18 @@ func parseComputeDisksToExtraDisks(disks []DiskRecord) []interface{} { func parseComputeDisks(disks []DiskRecord) []interface{} { // Return value was designed to d.Set("disks",) item of dataSourceCompute schema // However, this item was excluded from the schema as it is not directly - // managed through Terraform + // managed through Terraform length := len(disks) log.Debugf("parseComputeDisks: called for %d disks", length) - + /* - if length == 1 && disks[0].Type == "B" { - // there is only one disk in the list and it is a boot disk - // as we skip boot disks, the result will be of 0 lenght - length = 0 - } + if length == 1 && disks[0].Type == "B" { + // there is only one disk in the list and it is a boot disk + // as we skip boot disks, the result will be of 0 lenght + length = 0 + } */ - + result := []interface{}{} if length == 0 { @@ -90,10 +91,10 @@ func parseComputeDisks(disks []DiskRecord) []interface{} { for _, value := range disks { /* - if value.Type == "B" { - // skip boot disk when parsing the list of disks - continue - } + if value.Type == "B" { + // skip boot disk when parsing the list of disks + continue + } */ elem := make(map[string]interface{}) // keys in this map should correspond to the Schema definition @@ -112,11 +113,11 @@ func parseComputeDisks(disks []DiskRecord) []interface{} { // elem["status"] = value.Status // elem["tech_status"] = value.TechStatus elem["compute_id"] = value.ComputeID - + result = append(result, elem) } - return result + return result } func parseBootDiskSize(disks []DiskRecord) int { @@ -131,7 +132,7 @@ func parseBootDiskSize(disks []DiskRecord) int { } } - return 0 + return 0 } func parseBootDiskId(disks []DiskRecord) uint { @@ -146,10 +147,10 @@ func parseBootDiskId(disks []DiskRecord) uint { } } - return 0 + return 0 } -// Parse the list of interfaces from compute/get response into a list of networks +// Parse the list of interfaces from compute/get response into a list of networks // attached to this compute func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []interface{} { // return value will be used to d.Set("network") item of dataSourceCompute schema @@ -172,8 +173,9 @@ func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []interface{} { result = append(result, elem) } - return result + return result } + /* func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []map[string]interface{} { // return value will be used to d.Set("network") item of dataSourceCompute schema @@ -196,16 +198,15 @@ func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []map[string]int result[i] = elem } - return result + return result } */ - // NOTE: this function is retained for historical purposes and actually not used as of rc-1.10 func parseComputeInterfaces(ifaces []InterfaceRecord) []map[string]interface{} { // return value was designed to d.Set("interfaces",) item of dataSourceCompute schema // However, this item was excluded from the schema as it is not directly - // managed through Terraform + // managed through Terraform length := len(ifaces) log.Debugf("parseComputeInterfaces: called for %d ifaces", length) @@ -237,7 +238,7 @@ func parseComputeInterfaces(ifaces []InterfaceRecord) []map[string]interface{} { result[i] = elem } - return result + return result } func flattenCompute(d *schema.ResourceData, compFacts string) error { @@ -274,6 +275,12 @@ func flattenCompute(d *schema.ResourceData, compFacts string) error { // d.Set("status", model.Status) // d.Set("tech_status", model.TechStatus) + if model.TechStatus == "STARTED" { + d.Set("started", true) + } else { + d.Set("started", false) + } + if len(model.Disks) > 0 { log.Debugf("flattenCompute: calling parseComputeDisksToExtraDisks for %d disks", len(model.Disks)) if err = d.Set("extra_disks", parseComputeDisksToExtraDisks(model.Disks)); err != nil { @@ -403,24 +410,24 @@ func dataSourceCompute() *schema.Resource { }, "extra_disks": { - Type: schema.TypeSet, - Computed: true, + Type: schema.TypeSet, + Computed: true, MaxItems: MaxExtraDisksPerCompute, - Elem: &schema.Schema { - Type: schema.TypeInt, + Elem: &schema.Schema{ + Type: schema.TypeInt, }, Description: "IDs of the extra disk(s) attached to this compute.", }, - + /* - "disks": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: dataSourceDiskSchemaMake(), // ID, type, name, size, account ID, SEP ID, SEP type, pool, status, tech status, compute ID, image ID + "disks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: dataSourceDiskSchemaMake(), // ID, type, name, size, account ID, SEP ID, SEP type, pool, status, tech status, compute ID, image ID + }, + Description: "Detailed specification for all disks attached to this compute instance (including bood disk).", }, - Description: "Detailed specification for all disks attached to this compute instance (including bood disk).", - }, */ "network": { @@ -434,14 +441,14 @@ func dataSourceCompute() *schema.Resource { }, /* - "interfaces": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: interfaceSubresourceSchemaMake(), + "interfaces": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: interfaceSubresourceSchemaMake(), + }, + Description: "Specification for the virtual NICs configured on this compute instance.", }, - Description: "Specification for the virtual NICs configured on this compute instance.", - }, */ "os_users": { @@ -465,24 +472,31 @@ func dataSourceCompute() *schema.Resource { Description: "Placeholder for cloud_init parameters.", }, - /* - "status": { - Type: schema.TypeString, - Computed: true, - Description: "Current model status of this compute instance.", + "started": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Is compute started.", }, - "tech_status": { - Type: schema.TypeString, - Computed: true, - Description: "Current technical status of this compute instance.", - }, + /* + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current model status of this compute instance.", + }, - "internal_ip": { - Type: schema.TypeString, - Computed: true, - Description: "Internal IP address of this Compute.", - }, + "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.", + }, */ }, } diff --git a/decort/models_api.go b/decort/models_api.go index cd44e58..633f2f4 100644 --- a/decort/models_api.go +++ b/decort/models_api.go @@ -105,12 +105,12 @@ type ResgroupUpdateParam struct { // structures related to /cloudapi/rg/get API call // type QuotaRecord struct { // this is how quota is reported by /api/.../rg/get - Cpu int `json:"CU_C"` // CPU count in pcs - Ram float64 `json:"CU_M"` // RAM volume in MB, it is STILL reported as FLOAT - 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 + Cpu int `json:"CU_C"` // CPU count in pcs + Ram float64 `json:"CU_M"` // RAM volume in MB, it is STILL reported as FLOAT + 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 @@ -130,27 +130,27 @@ type UsageRecord struct { const ResgroupGetAPI = "/restmachine/cloudapi/rg/get" type ResgroupGetResp struct { - ACLs []UserAclRecord `json:"ACLs"` - Usage UsageRecord `json:"Resources"` - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` - GridID int `json:"gid"` - CreatedBy string `json:"createdBy"` - CreatedTime uint64 `json:"createdTime"` - DefaultNetID int `json:"def_net_id"` - DefaultNetType string `json:"def_net_type"` - DeletedBy string `json:"deletedBy"` - DeletedTime uint64 `json:"deletedTime"` - Desc string `json:"desc"` - ID uint `json:"id"` - LockStatus string `json:"lockStatus"` - Name string `json:"name"` - Quota QuotaRecord `json:"resourceLimits"` - Status string `json:"status"` - UpdatedBy string `json:"updatedBy"` - UpdatedTime uint64 `json:"updatedTime"` - Vins []int `json:"vins"` - Computes []int `json:"vms"` + ACLs []UserAclRecord `json:"ACLs"` + Usage UsageRecord `json:"Resources"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + GridID int `json:"gid"` + CreatedBy string `json:"createdBy"` + CreatedTime uint64 `json:"createdTime"` + DefaultNetID int `json:"def_net_id"` + DefaultNetType string `json:"def_net_type"` + DeletedBy string `json:"deletedBy"` + DeletedTime uint64 `json:"deletedTime"` + Desc string `json:"desc"` + ID uint `json:"id"` + LockStatus string `json:"lockStatus"` + Name string `json:"name"` + Quota QuotaRecord `json:"resourceLimits"` + Status string `json:"status"` + UpdatedBy string `json:"updatedBy"` + UpdatedTime uint64 `json:"updatedTime"` + Vins []int `json:"vins"` + Computes []int `json:"vms"` Ignored map[string]interface{} `json:"-"` } @@ -187,22 +187,23 @@ const KvmX86CreateAPI = "/restmachine/cloudapi/kvmx86/create" const KvmPPCCreateAPI = "/restmachine/cloudapi/kvmppc/create" 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"` - Ram int `json:"ram"` - ImageID int `json:"imageId"` - BootDisk int `json:"bootDisk"` - NetType string `json:"netType"` - NetId int `json:"netId"` - IPAddr string `json:"ipAddr"` - UserData string `json:"userdata"` - Desc string `json:"desc"` - Start bool `json:"start"` + RgID uint `json:"rgId"` + Name string `json:"name"` + Cpu int `json:"cpu"` + Ram int `json:"ram"` + ImageID int `json:"imageId"` + BootDisk int `json:"bootDisk"` + NetType string `json:"netType"` + NetId int `json:"netId"` + IPAddr string `json:"ipAddr"` + UserData string `json:"userdata"` + Desc string `json:"desc"` + Start bool `json:"start"` } // structures related to cloudapi/compute/start API const ComputeStartAPI = "/restmachine/cloudapi/compute/start" +const ComputeStopAPI = "/restmachine/cloudapi/compute/stop" // structures related to cloudapi/compute/delete API const ComputeDeleteAPI = "/restmachine/cloudapi/compute/delete" @@ -271,14 +272,14 @@ type ComputeRecord struct { SnapSets []SnapSetRecord `json:"snapSets"` Status string `json:"status"` // Tags []string `json:"tags"` // Tags were reworked since DECORT 3.7.1 - TechStatus string `json:"techStatus"` - TotalDiskSize int `json:"totalDiskSize"` - UpdatedBy string `json:"updatedBy"` - UpdateTime uint64 `json:"updateTime"` - UserManaged bool `json:"userManaged"` - Vgpus []int `json:"vgpus"` - VinsConnected int `json:"vinsConnected"` - VirtualImageID int `json:"virtualImageId"` + TechStatus string `json:"techStatus"` + TotalDiskSize int `json:"totalDiskSize"` + UpdatedBy string `json:"updatedBy"` + UpdateTime uint64 `json:"updateTime"` + UserManaged bool `json:"userManaged"` + Vgpus []int `json:"vgpus"` + VinsConnected int `json:"vinsConnected"` + VirtualImageID int `json:"virtualImageId"` } const ComputeListAPI = "/restmachine/cloudapi/compute/list" @@ -302,7 +303,7 @@ type DiskRecord struct { // ACLs `json:"ACL"` - it is a dictionary, special parsing required // was - Acl map[string]string `json:"acl"` AccountID int `json:"accountId"` - AccountName string `json:"accountName"` // NOTE: absent from compute/get output + AccountName string `json:"accountName"` // NOTE: absent from compute/get output BootPartition int `json:"bootPartition"` CreatedTime uint64 `json:"creationTime"` DeletedTime uint64 `json:"deletionTime"` @@ -325,7 +326,7 @@ type DiskRecord struct { PurgeTime uint64 `json:"purgeTime"` // Role string `json:"role"` SepType string `json:"sepType"` - SepID int `json:"sepId"` // NOTE: absent from compute/get output + SepID int `json:"sepId"` // NOTE: absent from compute/get output SizeMax int `json:"sizeMax"` SizeUsed int `json:"sizeUsed"` // sum over all snapshots of this disk to report total consumed space Snapshots []SnapshotRecord `json:"snapshots"` @@ -376,14 +377,14 @@ type ComputeGetResp struct { SnapSets []SnapSetRecord `json:"snapSets"` Status string `json:"status"` // Tags []string `json:"tags"` // Tags were reworked since DECORT 3.7.1 - TechStatus string `json:"techStatus"` - TotalDiskSize int `json:"totalDiskSize"` - UpdatedBy string `json:"updatedBy"` - UpdateTime uint64 `json:"updateTime"` - UserManaged bool `json:"userManaged"` - Vgpus []int `json:"vgpus"` - VinsConnected int `json:"vinsConnected"` - VirtualImageID int `json:"virtualImageId"` + TechStatus string `json:"techStatus"` + TotalDiskSize int `json:"totalDiskSize"` + UpdatedBy string `json:"updatedBy"` + UpdateTime uint64 `json:"updateTime"` + UserManaged bool `json:"userManaged"` + Vgpus []int `json:"vgpus"` + VinsConnected int `json:"vinsConnected"` + VirtualImageID int `json:"virtualImageId"` } // @@ -468,11 +469,12 @@ const ComputePfwDelAPI = "/restmachine/cloudapi/compute/pfwDel" // structures related to /cloudapi/compute/net Attach/Detach API // type ComputeNetMgmtRecord struct { // used to "cache" network specs when preparing to manage compute networks - ID int - Type string - IPAddress string - MAC string + ID int + Type string + IPAddress string + MAC string } + const ComputeNetAttachAPI = "/restmachine/cloudapi/compute/netAttach" const ComputeNetDetachAPI = "/restmachine/cloudapi/compute/netDetach" @@ -512,52 +514,52 @@ const DisksRenameAPI = "/restmachine/cloudapi/disks/rename" // const DisksDeleteAPI = "/restmachine/cloudapi/disks/delete" - // -// ViNS structures +// ViNS structures // // this is the structure of the element in the list returned by vins/search API -type VinsSearchRecord struct { - ID int `json:"id"` - Name string `json:"name"` - IPCidr string `json:"network"` - VxLanID int `json:"vxlanId"` - ExternalIP string `json:"externalIP"` - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` - RgID int `json:"rgId"` - RgName string `json:"rgName"` +type VinsSearchRecord struct { + ID int `json:"id"` + Name string `json:"name"` + IPCidr string `json:"network"` + VxLanID int `json:"vxlanId"` + ExternalIP string `json:"externalIP"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + RgID int `json:"rgId"` + RgName string `json:"rgName"` } const VinsSearchAPI = "/restmachine/cloudapi/vins/search" + type VinsSearchResp []VinsSearchRecord type VnfRecord struct { - ID int `json:"id"` - AccountID int `json:"accountId"` - Type string `json:"type"` // "DHCP", "NAT", "GW" etc - Config map[string]interface{} `json:"config"` // NOTE: VNF specs vary by VNF type + ID int `json:"id"` + AccountID int `json:"accountId"` + Type string `json:"type"` // "DHCP", "NAT", "GW" etc + Config map[string]interface{} `json:"config"` // NOTE: VNF specs vary by VNF type } type VnfGwConfigRecord struct { // describes GW VNF config structure inside ViNS, as returned by API vins/get - ExtNetID int `json:"ext_net_id"` - ExtNetIP string `json:"ext_net_ip"` - ExtNetMask int `json:"ext_net_mask"` - DefaultGW string `json:"default_gw"` + ExtNetID int `json:"ext_net_id"` + ExtNetIP string `json:"ext_net_ip"` + ExtNetMask int `json:"ext_net_mask"` + DefaultGW string `json:"default_gw"` } type VinsRecord struct { // represents part of the response from API vins/get - ID int `json:"id"` - Name string `json:"name"` - IPCidr string `json:"network"` - VxLanID int `json:"vxlanId"` - ExternalIP string `json:"externalIP"` - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` - RgID int `json:"rgid"` - RgName string `json:"rgName"` - VNFs map[string]VnfRecord `json:"vnfs"` - Desc string `json:"desc"` + ID int `json:"id"` + Name string `json:"name"` + IPCidr string `json:"network"` + VxLanID int `json:"vxlanId"` + ExternalIP string `json:"externalIP"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + RgID int `json:"rgid"` + RgName string `json:"rgName"` + VNFs map[string]VnfRecord `json:"vnfs"` + Desc string `json:"desc"` } const VinsGetAPI = "/restmachine/cloudapi/vins/get" @@ -572,13 +574,13 @@ const VinsDeleteAPI = "/restmachine/cloudapi/vins/delete" // // Grid ID structures -// +// type LocationRecord struct { - GridID int `json:"gid"` - Id int `json:"id"` - LocationCode string `json:"locationCode"` - Name string `json:"name"` - Flag string `json:"flag"` + GridID int `json:"gid"` + Id int `json:"id"` + LocationCode string `json:"locationCode"` + Name string `json:"name"` + Flag string `json:"flag"` } const LocationsListAPI = "/restmachine/cloudapi/locations/list" // Returns list of GridRecord on success diff --git a/decort/resource_compute.go b/decort/resource_compute.go index a27f16f..0631108 100644 --- a/decort/resource_compute.go +++ b/decort/resource_compute.go @@ -50,15 +50,15 @@ func cloudInitDiffSupperss(key, oldVal, newVal string, d *schema.ResourceData) b } func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { - // we assume all mandatory parameters it takes to create a comptue instance are properly + // we assume all mandatory parameters it takes to create a comptue instance are properly // specified - we rely on schema "Required" attributes to let Terraform validate them for us - + log.Debugf("resourceComputeCreate: called for Compute name %q, RG ID %d", d.Get("name").(string), d.Get("rg_id").(int)) // create basic Compute (i.e. without extra disks and network connections - those will be attached // by subsequent individual API calls). // creating Compute is a multi-step workflow, which may fail at some step, so we use "partial" feature of Terraform - d.Partial(true) + d.Partial(true) controller := m.(*ControllerCfg) urlValues := &url.Values{} urlValues.Add("rgId", fmt.Sprintf("%d", d.Get("rg_id").(int))) @@ -68,32 +68,32 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { urlValues.Add("imageId", fmt.Sprintf("%d", d.Get("image_id").(int))) urlValues.Add("bootDisk", fmt.Sprintf("%d", d.Get("boot_disk_size").(int))) urlValues.Add("netType", "NONE") // at the 1st step create isolated compute - urlValues.Add("start", "0") // at the 1st step create compute in a stopped state + urlValues.Add("start", "0") // at the 1st step create compute in a stopped state - argVal, argSet := d.GetOk("description") + argVal, argSet := d.GetOk("description") if argSet { urlValues.Add("desc", argVal.(string)) } /* - sshKeysVal, sshKeysSet := d.GetOk("ssh_keys") - if sshKeysSet { - // process SSH Key settings and set API values accordingly - log.Debugf("resourceComputeCreate: calling makeSshKeysArgString to setup SSH keys for guest login(s)") - urlValues.Add("userdata", makeSshKeysArgString(sshKeysVal.([]interface{}))) - } + sshKeysVal, sshKeysSet := d.GetOk("ssh_keys") + if sshKeysSet { + // process SSH Key settings and set API values accordingly + log.Debugf("resourceComputeCreate: calling makeSshKeysArgString to setup SSH keys for guest login(s)") + urlValues.Add("userdata", makeSshKeysArgString(sshKeysVal.([]interface{}))) + } */ computeCreateAPI := KvmX86CreateAPI - arch := d.Get("arch").(string) - if arch == "KVM_PPC" { + driver := d.Get("driver").(string) + if driver == "KVM_PPC" { computeCreateAPI = KvmPPCCreateAPI log.Debugf("resourceComputeCreate: creating Compute of type KVM VM PowerPC") } else { // note that we do not validate arch value for explicit "KVM_X86" here log.Debugf("resourceComputeCreate: creating Compute of type KVM VM x86") } - argVal, argSet = d.GetOk("cloud_init") + argVal, argSet = d.GetOk("cloud_init") if argSet { // userdata must not be empty string and must not be a reserved keyword "applied" userdata := argVal.(string) @@ -101,7 +101,7 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { urlValues.Add("userdata", userdata) } } - + apiResp, err := controller.decortAPICall("POST", computeCreateAPI, urlValues) if err != nil { return err @@ -117,16 +117,16 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { d.SetPartial("image_id") d.SetPartial("boot_disk_size") /* - if sshKeysSet { - d.SetPartial("ssh_keys") - } + if sshKeysSet { + d.SetPartial("ssh_keys") + } */ log.Debugf("resourceComputeCreate: new simple Compute ID %d, name %s created", compId, d.Get("name").(string)) // Configure data disks if any extraDisksOk := true - argVal, argSet = d.GetOk("extra_disks") + argVal, argSet = d.GetOk("extra_disks") if argSet && argVal.(*schema.Set).Len() > 0 { // urlValues.Add("desc", argVal.(string)) log.Debugf("resourceComputeCreate: calling utilityComputeExtraDisksConfigure to attach %d extra disk(s)", argVal.(*schema.Set).Len()) @@ -142,8 +142,8 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { // Configure external networks if any netsOk := true - argVal, argSet = d.GetOk("network") - if argSet && argVal.(*schema.Set).Len() > 0 { + argVal, argSet = d.GetOk("network") + if argSet && argVal.(*schema.Set).Len() > 0 { log.Debugf("resourceComputeCreate: calling utilityComputeNetworksConfigure to attach %d network(s)", argVal.(*schema.Set).Len()) err = controller.utilityComputeNetworksConfigure(d, false) // do_delta=false, as we are working on a new compute if err != nil { @@ -161,23 +161,25 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { d.Partial(false) } - // Note bene: we created compute in a STOPPED state (this is required to properly attach 1st network interface), + // Note bene: we created compute in a STOPPED state (this is required to properly attach 1st network interface), // now we need to start it before we report the sequence complete - reqValues := &url.Values{} - reqValues.Add("computeId", fmt.Sprintf("%d", compId)) - log.Debugf("resourceComputeCreate: starting Compute ID %d after completing its resource configuration", compId) - apiResp, err = controller.decortAPICall("POST", ComputeStartAPI, reqValues) - if err != nil { - return err + if d.Get("started").(bool) { + reqValues := &url.Values{} + reqValues.Add("computeId", fmt.Sprintf("%d", compId)) + log.Debugf("resourceComputeCreate: starting Compute ID %d after completing its resource configuration", compId) + apiResp, err = controller.decortAPICall("POST", ComputeStartAPI, reqValues) + if err != nil { + return err + } } - + log.Debugf("resourceComputeCreate: new Compute ID %d, name %s creation sequence complete", compId, d.Get("name").(string)) - // We may reuse dataSourceComputeRead here as we maintain similarity + // We may reuse dataSourceComputeRead here as we maintain similarity // between Compute resource and Compute data source schemas - // Compute read function will also update resource ID on success, so that Terraform + // Compute read function will also update resource ID on success, so that Terraform // will know the resource exists - return dataSourceComputeRead(d, m) + return dataSourceComputeRead(d, m) } func resourceComputeRead(d *schema.ResourceData, m interface{}) error { @@ -209,11 +211,12 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error { controller := m.(*ControllerCfg) - /* - 1. Resize CPU/RAM - 2. Resize (grow) boot disk - 3. Update extra disks - 4. Update networks + /* + 1. Resize CPU/RAM + 2. Resize (grow) boot disk + 3. Update extra disks + 4. Update networks + 5. Start/stop */ // 1. Resize CPU/RAM @@ -230,7 +233,7 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error { } else { params.Add("cpu", "0") // no change to CPU allocation } - + oldRam, newRam := d.GetChange("ram") if oldRam.(int) != newRam.(int) { params.Add("ram", fmt.Sprintf("%d", newRam.(int))) @@ -241,8 +244,8 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error { if doUpdate { log.Debugf("resourceComputeUpdate: changing CPU %d -> %d and/or RAM %d -> %d", - oldCpu.(int), newCpu.(int), - oldRam.(int), newRam.(int)) + oldCpu.(int), newCpu.(int), + oldRam.(int), newRam.(int)) _, err := controller.decortAPICall("POST", ComputeResizeAPI, params) if err != nil { return err @@ -251,14 +254,14 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error { d.SetPartial("ram") } - // 2. Resize (grow) Boot disk + // 2. Resize (grow) Boot disk oldSize, newSize := d.GetChange("boot_disk_size") if oldSize.(int) < newSize.(int) { bdsParams := &url.Values{} bdsParams.Add("diskId", fmt.Sprintf("%d", d.Get("boot_disk_id").(int))) bdsParams.Add("size", fmt.Sprintf("%d", newSize.(int))) log.Debugf("resourceComputeUpdate: compute ID %s, boot disk ID %d resize %d -> %d", - d.Id(), d.Get("boot_disk_id").(int), oldSize.(int), newSize.(int)) + d.Id(), d.Get("boot_disk_id").(int), oldSize.(int), newSize.(int)) _, err := controller.decortAPICall("POST", DisksResizeAPI, params) if err != nil { return err @@ -284,17 +287,32 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error { d.SetPartial("network") } + if d.HasChange("started") { + params := &url.Values{} + params.Add("computeId", d.Id()) + if d.Get("started").(bool) { + if _, err := controller.decortAPICall("POST", ComputeStartAPI, params); err != nil { + return err + } + } else { + if _, err := controller.decortAPICall("POST", ComputeStopAPI, params); err != nil { + return err + } + } + d.SetPartial("started") + } + d.Partial(false) - // we may reuse dataSourceComputeRead here as we maintain similarity + // we may reuse dataSourceComputeRead here as we maintain similarity // between Compute resource and Compute data source schemas - return dataSourceComputeRead(d, m) + return dataSourceComputeRead(d, m) } func resourceComputeDelete(d *schema.ResourceData, m interface{}) error { - // NOTE: this function destroys target Compute instance "permanently", so - // there is no way to restore it. - // If compute being destroyed has some extra disks attached, they are + // NOTE: this function destroys target Compute instance "permanently", so + // there is no way to restore it. + // If compute being destroyed has some extra disks attached, they are // detached from the compute log.Debugf("resourceComputeDelete: called for Compute name %s, RG ID %d", d.Get("name").(string), d.Get("rg_id").(int)) @@ -312,7 +330,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error { log.Debugf("resourceComputeDelete: ready to unmarshal string %s", compFacts) err = json.Unmarshal([]byte(compFacts), &model) if err == nil && len(model.Disks) > 0 { - // prepare to detach data disks from compute - do it only if compFacts unmarshalled + // prepare to detach data disks from compute - do it only if compFacts unmarshalled // properly and the resulting model contains non-empty Disks list for _, diskFacts := range model.Disks { if diskFacts.Type == "B" { @@ -338,7 +356,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error { params.Add("computeId", d.Id()) params.Add("permanently", "1") // TODO: this is for the upcoming API update - params.Add("detachdisks", "1") - + _, err = controller.decortAPICall("POST", ComputeDeleteAPI, params) if err != nil { return err @@ -398,13 +416,13 @@ func resourceCompute() *schema.Resource { Description: "ID of the resource group where this compute should be deployed.", }, - "arch": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - StateFunc: stateFuncToUpper, + "driver": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + StateFunc: stateFuncToUpper, ValidateFunc: validation.StringInSlice([]string{"KVM_X86", "KVM_PPC"}, false), // observe case while validating - Description: "Hardware architecture of this compute instance.", + Description: "Hardware architecture of this compute instance.", }, "cpu": { @@ -422,16 +440,16 @@ func resourceCompute() *schema.Resource { }, "image_id": { - Type: schema.TypeInt, - Required: true, - ForceNew: true, + Type: schema.TypeInt, + Required: true, + ForceNew: true, ValidateFunc: validation.IntAtLeast(1), - Description: "ID of the OS image to base this compute instance on.", + Description: "ID of the OS image to base this compute instance on.", }, "boot_disk_size": { - Type: schema.TypeInt, - Required: true, + Type: schema.TypeInt, + Required: true, Description: "This compute instance boot disk size in GB. Make sure it is large enough to accomodate selected OS image.", }, @@ -456,15 +474,15 @@ func resourceCompute() *schema.Resource { }, /* - "ssh_keys": { - Type: schema.TypeList, - Optional: true, - MaxItems: MaxSshKeysPerCompute, - Elem: &schema.Resource{ - Schema: sshSubresourceSchemaMake(), + "ssh_keys": { + Type: schema.TypeList, + Optional: true, + MaxItems: MaxSshKeysPerCompute, + Elem: &schema.Resource{ + Schema: sshSubresourceSchemaMake(), + }, + Description: "SSH keys to authorize on this compute instance.", }, - Description: "SSH keys to authorize on this compute instance.", - }, */ "description": { @@ -473,13 +491,12 @@ func resourceCompute() *schema.Resource { Description: "Optional text description of this compute instance.", }, - "cloud_init": { - Type: schema.TypeString, - Optional: true, - Default: "applied", + Type: schema.TypeString, + Optional: true, + Default: "applied", DiffSuppressFunc: cloudInitDiffSupperss, - Description: "Optional cloud_init parameters. Applied when creating new compute instance only, ignored in all other cases.", + Description: "Optional cloud_init parameters. Applied when creating new compute instance only, ignored in all other cases.", }, // The rest are Compute properties, which are "computed" once it is created @@ -516,37 +533,44 @@ func resourceCompute() *schema.Resource { Description: "Guest OS users provisioned on this compute instance.", }, + "started": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Is compute started.", + }, + /* - "disks": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: dataSourceDiskSchemaMake(), // ID, type, name, size, account ID, SEP ID, SEP type, pool, status, tech status, compute ID, image ID + "disks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: dataSourceDiskSchemaMake(), // ID, type, name, size, account ID, SEP ID, SEP type, pool, status, tech status, compute ID, image ID + }, + Description: "Detailed specification for all disks attached to this compute instance (including bood disk).", }, - Description: "Detailed specification for all disks attached to this compute instance (including bood disk).", - }, - "interfaces": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: interfaceSubresourceSchemaMake(), + "interfaces": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: interfaceSubresourceSchemaMake(), + }, + Description: "Specification for the virtual NICs configured on this compute instance.", }, - Description: "Specification for the virtual NICs configured on this compute instance.", - }, - "status": { - Type: schema.TypeString, - Computed: true, - Description: "Current model status of this compute instance.", - }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current model status of this compute instance.", + }, - "tech_status": { - Type: schema.TypeString, - Computed: true, - Description: "Current technical status of this compute instance.", - }, + "tech_status": { + Type: schema.TypeString, + Computed: true, + Description: "Current technical status of this compute instance.", + }, */ }, }