Compare commits

..

3 Commits

Author SHA1 Message Date
Sergey Shubin svs1370
a5f6c60a71 Improve stability of PFW check presence method 2021-10-11 19:29:17 +03:00
Sergey Shubin svs1370
105273af48 Implement more robust create and delete methods for PFW 2021-10-08 12:17:53 +03:00
Sergey Shubin svs1370
e501417166 PFW data source and resource initial implementation 2021-10-07 23:12:10 +03:00
21 changed files with 675 additions and 275 deletions

View File

@@ -1,7 +1,7 @@
# terraform-provider-decort
Terraform provider for Digital Energy Cloud Orchestration Technology (DECORT) platform
NOTE: provider rc-1.30 is designed for DECORT API 3.7.x. For older API versions please use:
NOTE: provider rc-1.40 is designed for DECORT API 3.7.x. For older API versions please use:
- DECORT API 3.6.x versions - provider version rc-1.10
- DECORT API versions prior to 3.6.0 - Terraform DECS provider (https://github.com/rudecs/terraform-provider-decs)

View File

@@ -132,7 +132,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
}
ret_config.auth_mode_code = MODE_LEGACY
default:
return nil, fmt.Errorf("Unknown authenticator mode %q provided.", ret_config.auth_mode_txt)
return nil, fmt.Errorf("Unknown authenticator mode %s provided.", ret_config.auth_mode_txt)
}
if allow_unverified_ssl {
@@ -205,7 +205,7 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
return "", fmt.Errorf("getOAuth2JWT method called for undefined authorization mode.")
}
if config.auth_mode_code != MODE_OAUTH2 {
return "", fmt.Errorf("getOAuth2JWT method called for incompatible authorization mode %q.", config.auth_mode_txt)
return "", fmt.Errorf("getOAuth2JWT method called for incompatible authorization mode %s.", config.auth_mode_txt)
}
params := url.Values{}
@@ -231,7 +231,7 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
// fmt.Println("response Status:", resp.Status)
// fmt.Println("response Headers:", resp.Header)
// fmt.Println("response Headers:", req.URL)
return "", fmt.Errorf("getOauth2JWT: unexpected status code %d when obtaining JWT from %q for APP_ID %q, request Body %q",
return "", fmt.Errorf("getOauth2JWT: unexpected status code %d when obtaining JWT from %s for APP_ID %s, request Body %s",
resp.StatusCode, req.URL, config.app_id, params_str)
}
defer resp.Body.Close()
@@ -279,7 +279,7 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
return false, err
}
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %q.",
return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %s.",
resp.StatusCode, req.URL)
}
defer resp.Body.Close()
@@ -298,7 +298,7 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
return false, fmt.Errorf("validateLegacyUser method called for undefined authorization mode.")
}
if config.auth_mode_code != MODE_LEGACY {
return false, fmt.Errorf("validateLegacyUser method called for incompatible authorization mode %q.", config.auth_mode_txt)
return false, fmt.Errorf("validateLegacyUser method called for incompatible authorization mode %s.", config.auth_mode_txt)
}
params := url.Values{}
@@ -319,7 +319,7 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
return false, err
}
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("validateLegacyUser: unexpected status code %d when validating legacy user %q against %q.",
return false, fmt.Errorf("validateLegacyUser: unexpected status code %d when validating legacy user %s against %s.",
resp.StatusCode, config.legacy_user, config.controller_url)
}
defer resp.Body.Close()
@@ -335,13 +335,15 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
return true, nil
}
func (config *ControllerCfg) decortAPICall(method string, api_name string, url_values *url.Values) (json_resp string, err error) {
func (config *ControllerCfg) decortAPICall(method string, api_name string, url_values *url.Values) (json_resp string, err error, hrc int) {
// This is a convenience wrapper around standard HTTP request methods that is aware of the
// authorization mode for which the provider was initialized and compiles request accordingly.
hrc = 0 // HTTP Response Code
if config.cc_client == nil {
// this should never happen if ClientConfig was properly called prior to decortAPICall
return "", fmt.Errorf("decortAPICall method called with unconfigured DECORT cloud controller HTTP client.")
return "", fmt.Errorf("decortAPICall method called with unconfigured DECORT cloud controller HTTP client."), 0
}
// Example: to create api_params, one would generally do the following:
@@ -359,7 +361,7 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v
//
if config.auth_mode_code == MODE_UNDEF {
return "", fmt.Errorf("decortAPICall method called for unknown authorization mode.")
return "", fmt.Errorf("decortAPICall method called for unknown authorization mode."), 0
}
if config.auth_mode_code == MODE_LEGACY {
@@ -369,7 +371,7 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v
req, err := http.NewRequest(method, config.controller_url + api_name, strings.NewReader(params_str))
if err != nil {
return "", err
return "", err, 0
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Content-Length", strconv.Itoa(len(params_str)))
@@ -380,29 +382,29 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v
resp, err := config.cc_client.Do(req)
if err != nil {
return "", err
return "", err, 0
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
tmp_body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
return "", err, resp.StatusCode
}
json_resp := Jo2JSON(string(tmp_body))
log.Debugf("decortAPICall: %s %s\n %s", method, api_name, json_resp)
return json_resp, nil
return json_resp, nil, resp.StatusCode
} else {
return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %q with request Body %q",
resp.StatusCode, req.URL, params_str)
return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %s with request Body %s",
resp.StatusCode, req.URL, params_str), resp.StatusCode
}
/*
if resp.StatusCode == StatusServiceUnavailable {
return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %q.", config.auth_mode_txt)
return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %s.", config.auth_mode_txt), resp.StatusCode
}
*/
return "", err
return "", err, resp.StatusCode
}

View File

