diff --git a/decort/data_source_vins.go b/decort/data_source_vins.go new file mode 100644 index 0000000..80876ea --- /dev/null +++ b/decort/data_source_vins.go @@ -0,0 +1,149 @@ +/* +Copyright (c) 2020-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +Technology platfom. + +Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. +*/ + +package decort + +import ( + "encoding/json" + "fmt" + + log "github.com/sirupsen/logrus" + + // "net/url" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + // "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +// vins_facts is a response string from API vins/get +func flattenVins(d *schema.ResourceData, vins_facts string) error { + // NOTE: this function modifies ResourceData argument - as such it should never be called + // from resourceVinsExists(...) method + log.Debugf("flattenVins: ready to decode response body from API") + details := VinsGetResp{} + err := json.Unmarshal([]byte(vins_facts), &details) + if err != nil { + return err + } + + log.Debugf("flattenVins: decoded ViNS name:ID %s:%d, account ID %d, RG ID %d", + details.Name, details.ID, details.AccountID, details.RgID) + + d.SetId(fmt.Sprintf("%d", details.ID)) + d.Set("rg_id", details.ID) + d.Set("name", details.Name) + d.Set("account_name", details.AccountName) + d.Set("account_id", details.AccountID) + d.Set("grid_id", details.GridID) + d.Set("description", details.Desc) + d.Set("status", details.Status) + d.Set("def_net_type", details.DefaultNetType) + d.Set("def_net_id", details.DefaultNetID) + + return nil +} + +func dataSourceVinsRead(d *schema.ResourceData, m interface{}) error { + vinsFacts, err := utilityVinsCheckPresence(d, m) + if vinsFacts == "" { + // if empty string is returned from utilityVinsCheckPresence then there is no + // such ViNS and err tells so - just return it to the calling party + d.SetId("") // ensure ID is empty in this case + return err + } + + return flattenVins(d, vinsFacts) +} + +func dataSourceVins() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + Read: dataSourceVinsRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &Timeout30s, + Default: &Timeout60s, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the ViNS. Names are case sensitive and unique within the context of an account or resource group.", + }, + + /* + "vins_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Unique ID of the ViNS. If ViNS ID is specified, then ViNS name, rg_id and account_id are ignored.", + }, + */ + + "rg_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Unique ID of the resource group, where this ViNS is belongs to (for ViNS created at resource group level, 0 otherwise).", + }, + + "account_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Unique ID of the account, which this ViNS belongs to.", + }, + + // the rest of attributes are computed + "account_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the account, which this ViNS belongs to.", + }, + + "description": { + Type: schema.TypeString, + Computed: true, + Description: "User-defined text description of this ViNS.", + }, + + "ext_ip_addr": { + Type: schema.TypeString, + Computed: true, + Description: "IP address of the external connection (valid for ViNS connected to external network, empty string otherwise).", + }, + + "ext_net_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the external network this ViNS is connected to (-1 means no external connection).", + }, + + "ipcidr": { + Type: schema.TypeString, + Computed: true, + Description: "Network address used by this ViNS." + }, + }, + } +} diff --git a/decort/models_api.go b/decort/models_api.go index 3908c1c..9435285 100644 --- a/decort/models_api.go +++ b/decort/models_api.go @@ -496,6 +496,58 @@ const DisksResizeAPI = "/restmachine/cloudapi/disks/resize2" // const DisksDeleteAPI = "/restmachine/cloudapi/disks/delete" + +// +// 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"` +} + +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 string `json:"config"` // NOTE: VNF specs vary by VNF type +} + +type VnfGwConfigRecord struct { // describes GW VNF config structure + 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"` +} + +const VinsGetAPI = "/restmachine/cloudapi/vins/get" + +const VinsCreateAPI = "/restmachine/cloudapi/vins/create" +const VinsDeleteAPI = "/restmachine/cloudapi/vins/delete" + // // Grid ID structures // diff --git a/decort/network_subresource.go b/decort/network_subresource.go index 21a96d3..3d1edb7 100644 --- a/decort/network_subresource.go +++ b/decort/network_subresource.go @@ -25,7 +25,7 @@ import ( // "net/url" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - // "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" ) // This is subresource of compute resource used when creating/managing compute network connections @@ -35,6 +35,7 @@ func networkSubresourceSchemaMake() map[string]*schema.Schema { "net_type": { Type: schema.TypeString, Required: true, + ValidateFunc: validation.StringInSlice([]string{"EXTNET", "VINS"}, false), // observe case while validating Description: "Type of the network for this connection, either EXTNET or VINS.", }, diff --git a/decort/provider.go b/decort/provider.go index a59f1b3..060f0c1 100644 --- a/decort/provider.go +++ b/decort/provider.go @@ -101,7 +101,7 @@ func Provider() *schema.Provider { ResourcesMap: map[string]*schema.Resource{ "decort_resgroup": resourceResgroup(), "decort_kvmvm": resourceCompute(), - // "decort_disk": resourceDisk(), + "decort_disk": resourceDisk(), // "decort_vins": resourceVins(), // "decort_pfw": resourcePfw(), }, diff --git a/decort/resource_compute.go b/decort/resource_compute.go index 9fc550b..0d37cf0 100644 --- a/decort/resource_compute.go +++ b/decort/resource_compute.go @@ -256,6 +256,7 @@ func resourceCompute() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"KVM_X86", "KVM_PPC"}, false), // observe case while validating Description: "Hardware architecture of this compute instance.", }, diff --git a/decort/utility_disk.go b/decort/utility_disk.go index 3bea7a9..8a02a2b 100644 --- a/decort/utility_disk.go +++ b/decort/utility_disk.go @@ -33,7 +33,6 @@ import ( log "github.com/sirupsen/logrus" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - // "github.com/hashicorp/terraform-plugin-sdk/helper/validation" ) diff --git a/decort/utility_rg.go b/decort/utility_rg.go index e8117d3..06301b6 100644 --- a/decort/utility_rg.go +++ b/decort/utility_rg.go @@ -76,8 +76,8 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string // - if resource group name is specifeid -> by RG name and either account ID or account name // // If succeeded, it returns non empty string that contains JSON formatted facts about the - // resource group as returned by cloudspaces/get API call. - // Otherwise it returns empty string and meaningful error. + // resource group as returned by rg/get API call. + // Otherwise it returns empty string and a meaningful error. // // NOTE: As our provider always deletes RGs permanently, there is no "restore" method and // consequently we are not interested in matching RGs in DELETED state. Hence, we call @@ -90,7 +90,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string controller := m.(*ControllerCfg) urlValues := &url.Values{} - rgId, argSet := d.GetOk("rgId") + rgId, argSet := d.GetOk("rg_id") if argSet { // go straight for the RG by its ID log.Debugf("utilityResgroupCheckPresence: locating RG by its ID %d", rgId.(int)) diff --git a/decort/utility_vins.go b/decort/utility_vins.go new file mode 100644 index 0000000..ce93dfd --- /dev/null +++ b/decort/utility_vins.go @@ -0,0 +1,136 @@ +/* +Copyright (c) 2020-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +Technology platfom. + +Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. +*/ + +package decort + +import ( + "encoding/json" + "fmt" + "net/url" + + log "github.com/sirupsen/logrus" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func (ctrl *ControllerCfg) utilityVinsConfigGet(vinsid int) (*VinsRecord, error) { + urlValues := &url.Values{} + urlValues.Add("vinsId", fmt.Sprintf("%d", vinsid)) + vinsFacts, err := ctrl.decortAPICall("POST", VinsGetAPI, urlValues) + if err != nil { + return nil, err + } + + log.Debugf("utilityVinsConfigGet: ready to unmarshal string %q", vinsFacts) + model := &VinsRecord{} + err = json.Unmarshal([]byte(vinsFacts), model) + if err != nil { + return nil, err + } + + log.Debugf("utilityVinsConfigGet: Name %d, account name:ID %s:%d, RG Name:ID %s:%d", + model.Name, model.AccountName, model.AccountID, + model.RgName, model.RgID) + + return model, nil +} + +// On success this function returns a string, as returned by API vins/get, which could be unmarshalled +// into VinsGetResp structure +func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, error) { + // This function tries to locate ViNS by one of the following algorithms depending + // on the parameters passed: + // - if resource group ID is specified -> it looks for a ViNS at the RG level + // - if account ID is specifeid -> it looks for a ViNS at the account level + // + // If succeeded, it returns non empty string that contains JSON formatted facts about the + // ViNS as returned by vins/get API call. + // Otherwise it returns empty string and a meaningful error. + // + // This function does not modify its ResourceData argument, so it is safe to use it as core + // method for the Terraform resource Exists method. + // + + controller := m.(*ControllerCfg) + urlValues := &url.Values{} + + vinsName, argSet := d.GetOk("name") + if !argSet { + // if ViNS name is not set. then we cannot locate ViNS + return "", fmt.Errorf("Cannot check ViNS presence if ViNS name is empty") + } + urlValues.Add("name", vinsName.(string)) + log.Debugf("utilityVinsCheckPresence: locating ViNS %s", vinsName.(string)) + + rgId, rgSet := d.GetOk("rg_id") + if rgSet { + log.Debugf("utilityVinsCheckPresence: limiting ViNS t search to RG ID %d", rgId.(int)) + urlValues.Add("rgId", fmt.Sprintf("%d", rgId.(int))) + } + + accountId, accountSet := d.GetOk("account_id") + if accountSet { + log.Debugf("utilityVinsCheckPresence: limiting ViNS search to Account ID %d", accountId.(int)) + urlValues.Add("accountId", fmt.Sprintf("%d", accountId.(int))) + } + + apiResp, err := controller.decortAPICall("POST", VinsSearchAPI, urlValues) + if err != nil { + return "", err + } + + // log.Debugf("%s", apiResp) + // log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %s", VinsSearchAPI) + model := VinsSearchResp{} + err = json.Unmarshal([]byte(apiResp), &model) + if err != nil { + return "", err + } + + log.Debugf("utilityVinsCheckPresence: traversing decoded Json of length %d", len(model)) + for index, item := range model { + if item.Name == vinsName.(string) { + if ( accountSet && item.AccountID != accountId.(int) ) || + ( rgSet && item.RgID != rgId.(int) ) { + // double check that account ID and Rg ID match, if set in the schema + continue + } + + log.Debugf("utilityVinsCheckPresence: match ViNS name %s / ID %d, account ID %d, RG ID %d at index %d", + item.Name, item.ID, item.AccountID, item.RgID, index) + + // element returned by API vins/search does not contain all information we may need to + // manage ViNS, so we have to get detailed info by calling API vins/get + rqValues := &url.Values{} + rqValues.Add("vinsId", fmt.Sprintf("%d",item.ID)) + apiResp, err = controller.decortAPICall("POST", VinsGetAPI, rqValues) + if err != nil { + return "", err + } + return apiResp, nil + } + } + + return "", fmt.Errorf("Cannot find ViNS name %s. Check name and/or RG ID & Account ID", vinsName.(string)) +}