@@ -28,7 +28,6 @@ import (
"encoding/json"
"fmt"
// "net/url"
// "strconv"
log "github.com/sirupsen/logrus"
@@ -152,7 +151,7 @@ func parseBootDiskId(disks []DiskRecord) uint {
// Parse the list of interfaces from compute/get response into a list of networks
// attached to this compute
func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord, pfwVinsID int, pfwRules []map[string]interface{}) []interface{} {
func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []interface{} {
// return value will be used to d.Set("network") item of dataSourceCompute schema
length := len(ifaces)
log.Debugf("parseComputeInterfacesToNetworks: called for %d ifaces", length)
@@ -168,14 +167,6 @@ func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord, pfwVinsID int, p
elem["ip_address"] = value.IPAddress
elem["mac"] = value.MAC
if value.NetType == "VINS" && len(pfwRules) > 0 && pfwVinsID == value.NetID {
// we have non-empty port forward rules that seem to be relevant to the current
// network segment - set "pfw_rule" element accordingly
log.Debugf("parseComputeInterfacesToNetworks: setting pfw_rule attributes on network block for ViNS ID %d",
value.NetID)
elem["pfw_rule"] = pfwRules
}
// log.Debugf(" element %d: net_id=%d, net_type=%s", i, value.NetID, value.NetType)
result = append(result, elem)
@@ -183,32 +174,6 @@ func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord, pfwVinsID int, p
return result
}
/*
func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []map[string]interface{} {
// return value will be used to d.Set("network") item of dataSourceCompute schema
length := len(ifaces)
log.Debugf("parseComputeInterfacesToNetworks: called for %d ifaces", length)
result := make([]map[string]interface{}, length, length)
for i, value := range ifaces {
elem := make(map[string]interface{})
// Keys in this map should correspond to the Schema definition
// as returned by networkSubresourceSchemaMake()
elem["net_id"] = value.NetID
elem["net_type"] = value.NetType
elem["ip_address"] = value.IPAddress
elem["mac"] = value.MAC
// log.Debugf(" element %d: net_id=%d, net_type=%s", i, value.NetID, value.NetType)
result[i] = elem
}
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{} {
@@ -249,7 +214,7 @@ func parseComputeInterfaces(ifaces []InterfaceRecord) []map[string]interface{} {
return result
}
func flattenCompute(d *schema.ResourceData, compFacts string, pfwVinsID int, pfwRules []map[string]interface{}) error {
func flattenCompute(d *schema.ResourceData, compFacts string) error {
// This function expects that compFacts string contains response from API compute/get,
// i.e. detailed information about compute instance.
//
@@ -292,7 +257,7 @@ func flattenCompute(d *schema.ResourceData, compFacts string, pfwVinsID int, pfw
if len(model.Interfaces) > 0 {
log.Debugf("flattenCompute: calling parseComputeInterfacesToNetworks for %d interfaces", len(model.Interfaces))
if err = d.Set("network", parseComputeInterfacesToNetworks(model.Interfaces, pfwVinsID, pfwRules)); err != nil {
if err = d.Set("network", parseComputeInterfacesToNetworks(model.Interfaces)); err != nil {
return err
}
}
@@ -308,23 +273,15 @@ func flattenCompute(d *schema.ResourceData, compFacts string, pfwVinsID int, pfw
}
func dataSourceComputeRead(d *schema.ResourceData, m interface{}) error {
compID, compFacts, err := utilityComputeCheckPresence(d, m)
compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" {
// if empty compFacts is returned from utilityComputeCheckPresence and err=nil
// it means that there is no such Compute;
// In any other case non-nil error will be reported.
// if empty string is returned from utilityComputeCheckPresence then there is no
// such Compute and err tells so - just return it to the calling party
d.SetId("") // ensure ID is empty
return err
}
vinsID, pfwRules, err := utilityComputePfwGet(compID, m)
if err != nil {
log.Errorf("dataSourceComputeRead: there was error calling utilityComputePfwGet for compute ID %s: %s",
d.Id(), err)
return err
}
return flattenCompute(d, compFacts, vinsID, pfwRules)
return flattenCompute(d, compFacts)
}
func dataSourceCompute() *schema.Resource {

View File

@@ -45,7 +45,7 @@ func dataSourceImageRead(d *schema.ResourceData, m interface{}) error {
if accSet {
url_values.Add("accountId", fmt.Sprintf("%d", accId.(int)))
}
body_string, err := controller.decortAPICall("POST", ImagesListAPI, url_values)
body_string, err, _ := controller.decortAPICall("POST", ImagesListAPI, url_values)
if err != nil {
return err
}

144
decort/data_source_pfw.go Normal file
View File

@@ -0,0 +1,144 @@
/*
Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
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.
*/
package decort
import (
"encoding/json"
"fmt"
// "hash/fnv"
log "github.com/sirupsen/logrus"
// "net/url"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)
func flattenPfw(d *schema.ResourceData, pfwFacts string) error {
// NOTE: this function modifies ResourceData argument - as such it should never be called
// from resourcePfwExists(...) method
pfwRecord := ComputePfwListResp{}
err := json.Unmarshal([]byte(pfwFacts), &pfwRecord)
if err != nil {
return err
}
log.Debugf("flattenPfw: decoded %d PFW rules for compute ID %s on ViNS ID %d",
len(pfwRecord.Rules), pfwRecord.Header.VinsID, pfwRecord.Header.VinsID)
/*
Here it gets a little bit interesting.
Unlike compute or disk, port forwaring rules are NOT represented by any cloud
platform resource, which might have had a unique ID. They are just a subset of
rules in the list maintained by the corresponding ViNS instance. However,
Terraform needs a unique ID for each resource it manages so that it could be
stored in the state file and retrieved for use.
Therefore we need to make up an ID and supply it to Terraform in a standard
way (i.e. by calling d.SetId(...)).
Fortunately, a combination of Compute ID and ViNS ID with GW VNF, where this
compute is plugged in, makes a unique string, so we use it as an ID for
the PFW ruleset.
The following few lines are legacy from the first attempt to make an ID
as a hash of concatenated Compute ID & ViNS ID, but it did not work as
expected for a number of reasons, which explanation is not a primary
intent of the comment in the source code.
combo := fmt.Sprintf("%d:%d", compId.(int), pfwRecord.ViNS.VinsID)
hasher := fnv.New32a()
hasher.Write([]byte(combo))
d.SetId(fmt.Sprintf("%d", hasher.Sum32()))
*/
// set ID of this PFW rule set as "compute_id:vins_id"
d.SetId(fmt.Sprintf("%d:%d", pfwRecord.Header.ComputeID, pfwRecord.Header.VinsID))
log.Debugf("flattenPfw: PFW rule set ID %s", d.Id())
d.Set("compute_id", pfwRecord.Header.ComputeID)
d.Set("vins_id", pfwRecord.Header.VinsID)
pfwRulesList := []interface{}{}
for _, runner := range pfwRecord.Rules {
rule := map[string]interface{}{
"pub_port_start": runner.PublicPortStart,
"pub_port_end": runner.PublicPortEnd,
"local_port": runner.LocalPort,
"proto": runner.Protocol,
"rule_id": runner.ID,
}
pfwRulesList = append(pfwRulesList, rule)
}
if err = d.Set("rule", pfwRulesList); err != nil {
return err
}
return nil
}
func dataSourcePfwRead(d *schema.ResourceData, m interface{}) error {
pfwFacts, err := utilityPfwCheckPresence(d, m)
if pfwFacts == "" {
// if empty string is returned from dataSourcePfwRead then we got no
// PFW rules. It could also be because there was some error, which
// is indicated by non-nil err value
d.SetId("") // ensure ID is empty in this case anyway
return err
}
return flattenPfw(d, pfwFacts)
}
func dataSourcePfw() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
Read: dataSourcePfwRead,
Timeouts: &schema.ResourceTimeout{
Read: &Timeout30s,
Default: &Timeout60s,
},
Schema: map[string]*schema.Schema{
"compute_id": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
Description: "ID of the compute instance to configure port forwarding rules for.",
},
"vins_id": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
Description: "ID of the ViNS to configure port forwarding rules on. Compute must be already plugged into this ViNS and ViNS must have external network connection.",
},
// TODO: consider making "rule" attribute Required with MinItems = 1
"rule": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: rulesSubresourceSchemaMake(),
},
Description: "Port forwarding rule. You may specify several rules, one in each such block.",
},
},
}
}

View File

@@ -444,15 +444,20 @@ const AccountsListAPI = "/restmachine/cloudapi/account/list" // returns list of
type AccountsListResp []AccountRecord
//
// structures related to /cloudapi/compute/pfwlLst API
// structures related to /cloudapi/portforwarding/list API
//
// Note that if there are port forwarding rules for compute, then compute/pfwList response
// will contain a list which starts with prefix (see PfwPrefixRecord) and then contains
// one or more rule records (see PfwRuleRecord)
// Note the specifics of compute/pfwList response in API 3.7.x (this may be changed in the future):
// 1) if there are no PFW rules and compute is not connected to any PFW-able ViNS
// the response will be empty string
// 2) if there are no PFW rules but compute is connected to a PFW-able ViNS
// the response will contain a list with a single element - prefix (see PfwPrefixRecord)
// 3) if there are port forwarding rules, the response will contain a list which starts
// with prefix (see PfwPrefixRecord) and then followed by one or more rule records
// (see PfwRuleRecord)
type PfwPrefixRecord struct {
VinsID int `json:"vinsId"`
VinsName string `json:"vinsName"`
ComputeID int `json:"computeId"`
}
type PfwRuleRecord struct {
ID int `json:"id"`
@@ -464,6 +469,11 @@ type PfwRuleRecord struct {
ComputeID int `json:"vmId"`
}
type ComputePfwListResp struct {
Header PfwPrefixRecord `json:"header"`
Rules []PfwRuleRecord `json:"rules"`
}
const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList"
const ComputePfwAddAPI = "/restmachine/cloudapi/compute/pfwAdd"
@@ -544,8 +554,6 @@ type VnfRecord struct {
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
Status string `json:"status"`
TechStatus string `json:"techStatus"`
}
type VnfGwConfigRecord struct { // describes GW VNF config structure inside ViNS, as returned by API vins/get
@@ -554,14 +562,6 @@ type VnfGwConfigRecord struct { // describes GW VNF config structure inside ViNS
ExtNetMask int `json:"ext_net_mask"`
DefaultGW string `json:"default_gw"`
}
type NatRuleRecord struct { // describes one NAT rule, a list of such rules is maintained inside VNF NAT Config
}
type VnfNatConfigRecord struct { // describes NAT VNF config structure inside ViNS, as returned by API vins/get
Netmask int `json:"netmask"`
Network string `json:"network"` // just network address, no mask, e.g. "192.168.1.0"
Rules []NatRuleRecord `json:"rules"`
}
type VinsRecord struct { // represents part of the response from API vins/get
ID int `json:"id"`
Name string `json:"name"`

View File

@@ -129,7 +129,7 @@ func networkSubresourceSchemaMake() map[string]*schema.Schema {
Optional: true,
Computed: true,
DiffSuppressFunc: networkSubresIPAddreDiffSupperss,
Description: "Optional IP address to assign to this connection. This IP should belong to the selected network and available for use.",
Description: "Optional IP address to assign to this connection. This IP should belong to the selected network and free for use.",
},
"mac": {
@@ -138,15 +138,6 @@ func networkSubresourceSchemaMake() map[string]*schema.Schema {
Description: "MAC address associated with this connection. MAC address is assigned automatically.",
},
"pfw_rule": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: pfwSubresourceSchemaMake(),
},
Description: "Port forwarding rule to setup for this connection. You may specify several such blocks, one for each rule.",
},
}
return rets
}

View File

@@ -103,7 +103,7 @@ func Provider() *schema.Provider {
"decort_kvmvm": resourceCompute(),
"decort_disk": resourceDisk(),
"decort_vins": resourceVins(),
// "decort_k8s": resourceK8s(),
"decort_pfw": resourcePfw(),
},
DataSourcesMap: map[string]*schema.Resource{
@@ -113,8 +113,7 @@ func Provider() *schema.Provider {
"decort_image": dataSourceImage(),
"decort_disk": dataSourceDisk(),
"decort_vins": dataSourceVins(),
// "decort_k8ci": dataSourceK8ci(),
// "decort_k8s": dataSourceK8s(),
"decort_pfw": dataSourcePfw(),
},
ConfigureFunc: providerConfigure,

View File

@@ -102,7 +102,7 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error {
}
}
apiResp, err := controller.decortAPICall("POST", computeCreateAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", computeCreateAPI, urlValues)
if err != nil {
return err
}
@@ -166,7 +166,7 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error {
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)
apiResp, err, _ = controller.decortAPICall("POST", ComputeStartAPI, reqValues)
if err != nil {
return err
}
@@ -184,8 +184,9 @@ func resourceComputeRead(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceComputeRead: called for Compute name %s, RG ID %d",
d.Get("name").(string), d.Get("rg_id").(int))
compID, compFacts, err := utilityComputeCheckPresence(d, m)
compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" {
d.SetId("")
if err != nil {
return err
}
@@ -193,14 +194,7 @@ func resourceComputeRead(d *schema.ResourceData, m interface{}) error {
return nil
}
vinsID, pfwRules, err := utilityComputePfwGet(compID, m)
if err != nil {
log.Errorf("resourceComputeRead: there was error calling utilityComputePfwGet for compute ID %s: %s",
d.Id(), err)
return err
}
if err = flattenCompute(d, compFacts, vinsID, pfwRules); err != nil {
if err = flattenCompute(d, compFacts); err != nil {
return err
}
@@ -250,7 +244,7 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceComputeUpdate: changing CPU %d -> %d and/or RAM %d -> %d",
oldCpu.(int), newCpu.(int),
oldRam.(int), newRam.(int))
_, err := controller.decortAPICall("POST", ComputeResizeAPI, params)
_, err, _ := controller.decortAPICall("POST", ComputeResizeAPI, params)
if err != nil {
return err
}
@@ -266,7 +260,7 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error {
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))
_, err := controller.decortAPICall("POST", DisksResizeAPI, params)
_, err, _ := controller.decortAPICall("POST", DisksResizeAPI, params)
if err != nil {
return err
}
@@ -306,7 +300,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceComputeDelete: called for Compute name %s, RG ID %d",
d.Get("name").(string), d.Get("rg_id").(int))
_, compFacts, err := utilityComputeCheckPresence(d, m)
compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" {
// the target Compute does not exist - in this case according to Terraform best practice
// we exit from Destroy method without error
@@ -333,7 +327,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error {
detachParams.Add("computeId", d.Id())
detachParams.Add("diskId", fmt.Sprintf("%d", diskFacts.ID))
_, err = controller.decortAPICall("POST", ComputeDiskDetachAPI, detachParams)
_, err, _ = controller.decortAPICall("POST", ComputeDiskDetachAPI, detachParams)
if err != nil {
// We do not fail compute deletion on data disk detach errors
log.Errorf("resourceComputeDelete: error when detaching Disk ID %d: %s", diskFacts.ID, err)
@@ -346,7 +340,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error {
params.Add("permanently", "1")
// TODO: this is for the upcoming API update - params.Add("detachdisks", "1")
_, err = controller.decortAPICall("POST", ComputeDeleteAPI, params)
_, err, _ = controller.decortAPICall("POST", ComputeDeleteAPI, params)
if err != nil {
return err
}
@@ -359,7 +353,7 @@ func resourceComputeExists(d *schema.ResourceData, m interface{}) (bool, error)
log.Debugf("resourceComputeExist: called for Compute name %s, RG ID %d",
d.Get("name").(string), d.Get("rg_id").(int))
_, compFacts, err := utilityComputeCheckPresence(d, m)
compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" {
if err != nil {
return false, err

View File

@@ -55,7 +55,7 @@ func resourceDiskCreate(d *schema.ResourceData, m interface{}) error {
urlValues.Add("description", argVal.(string))
}
apiResp, err := controller.decortAPICall("POST", DisksCreateAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", DisksCreateAPI, urlValues)
if err != nil {
return err
}
@@ -105,7 +105,7 @@ func resourceDiskUpdate(d *schema.ResourceData, m interface{}) error {
sizeParams := &url.Values{}
sizeParams.Add("diskId", d.Id())
sizeParams.Add("size", fmt.Sprintf("%d", newSize.(int)))
_, err := controller.decortAPICall("POST", DisksResizeAPI, sizeParams)
_, err, _ := controller.decortAPICall("POST", DisksResizeAPI, sizeParams)
if err != nil {
return err
}
@@ -121,7 +121,7 @@ func resourceDiskUpdate(d *schema.ResourceData, m interface{}) error {
renameParams := &url.Values{}
renameParams.Add("diskId", d.Id())
renameParams.Add("name", newName.(string))
_, err := controller.decortAPICall("POST", DisksRenameAPI, renameParams)
_, err, _ := controller.decortAPICall("POST", DisksRenameAPI, renameParams)
if err != nil {
return err
}
@@ -171,7 +171,7 @@ func resourceDiskDelete(d *schema.ResourceData, m interface{}) error {
params.Add("permanently", "1")
controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", DisksDeleteAPI, params)
_, err, _ = controller.decortAPICall("POST", DisksDeleteAPI, params)
if err != nil {
return err
}

212
decort/resource_pfw.go Normal file
View File

@@ -0,0 +1,212 @@
/*
Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
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.
*/
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"
)
func resourcePfwCreate(d *schema.ResourceData, m interface{}) error {
compId := d.Get("compute_id")
rules_set, ok := d.GetOk("rule")
if !ok || rules_set.(*schema.Set).Len() == 0 {
log.Debugf("resourcePfwCreate: empty new PFW rules set requested for compute ID %d - nothing to create", compId.(int))
return nil
}
log.Debugf("resourcePfwCreate: ready to setup %d PFW rules for compute ID %d",
rules_set.(*schema.Set).Len(), compId.(int))
controller := m.(*ControllerCfg)
apiErrCount := 0
var lastSavedError error
for _, runner := range rules_set.(*schema.Set).List() {
rule := runner.(map[string]interface{})
params := &url.Values{}
params.Add("computeId", fmt.Sprintf("%d", compId.(int)))
params.Add("publicPortStart", fmt.Sprintf("%d", rule["pub_port_start"].(int)))
params.Add("publicPortEnd", fmt.Sprintf("%d", rule["pub_port_end"].(int)))
params.Add("localBasePort", fmt.Sprintf("%d", rule["local_port"].(int)))
params.Add("proto", rule["proto"].(string))
log.Debugf("resourcePfwCreate: ready to add rule %d:%d -> %d %s for Compute ID %d",
rule["pub_port_start"].(int),rule["pub_port_end"].(int),
rule["local_port"].(int), rule["proto"].(string),
compId.(int))
_, err, _ := controller.decortAPICall("POST", ComputePfwAddAPI, params)
if err != nil {
log.Errorf("resourcePfwCreate: error adding rule %d:%d -> %d %s for Compute ID %d: %s",
rule["pub_port_start"].(int),rule["pub_port_end"].(int),
rule["local_port"].(int), rule["proto"].(string),
compId.(int),
err)
apiErrCount++
lastSavedError = err
}
}
if apiErrCount > 0 {
log.Errorf("resourcePfwCreate: there were %d error(s) adding PFW rules to Compute ID %s. Last error was: %s",
apiErrCount, compId.(int), lastSavedError)
return lastSavedError
}
return resourcePfwRead(d, m)
}
func resourcePfwRead(d *schema.ResourceData, m interface{}) error {
pfwFacts, err := utilityPfwCheckPresence(d, m)
if pfwFacts == "" {
// if empty string is returned from dataSourcePfwRead then we got no
// PFW rules. It could also be because there was some error, which
// is indicated by non-nil err value
d.SetId("") // ensure ID is empty in this case anyway
return err
}
return flattenPfw(d, pfwFacts)
}
func resourcePfwUpdate(d *schema.ResourceData, m interface{}) error {
// TODO: update not implemented yet
compId := d.Get("compute_id")
return fmt.Errorf("resourcePfwUpdate: method is not implemented yet (Compute ID %d)", compId.(int))
// return resourcePfwRead(d, m)
}
func resourcePfwDelete(d *schema.ResourceData, m interface{}) error {
compId := d.Get("compute_id")
rules_set, ok := d.GetOk("rule")
if !ok || rules_set.(*schema.Set).Len() == 0 {
log.Debugf("resourcePfwDelete: no PFW rules defined for compute ID %d - nothing to delete", compId.(int))
return nil
}
log.Debugf("resourcePfwDelete: ready to delete %d PFW rules from compute ID %d",
rules_set.(*schema.Set).Len(), compId.(int))
controller := m.(*ControllerCfg)
apiErrCount := 0
var lastSavedError error
for _, runner := range rules_set.(*schema.Set).List() {
rule := runner.(map[string]interface{})
params := &url.Values{}
params.Add("computeId", fmt.Sprintf("%d", compId.(int)))
params.Add("ruleId", fmt.Sprintf("%d", rule["rule_id"].(int)))
log.Debugf("resourcePfwCreate: ready to delete rule ID%s (%d:%d -> %d %s) from Compute ID %d",
rule["rule_id"].(int),
rule["pub_port_start"].(int),rule["pub_port_end"].(int),
rule["local_port"].(int), rule["proto"].(string),
compId.(int))
_, err, _ := controller.decortAPICall("POST", ComputePfwDelAPI, params)
if err != nil {
log.Errorf("resourcePfwDelete: error deleting rule ID %d (%d:%d -> %d %s) from Compute ID %d: %s",
rule["rule_id"].(int),
rule["pub_port_start"].(int),rule["pub_port_end"].(int),
rule["local_port"].(int), rule["proto"].(string),
compId.(int),
err)
apiErrCount++
lastSavedError = err
}
}
if apiErrCount > 0 {
log.Errorf("resourcePfwDelete: there were %d error(s) when deleting PFW rules from Compute ID %s. Last error was: %s",
apiErrCount, compId.(int), lastSavedError)
return lastSavedError
}
return nil
}
func resourcePfwExists(d *schema.ResourceData, m interface{}) (bool, error) {
// Reminder: according to Terraform rules, this function should not modify its ResourceData argument
log.Debugf("resourcePfwExists: called for Compute ID %d", d.Get("compute_id").(int))
pfwFacts, err := utilityPfwCheckPresence(d, m)
if pfwFacts == "" {
if err != nil {
return false, err
}
return false, nil
}
return true, nil
}
func resourcePfw() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
Create: resourcePfwCreate,
Read: resourcePfwRead,
Update: resourcePfwUpdate,
Delete: resourcePfwDelete,
Exists: resourcePfwExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Timeouts: &schema.ResourceTimeout{
Create: &Timeout180s,
Read: &Timeout30s,
Update: &Timeout180s,
Delete: &Timeout60s,
Default: &Timeout60s,
},
Schema: map[string]*schema.Schema{
"compute_id": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
Description: "ID of the compute instance to configure port forwarding rules for.",
},
"vins_id": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
Description: "ID of the ViNS to configure port forwarding rules on. Compute must be already plugged into this ViNS and ViNS must have external network connection.",
},
// TODO: consider making "rule" attribute Required with MinItems = 1 to prevent
// empty PFW list definition
"rule": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: rulesSubresourceSchemaMake(),
},
Description: "Port forwarding rule. You may specify several rules, one in each such block.",
},
},
}
}

View File

@@ -115,7 +115,7 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
url_values.Add("extIp", ext_ip.(string))
}
api_resp, err := controller.decortAPICall("POST", ResgroupCreateAPI, url_values)
api_resp, err, _ := controller.decortAPICall("POST", ResgroupCreateAPI, url_values)
if err != nil {
return err
}
@@ -233,7 +233,7 @@ func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error {
if do_general_update {
log.Debugf("resourceResgroupUpdate: detected delta between new and old RG specs - updating the RG")
_, err := controller.decortAPICall("POST", ResgroupUpdateAPI, url_values)
_, err, _ := controller.decortAPICall("POST", ResgroupUpdateAPI, url_values)
if err != nil {
return err
}
@@ -264,7 +264,7 @@ func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error {
url_values.Add("reason", "Destroyed by DECORT Terraform provider")
controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", ResgroupDeleteAPI, url_values)
_, err, _ = controller.decortAPICall("POST", ResgroupDeleteAPI, url_values)
if err != nil {
return err
}

View File

@@ -106,7 +106,7 @@ func resourceVinsCreate(d *schema.ResourceData, m interface{}) error {
urlValues.Add("desc", argVal.(string))
}
apiResp, err := controller.decortAPICall("POST", apiToCall, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", apiToCall, urlValues)
if err != nil {
return err
}
@@ -154,7 +154,7 @@ func resourceVinsUpdate(d *schema.ResourceData, m interface{}) error {
if oldExtNetId.(int) > 0 {
// there was preexisting external net connection - disconnect ViNS
_, err := controller.decortAPICall("POST", VinsExtNetDisconnectAPI, extnetParams)
_, err, _ := controller.decortAPICall("POST", VinsExtNetDisconnectAPI, extnetParams)
if err != nil {
return err
}
@@ -163,7 +163,7 @@ func resourceVinsUpdate(d *schema.ResourceData, m interface{}) error {
if newExtNedId.(int) > 0 {
// new external network connection requested - connect ViNS
extnetParams.Add("netId", fmt.Sprintf("%d", newExtNedId.(int)))
_, err := controller.decortAPICall("POST", VinsExtNetConnectAPI, extnetParams)
_, err, _ := controller.decortAPICall("POST", VinsExtNetConnectAPI, extnetParams)
if err != nil {
return err
}
@@ -196,7 +196,7 @@ func resourceVinsDelete(d *schema.ResourceData, m interface{}) error {
params.Add("permanently", "1") // delete ViNS immediately bypassing recycle bin
controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", VinsDeleteAPI, params)
_, err, _ = controller.decortAPICall("POST", VinsDeleteAPI, params)
if err != nil {
return err
}

View File

@@ -29,12 +29,11 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)
// This is subresource of network subresource of compute resource used
// This is rules subresource of PFW resource used
// when creating/managing port forwarding rules for a compute connected
// to the corresponding network
// It only applies to a ViNS connection AND to a ViNS with external network connection
func pfwSubresourceSchemaMake() map[string]*schema.Schema {
func rulesSubresourceSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"pub_port_start": {
Type: schema.TypeInt,
@@ -65,6 +64,14 @@ func pfwSubresourceSchemaMake() map[string]*schema.Schema {
Description: "Protocol for this rule. Could be either tcp or udp.",
},
// the rest are computed
"rule_id": {
Type: schema.TypeInt,
Computed: true,
Description: "Rule ID as assigned by the cloud platform.",
},
}
return rets
}

View File

@@ -44,7 +44,7 @@ func utilityAccountCheckPresence(d *schema.ResourceData, m interface{}) (string,
// get Account right away by its ID
log.Debugf("utilityAccountCheckPresence: locating Account by its ID %d", accId.(int))
urlValues.Add("accountId", fmt.Sprintf("%d", accId.(int)))
apiResp, err := controller.decortAPICall("POST", AccountsGetAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", AccountsGetAPI, urlValues)
if err != nil {
return "", err
}
@@ -57,7 +57,7 @@ func utilityAccountCheckPresence(d *schema.ResourceData, m interface{}) (string,
return "", fmt.Errorf("Cannot check account presence if name is empty and no account ID specified")
}
apiResp, err := controller.decortAPICall("POST", AccountsListAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", AccountsListAPI, urlValues)
if err != nil {
return "", err
}
@@ -131,7 +131,7 @@ func utilityGetAccountIdBySchema(d *schema.ResourceData, m interface{}) (int, er
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
apiResp, err := controller.decortAPICall("POST", AccountsListAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", AccountsListAPI, urlValues)
if err != nil {
return 0, err
}

View File

@@ -29,7 +29,6 @@ import (
"fmt"
"net/url"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
@@ -63,7 +62,7 @@ func (ctrl *ControllerCfg) utilityComputeExtraDisksConfigure(d *schema.ResourceD
urlValues := &url.Values{}
urlValues.Add("computeId", d.Id())
urlValues.Add("diskId", fmt.Sprintf("%d", disk.(int)))
_, err := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues)
_, err, _ := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues)
if err != nil {
// failed to attach extra disk - partial resource update
apiErrCount++
@@ -86,7 +85,7 @@ func (ctrl *ControllerCfg) utilityComputeExtraDisksConfigure(d *schema.ResourceD
urlValues := &url.Values{}
urlValues.Add("computeId", d.Id())
urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int)))
_, err := ctrl.decortAPICall("POST", ComputeDiskDetachAPI, urlValues)
_, err, _ := ctrl.decortAPICall("POST", ComputeDiskDetachAPI, urlValues)
if err != nil {
// failed to detach disk - there will be partial resource update
log.Errorf("utilityComputeExtraDisksConfigure: failed to detach disk ID %d from Compute ID %s: %s", diskId.(int), d.Id(), err)
@@ -101,7 +100,7 @@ func (ctrl *ControllerCfg) utilityComputeExtraDisksConfigure(d *schema.ResourceD
urlValues := &url.Values{}
urlValues.Add("computeId", d.Id())
urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int)))
_, err := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues)
_, err, _ := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues)
if err != nil {
// failed to attach disk - there will be partial resource update
log.Errorf("utilityComputeExtraDisksConfigure: failed to attach disk ID %d to Compute ID %s: %s", diskId.(int), d.Id(), err)
@@ -146,48 +145,11 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat
if ipSet {
urlValues.Add("ipAddr", ipaddr.(string))
}
log.Debugf("utilityComputeNetworksConfigure: ready to add network type %s ID %d for Compute ID %s",
net_data["net_type"].(string), net_data["net_id"].(int), d.Id())
_, err := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues)
_, err, _ := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues)
if err != nil {
// failed to attach network - partial resource update
apiErrCount++
lastSavedError = err
continue
}
if pfw_rules, ok := net_data["pfw_rule"]; ok {
// fool-proof - port forwarding is applicable to VINS type networks only! And only to
// those ViNSes that have active GW VNF, but here we check for VINS type only, the rest
// will be validated by the cloud platform
if net_data["net_type"].(string) != "VINS" {
log.Errorf("utilityComputeNetworksConfigure: encountered port forward rules specs in network block of type %s for Compute ID %s",
net_data["net_type"].(string), d.Id())
apiErrCount++
lastSavedError = err
continue
}
log.Debugf("utilityComputeNetworksConfigure: found port forward rules specs in network block ID %d for Compute ID %s",
net_data["net_id"].(int), d.Id())
for _, rule_runner := range pfw_rules.(*schema.Set).List() {
pfwValues := &url.Values{}
rule := rule_runner.(map[string]interface{})
pfwValues.Add("computeId", d.Id())
pfwValues.Add("publicPortStart", fmt.Sprintf("%d", rule["pub_port_start"].(int)))
pfwValues.Add("publicPortEnd", fmt.Sprintf("%d", rule["pub_port_end"].(int)))
pfwValues.Add("localBasePort", fmt.Sprintf("%d", rule["local_port"].(int)))
pfwValues.Add("proto", rule["proto"].(string))
log.Debugf("utilityComputeNetworksConfigure: ready to add pfw rule %d:%d -> %d proto %s for Compute ID %s",
rule["pub_port_start"].(int), rule["pub_port_end"].(int),
rule["proto"].(string), d.Id())
_, err := ctrl.decortAPICall("POST", ComputePfwAddAPI, pfwValues)
if err != nil {
// failed to add port forward rule - partial resource update
apiErrCount++
lastSavedError = err
}
}
}
}
@@ -207,7 +169,7 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat
urlValues.Add("computeId", d.Id())
urlValues.Add("ipAddr", net_data["ip_address"].(string))
urlValues.Add("mac", net_data["mac"].(string))
_, err := ctrl.decortAPICall("POST", ComputeNetDetachAPI, urlValues)
_, err, _ := ctrl.decortAPICall("POST", ComputeNetDetachAPI, urlValues)
if err != nil {
// failed to detach this network - there will be partial resource update
log.Errorf("utilityComputeNetworksConfigure: failed to detach net ID %d of type %s from Compute ID %s: %s",
@@ -228,7 +190,7 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat
if net_data["ip_address"].(string) != "" {
urlValues.Add("ipAddr", net_data["ip_address"].(string))
}
_, err := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues)
_, err, _ := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues)
if err != nil {
// failed to attach this network - there will be partial resource update
log.Errorf("utilityComputeNetworksConfigure: failed to attach net ID %d of type %s to Compute ID %s: %s",
@@ -247,11 +209,7 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat
return nil
}
//func (ctrl *ControllerCfg) utilityComputePfwConfigure(d *schema.ResourceData, do_delta bool) error {
//}
func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (int, string, error) {
func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (string, error) {
// This function tries to locate Compute by one of the following approaches:
// - if compute_id is specified - locate by compute ID
// - if compute_name is specified - locate by a combination of compute name and resource
@@ -286,29 +244,29 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (int, st
// compute ID is specified, try to get compute instance straight by this ID
log.Debugf("utilityComputeCheckPresence: locating compute by its ID %d", theId)
urlValues.Add("computeId", fmt.Sprintf("%d", theId))
computeFacts, err := controller.decortAPICall("POST", ComputeGetAPI, urlValues)
computeFacts, err, _ := controller.decortAPICall("POST", ComputeGetAPI, urlValues)
if err != nil {
return 0, "", err
return "", err
}
return theId, computeFacts, nil
return computeFacts, nil
}
// ID was not set in the schema upon entering this function - work through Compute name
// and RG ID
computeName, argSet := d.GetOk("name")
if !argSet {
return 0, "", fmt.Errorf("Cannot locate compute instance if name is empty and no compute ID specified")
return "", fmt.Errorf("Cannot locate compute instance if name is empty and no compute ID specified")
}
rgId, argSet := d.GetOk("rg_id")
if !argSet {
return 0, "", fmt.Errorf("Cannot locate compute by name %s if no resource group ID is set", computeName.(string))
return "", fmt.Errorf("Cannot locate compute by name %s if no resource group ID is set", computeName.(string))
}
urlValues.Add("rgId", fmt.Sprintf("%d", rgId))
apiResp, err := controller.decortAPICall("POST", RgListComputesAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", RgListComputesAPI, urlValues)
if err != nil {
return 0, "", err
return "", err
}
log.Debugf("utilityComputeCheckPresence: ready to unmarshal string %s", apiResp)
@@ -316,7 +274,7 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (int, st
computeList := RgListComputesResp{}
err = json.Unmarshal([]byte(apiResp), &computeList)
if err != nil {
return 0, "", err
return "", err
}
// log.Printf("%#v", computeList)
@@ -328,85 +286,13 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (int, st
// we found the Compute we need - now get detailed information via compute/get API
cgetValues := &url.Values{}
cgetValues.Add("computeId", fmt.Sprintf("%d", item.ID))
apiResp, err = controller.decortAPICall("POST", ComputeGetAPI, cgetValues)
apiResp, err, _ = controller.decortAPICall("POST", ComputeGetAPI, cgetValues)
if err != nil {
return 0, "", err
return "", err
}
// NOTE: compute ID is unsigned int in the platform. Here we convert it to int, which may have
// unwanted side effects when the number of compute instances grows
return int(item.ID), apiResp, nil
return apiResp, nil
}
}
return 0, "", nil // there should be no error if Compute does not exist
return "", nil // there should be no error if Compute does not exist
}
// This function reads port forwards from a specified compute and returns them (if any) in a
// form of a list of maps of interfaces suitable to be used for d.Set("pfw_rule") on the
// network block, corresponding to the ViNS these rules belong to. To simlify this network
// block identification among multiple blocks of the same compute this function also
// returns the ID of the ViNS associated with listed rules.
func utilityComputePfwGet(compId int, m interface{}) (int, []map[string]interface{}, error) {
// If there is an error either reading portforward rules from the cloud or parsing them, error is
// returned.
// In case there are no portforwarding rules for this compute, err = nil and rule record list is empty.
// Otherwise, both prefix record and rule record list contain meaningful data.
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
pfwPrefix := PfwPrefixRecord{}
pfwRules := []PfwRuleRecord{}
pfwRulesList := []map[string]interface{}{}
urlValues.Add("computeId", fmt.Sprintf("%d", compId))
apiResp, err := controller.decortAPICall("POST", ComputePfwListAPI, urlValues)
if err != nil {
return 0, pfwRulesList, err
}
if apiResp == "" {
// No port forward rules defined for this compute
return 0, pfwRulesList, nil
}
log.Debugf("utilityComputePfwGet: ready to split API response string %s", apiResp)
twoParts := strings.SplitN(apiResp, "},", 2)
if len(twoParts) != 2 {
log.Errorf("utilityComputePfwGet: non-empty pfwList response for compute ID %d failed to split into 2 fragments (got %d)", compId, len(twoParts))
return 0, pfwRulesList, fmt.Errorf("Non-empty pfwList response failed to split into 2 fragments")
}
prefixResp := strings.TrimSuffix(strings.TrimPrefix(twoParts[0], "["), ",") + "}"
log.Debugf("utilityComputePfwGet: ready to unmarshal prefix part %s", prefixResp)
err = json.Unmarshal([]byte(prefixResp), &pfwPrefix)
if err != nil {
log.Errorf("utilityComputePfwGet: failed to unmarshal prefix part of API response: %s", err)
return 0, pfwRulesList, err
}
rulesResp := "[" + twoParts[1]
log.Debugf("utilityComputePfwGet: ready to unmarshal rules part %s", rulesResp)
err = json.Unmarshal([]byte(rulesResp), &pfwRules)
if err != nil {
log.Errorf("utilityComputePfwGet: failed to unmarshal rules part of API response: %s", err)
return 0, pfwRulesList, err
}
log.Debugf("utilityComputePfwGet: successfully read %d port forward rules for Compute ID %d, ViNS ID %d",
len(pfwRules), compId, pfwPrefix.VinsID)
for _, runner := range pfwRules {
rule := map[string]interface{}{
"pub_port_start": runner.PublicPortStart,
"pub_port_end": runner.PublicPortEnd,
"local_port": runner.LocalPort,
"proto": runner.Protocol,
}
pfwRulesList = append(pfwRulesList, rule)
}
return pfwPrefix.VinsID, pfwRulesList, nil
}

View File

@@ -75,7 +75,7 @@ func utilityDiskCheckPresence(d *schema.ResourceData, m interface{}) (string, er
// disk ID is specified, try to get disk instance straight by this ID
log.Debugf("utilityDiskCheckPresence: locating disk by its ID %d", theId)
urlValues.Add("diskId", fmt.Sprintf("%d", theId))
diskFacts, err := controller.decortAPICall("POST", DisksGetAPI, urlValues)
diskFacts, err, _ := controller.decortAPICall("POST", DisksGetAPI, urlValues)
if err != nil {
return "", err
}
@@ -98,7 +98,7 @@ func utilityDiskCheckPresence(d *schema.ResourceData, m interface{}) (string, er
}
urlValues.Add("accountId", fmt.Sprintf("%d", validatedAccountId))
diskFacts, err := controller.decortAPICall("POST", DisksListAPI, urlValues)
diskFacts, err, _ := controller.decortAPICall("POST", DisksListAPI, urlValues)
if err != nil {
return "", err
}

View File

@@ -41,7 +41,7 @@ func (controller *ControllerCfg) utilityLocationGetDefaultGridID() (int, error)
urlValues := &url.Values{}
log.Debug("utilityLocationGetDefaultGridID: retrieving locations list")
apiResp, err := controller.decortAPICall("POST", LocationsListAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", LocationsListAPI, urlValues)
if err != nil {
return 0, err
}

208
decort/utility_pfw.go Normal file
View File

@@ -0,0 +1,208 @@
/*
Copyright (c) 2020-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
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"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)
func utilityPfwCheckPresence(d *schema.ResourceData, m interface{}) (string, 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.
//
// The string returned by this method mimics response of an imaginary API that will
// report PFW rules (if any) as following:
// {
// "header": {
// "computeId": <int>,
// "vinsId": <int>,
// },
// "rules": [
// {
// "id": <int>,
// "localPort": <int>,
// "protocol": <int>,
// "publicPortStart": <int>,
// "publicPortEnd": <int>,
// "vmId": <int>,
// },
// {
// ...
// },
// ],
// }
// This response unmarshalls into ComputePfwListResp structure.
// NOTE: If there are no rules for this compute, an empty string is returned along with err=nil
//
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
var compId, vinsId int
// PFW resource ID is a combination of compute_id and vins_id separated by ":"
// See a note in "flattenPfw" method explaining the rationale behind this approach.
if d.Id() != "" {
log.Debugf("utilityPfwCheckPresence: setting context from PFW ID %s", d.Id())
idParts := strings.SplitN(d.Id(), ":", 2)
compId, _ = strconv.Atoi(idParts[0])
vinsId, _ = strconv.Atoi(idParts[1])
log.Debugf("utilityPfwCheckPresence: extracted Compute ID %d, ViNS %d", compId, vinsId)
if compId <= 0 || vinsId <= 0 {
return "", fmt.Errorf("Ivalid context from d.Id %s", d.Id())
}
} else {
scId, cSet := d.GetOk("compute_id")
svId, vSet := d.GetOk("vins_id")
if cSet || vSet {
log.Debugf("utilityPfwCheckPresence: setting Compute ID from schema")
compId = scId.(int)
vinsId = svId.(int)
log.Debugf("utilityPfwCheckPresence: extracted Compute ID %d, ViNS %d", compId, vinsId)
} else {
return "", fmt.Errorf("Cannot get context to check PFW rules neither from PFW ID nor from schema")
}
}
log.Debugf("utilityPfwCheckPresence: preparing to get PFW rules for Compute ID %d on ViNS ID %d", compId, vinsId)
urlValues.Add("computeId", fmt.Sprintf("%d", compId))
apiResp, err, respCode := controller.decortAPICall("POST", ComputePfwListAPI, urlValues)
if respCode == 500 {
// this is workaround for API 3.7.0 "feature" - will be removed in one of the future versions
log.Errorf("utilityPfwCheckPresence: Compute ID %d has no PFW and no connection to PFW-ready ViNS", compId)
return "", nil
}
if err != nil {
return "", err
}
pfwListResp := ComputePfwListResp{}
// Note the specifics of compute/pfwList response in API 3.7.x (this may be changed in the future):
// 1) if there are no PFW rules and compute is not connected to any PFW-able ViNS
// the response will be empty string (or HTTP error code 500)
// 2) if there are no PFW rules but compute is connected to a PFW-able ViNS
// the response will contain a list with a single element - prefix (see PfwPrefixRecord)
// 3) if there are port forwarding rules, the response will contain a list which starts
// with prefix (see PfwPrefixRecord) and then followed by one or more rule records
// (see PfwRuleRecord)
//
// EXTRA NOTE: in API 3.7.0 and the likes pfwList returns HTTP response code 500 for a compute
// that is not connected to any PFW-able ViNS - need to implement temporary workaround
if apiResp == "" {
// No port forward rules defined for this compute
return "", nil
}
log.Debugf("utilityPfwCheckPresence: ready to split API response string %s", apiResp)
twoParts := strings.SplitN(apiResp, "},", 2)
if len(twoParts) < 1 || len(twoParts) > 2 {
// Case: invalid format of API response
log.Errorf("utilityPfwCheckPresence: non-empty pfwList response for compute ID %d failed to split properly", compId)
return "", fmt.Errorf("Non-empty pfwList response failed to split properly")
}
if len(twoParts) == 1 {
// Case: compute is connected to a PWF-ready ViNS but has no PFW rules defined
log.Debugf("utilityPfwCheckPresence: compute ID %d is connected to PFW-ready ViNS but has no PFW rules", compId)
return "", nil
}
// Case: compute is connected to a PFW ready ViNS and has some PFW rule
prefixResp := strings.TrimSuffix(strings.TrimPrefix(twoParts[0], "["), ",") + "}"
log.Debugf("utilityPfwCheckPresence: ready to unmarshal prefix part %s", prefixResp)
err = json.Unmarshal([]byte(prefixResp), &pfwListResp.Header)
if err != nil {
log.Errorf("utilityPfwCheckPresence: failed to unmarshal prefix part of API response: %s", err)
return "", err
}
rulesResp := "[" + twoParts[1]
log.Debugf("utilityPfwCheckPresence: ready to unmarshal rules part %s", rulesResp)
err = json.Unmarshal([]byte(rulesResp), &pfwListResp.Rules)
if err != nil {
log.Errorf("utilityPfwCheckPresence: failed to unmarshal rules part of API response: %s", err)
return "", err
}
log.Debugf("utilityPfwCheckPresence: successfully read %d port forward rules for Compute ID %d",
len(pfwListResp.Rules), compId)
if len(pfwListResp.Rules) == 0 {
// this compute technically can have rules, but no rules are currently defined
return "", nil
}
// Now we can double check that the PFWs we've got do belong to the compute & ViNS specified in the
// PFW definition. Do so by reading compute details and comparing NatableVinsID value with the
// specified ViNS ID
//
// We may reuse urlValues here, as it already contains computeId field (see initialization above)
apiResp, err, _ = controller.decortAPICall("POST", ComputeGetAPI, urlValues)
if err != nil || apiResp == "" {
log.Errorf("utilityPfwCheckPresence: failed to get Compute ID %d details", compId)
return "", err
}
compFacts := ComputeGetResp{}
err = json.Unmarshal([]byte(rulesResp), &apiResp)
if err != nil {
log.Errorf("utilityPfwCheckPresence: failed to unmarshal compute details for ID %d: %s", compId, err)
return "", err
}
if compFacts.NatableVinsID <= 0 {
log.Errorf("utilityPfwCheckPresence: compute ID %d is not connected to a NAT-able ViNS", compId)
return "", fmt.Errorf("Compute ID %d is not connected to a NAT-able ViNS", compId)
}
if compFacts.NatableVinsID != vinsId {
log.Errorf("utilityPfwCheckPresence: ViNS ID mismatch for PFW rules on compute ID %d: actual %d, required %d",
compId, compFacts.NatableVinsID, vinsId)
return "", fmt.Errorf("ViNS ID mismatch for PFW rules on compute ID %d: actual %d, required %d",
compId, compFacts.NatableVinsID, vinsId)
}
// reconstruct API response string to return
pfwListResp.Header.ComputeID = compId
pfwListResp.Header.VinsID = compFacts.NatableVinsID
reencodedItem, err := json.Marshal(pfwListResp)
if err != nil {
return "", err
}
return string(reencodedItem[:]), nil
}

View File

@@ -39,7 +39,7 @@ import (
func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp, error) {
urlValues := &url.Values{}
urlValues.Add("rgId", fmt.Sprintf("%d", rgid))
rgFacts, err := ctrl.decortAPICall("POST", ResgroupGetAPI, urlValues)
rgFacts, err, _ := ctrl.decortAPICall("POST", ResgroupGetAPI, urlValues)
if err != nil {
return nil, err
}
@@ -109,7 +109,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
// go straight for the RG by its ID
log.Debugf("utilityResgroupCheckPresence: locating RG by its ID %d", theId)
urlValues.Add("rgId", fmt.Sprintf("%d", theId))
rgFacts, err := controller.decortAPICall("POST", ResgroupGetAPI, urlValues)
rgFacts, err, _ := controller.decortAPICall("POST", ResgroupGetAPI, urlValues)
if err != nil {
return "", err
}
@@ -130,7 +130,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
}
urlValues.Add("includedeleted", "false")
apiResp, err := controller.decortAPICall("POST", ResgroupListAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", ResgroupListAPI, urlValues)
if err != nil {
return "", err
}
@@ -154,7 +154,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
// Namely, we need resource quota settings
reqValues := &url.Values{}
reqValues.Add("rgId", fmt.Sprintf("%d", item.ID))
apiResp, err := controller.decortAPICall("POST", ResgroupGetAPI, reqValues)
apiResp, err, _ := controller.decortAPICall("POST", ResgroupGetAPI, reqValues)
if err != nil {
return "", err
}

View File

@@ -38,7 +38,7 @@ import (
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)
vinsFacts, err, _ := ctrl.decortAPICall("POST", VinsGetAPI, urlValues)
if err != nil {
return nil, err
}
@@ -94,7 +94,7 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er
// ViNS ID is specified, try to get compute instance straight by this ID
log.Debugf("utilityVinsCheckPresence: locating ViNS by its ID %d", theId)
urlValues.Add("vinsId", fmt.Sprintf("%d", theId))
vinsFacts, err := controller.decortAPICall("POST", VinsGetAPI, urlValues)
vinsFacts, err, _ := controller.decortAPICall("POST", VinsGetAPI, urlValues)
if err != nil {
return "", err
}
@@ -125,7 +125,7 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er
urlValues.Add("accountId", fmt.Sprintf("%d", accountId.(int)))
}
apiResp, err := controller.decortAPICall("POST", VinsSearchAPI, urlValues)
apiResp, err, _ := controller.decortAPICall("POST", VinsSearchAPI, urlValues)
if err != nil {
return "", err
}
@@ -154,7 +154,7 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er
// 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))
vinsGetResp, err := controller.decortAPICall("POST", VinsGetAPI, rqValues)
vinsGetResp, err, _ := controller.decortAPICall("POST", VinsGetAPI, rqValues)
if err != nil {
return "", err
}