diff --git a/.gitignore b/.gitignore
index af17b90f9..6572b8bec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ website/node_modules
*~
.*.swp
.idea
+.vscode/
*.iml
*.test
*.iml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b7d64f23d..572a289a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,12 @@
## 2.4.0 (Unreleased)
IMPROVEMENTS:
-* Change resource handling to use locking mechanism when resource parallel handling is not supported by vCD. [GH-#255]
+* Change resource handling to use locking mechanism when resource parallel handling is not supported by vCD. [GH-255]
+
+FEATURES:
+
+* **New Resource:** Load Balancer Service Monitor `vcd_lb_service_monitor` - [GH-256]
+* **New Data Source:** Load Balancer Service Monitor `vcd_lb_service_monitor` - [GH-256]
## 2.3.0 (May 29, 2019)
diff --git a/go.mod b/go.mod
index f6a73c0be..91cd308f4 100644
--- a/go.mod
+++ b/go.mod
@@ -4,5 +4,5 @@ go 1.12
require (
github.com/hashicorp/terraform v0.12.0
- github.com/vmware/go-vcloud-director/v2 v2.3.0-alpha.1
+ github.com/vmware/go-vcloud-director/v2 v2.3.0-alpha.3
)
diff --git a/go.sum b/go.sum
index a8bbafce5..04090d9cc 100644
--- a/go.sum
+++ b/go.sum
@@ -301,8 +301,8 @@ github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU=
github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
-github.com/vmware/go-vcloud-director/v2 v2.3.0-alpha.1 h1:zQD5RbcompjwAeACoJjtMgTeUc4lZaLceBv66j5Ayy8=
-github.com/vmware/go-vcloud-director/v2 v2.3.0-alpha.1/go.mod h1:HonlGxbjJ1NAibWh99eE4/S2l6ZOZ5KJzKK1rh2a9vc=
+github.com/vmware/go-vcloud-director/v2 v2.3.0-alpha.3 h1:cgQGF5SmFtHPbwUESzlKu6C8mXBoCei/Wqkji8aScBM=
+github.com/vmware/go-vcloud-director/v2 v2.3.0-alpha.3/go.mod h1:+Hq7ryFfgZqsO6mXH29RQFnpIMSujCOMI57otHoXHhQ=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
diff --git a/vcd/datasource_vcd_lb_service_monitor.go b/vcd/datasource_vcd_lb_service_monitor.go
new file mode 100644
index 000000000..7c5e88f3b
--- /dev/null
+++ b/vcd/datasource_vcd_lb_service_monitor.go
@@ -0,0 +1,92 @@
+package vcd
+
+import (
+ "fmt"
+
+ "github.com/hashicorp/terraform/helper/schema"
+ "github.com/vmware/go-vcloud-director/v2/types/v56"
+)
+
+func datasourceVcdLbServiceMonitor() *schema.Resource {
+ return &schema.Resource{
+ Read: datasourceVcdLbServiceMonitorRead,
+ Schema: map[string]*schema.Schema{
+ "org": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "vCD organization in which the Service Monitor is located",
+ },
+ "vdc": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "vCD virtual datacenter in which the Service Monitor is located",
+ },
+ "edge_gateway": &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Edge gateway name in which the Service Monitor is located",
+ },
+ "name": &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Service Monitor name",
+ },
+ "interval": &schema.Schema{
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+ "timeout": &schema.Schema{
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+ "max_retries": &schema.Schema{
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+ "type": &schema.Schema{
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "expected": &schema.Schema{
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "method": &schema.Schema{
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "url": &schema.Schema{
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "send": &schema.Schema{
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "receive": &schema.Schema{
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "extension": {
+ Type: schema.TypeMap,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func datasourceVcdLbServiceMonitorRead(d *schema.ResourceData, meta interface{}) error {
+ vcdClient := meta.(*VCDClient)
+ edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d)
+ if err != nil {
+ return fmt.Errorf(errorUnableToFindEdgeGateway, err)
+ }
+
+ readLBMonitor, err := edgeGateway.ReadLBServiceMonitor(&types.LBMonitor{Name: d.Get("name").(string)})
+ if err != nil {
+ return fmt.Errorf("unable to find load balancer service monitor with Name %s: %s", d.Get("name").(string), err)
+ }
+
+ d.SetId(readLBMonitor.ID)
+ return setLBMonitorData(d, readLBMonitor)
+}
diff --git a/vcd/provider.go b/vcd/provider.go
index 030f2a808..ee65d1212 100644
--- a/vcd/provider.go
+++ b/vcd/provider.go
@@ -83,25 +83,30 @@ func Provider() terraform.ResourceProvider {
},
ResourcesMap: map[string]*schema.Resource{
- "vcd_network": resourceVcdNetwork(), // DEPRECATED: replaced by vcd_network_routed
- "vcd_network_routed": resourceVcdNetworkRouted(),
- "vcd_network_direct": resourceVcdNetworkDirect(),
- "vcd_network_isolated": resourceVcdNetworkIsolated(),
- "vcd_vapp_network": resourceVcdVappNetwork(),
- "vcd_vapp": resourceVcdVApp(),
- "vcd_firewall_rules": resourceVcdFirewallRules(),
- "vcd_dnat": resourceVcdDNAT(),
- "vcd_snat": resourceVcdSNAT(),
- "vcd_edgegateway_vpn": resourceVcdEdgeGatewayVpn(),
- "vcd_vapp_vm": resourceVcdVAppVm(),
- "vcd_org": resourceOrg(),
- "vcd_org_vdc": resourceVcdOrgVdc(),
- "vcd_catalog": resourceVcdCatalog(),
- "vcd_catalog_item": resourceVcdCatalogItem(),
- "vcd_catalog_media": resourceVcdCatalogMedia(),
- "vcd_inserted_media": resourceVcdInsertedMedia(),
- "vcd_independent_disk": resourceVcdIndependentDisk(),
- "vcd_external_network": resourceVcdExternalNetwork(),
+ "vcd_network": resourceVcdNetwork(), // DEPRECATED: replaced by vcd_network_routed
+ "vcd_network_routed": resourceVcdNetworkRouted(),
+ "vcd_network_direct": resourceVcdNetworkDirect(),
+ "vcd_network_isolated": resourceVcdNetworkIsolated(),
+ "vcd_vapp_network": resourceVcdVappNetwork(),
+ "vcd_vapp": resourceVcdVApp(),
+ "vcd_firewall_rules": resourceVcdFirewallRules(),
+ "vcd_dnat": resourceVcdDNAT(),
+ "vcd_snat": resourceVcdSNAT(),
+ "vcd_edgegateway_vpn": resourceVcdEdgeGatewayVpn(),
+ "vcd_vapp_vm": resourceVcdVAppVm(),
+ "vcd_org": resourceOrg(),
+ "vcd_org_vdc": resourceVcdOrgVdc(),
+ "vcd_catalog": resourceVcdCatalog(),
+ "vcd_catalog_item": resourceVcdCatalogItem(),
+ "vcd_catalog_media": resourceVcdCatalogMedia(),
+ "vcd_inserted_media": resourceVcdInsertedMedia(),
+ "vcd_independent_disk": resourceVcdIndependentDisk(),
+ "vcd_external_network": resourceVcdExternalNetwork(),
+ "vcd_lb_service_monitor": resourceVcdLbServiceMonitor(),
+ },
+
+ DataSourcesMap: map[string]*schema.Resource{
+ "vcd_lb_service_monitor": datasourceVcdLbServiceMonitor(),
},
ConfigureFunc: providerConfigure,
diff --git a/vcd/provider_test.go b/vcd/provider_test.go
index 879b1e558..64a7c3b8b 100644
--- a/vcd/provider_test.go
+++ b/vcd/provider_test.go
@@ -51,3 +51,23 @@ func testAccPreCheck(t *testing.T) {
func init() {
testingTags["api"] = "provider_test.go"
}
+
+// createTemporaryVCDConnection is meant to create a VCDClient to check environment before executing specific acceptance
+// tests and before VCDClient is accessible.
+func createTemporaryVCDConnection() *VCDClient {
+ config := Config{
+ User: testConfig.Provider.User,
+ Password: testConfig.Provider.Password,
+ SysOrg: testConfig.Provider.SysOrg,
+ Org: testConfig.VCD.Org,
+ Vdc: testConfig.VCD.Vdc,
+ Href: testConfig.Provider.Url,
+ InsecureFlag: testConfig.Provider.AllowInsecure,
+ MaxRetryTimeout: testConfig.Provider.MaxRetryTimeout,
+ }
+ conn, err := config.Client()
+ if err != nil {
+ panic("unable to initialize VCD connection :" + err.Error())
+ }
+ return conn
+}
diff --git a/vcd/resource_vcd_firewall_rules_test.go b/vcd/resource_vcd_firewall_rules_test.go
index e64e40bab..ca7a3306b 100644
--- a/vcd/resource_vcd_firewall_rules_test.go
+++ b/vcd/resource_vcd_firewall_rules_test.go
@@ -83,21 +83,7 @@ func createFirewallRulesConfigs(existingRules *govcd.EdgeGateway) string {
defaultAction := "drop"
edgeGatewayName := testConfig.Networking.EdgeGateway
if !vcdShortTest {
- config := Config{
- User: testConfig.Provider.User,
- Password: testConfig.Provider.Password,
- SysOrg: testConfig.Provider.SysOrg,
- Org: testConfig.VCD.Org,
- Vdc: testConfig.VCD.Vdc,
- Href: testConfig.Provider.Url,
- InsecureFlag: testConfig.Provider.AllowInsecure,
- MaxRetryTimeout: 140,
- }
-
- conn, err := config.Client()
- if err != nil {
- panic(err)
- }
+ conn := createTemporaryVCDConnection()
if edgeGatewayName == "" {
panic(fmt.Errorf("could not get an Edge Gateway. Variable networking.edgeGateway is not set"))
diff --git a/vcd/resource_vcd_lb_service_monitor.go b/vcd/resource_vcd_lb_service_monitor.go
new file mode 100644
index 000000000..791add988
--- /dev/null
+++ b/vcd/resource_vcd_lb_service_monitor.go
@@ -0,0 +1,335 @@
+package vcd
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/hashicorp/terraform/helper/schema"
+ "github.com/vmware/go-vcloud-director/v2/types/v56"
+)
+
+func resourceVcdLbServiceMonitor() *schema.Resource {
+ return &schema.Resource{
+ Create: resourceVcdLbServiceMonitorCreate,
+ Read: resourceVcdLbServiceMonitorRead,
+ Update: resourceVcdLbServiceMonitorUpdate,
+ Delete: resourceVcdLbServiceMonitorDelete,
+ Importer: &schema.ResourceImporter{
+ State: resourceVcdLbServiceMonitorImport,
+ },
+
+ Schema: map[string]*schema.Schema{
+ "org": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ },
+ "vdc": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ },
+ "edge_gateway": &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Edge gateway name",
+ },
+ "name": &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Unique Service Monitor name",
+ },
+ "interval": &schema.Schema{
+ Type: schema.TypeInt,
+ Default: 10,
+ Optional: true,
+ Description: "Interval in seconds at which a server is to be monitored",
+ },
+ "timeout": &schema.Schema{
+ Type: schema.TypeInt,
+ Default: 15,
+ Optional: true,
+ Description: "Maximum time in seconds within which a response from the server must be received",
+ },
+ "max_retries": &schema.Schema{
+ Type: schema.TypeInt,
+ Default: 3,
+ Optional: true,
+ Description: "Number of times the specified monitoring Method must fail sequentially before the server is declared down",
+ },
+ "type": &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Way in which you want to send the health check request to the server. One of http, https, tcp, icmp, or udp",
+ ValidateFunc: validateCase("lower"),
+ },
+ "expected": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "String that the monitor expects to match in the status line of the http or https response (for example, HTTP/1.1)",
+ },
+ "method": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Method to be used to detect server status. One of OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, or CONNECT",
+ ValidateFunc: validateCase("upper"),
+ },
+ "url": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "URL to be used in the server status request",
+ },
+ "send": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Data to be sent",
+ },
+ "receive": &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "String to be matched in the response content",
+ },
+ "extension": {
+ Type: schema.TypeMap,
+ Optional: true,
+ Description: "Advanced monitor parameters as key=value pairs",
+ },
+ },
+ }
+}
+
+// validateCase checks if a string is of caseType "upper" or "lower"
+func validateCase(caseType string) schema.SchemaValidateFunc {
+ return func(i interface{}, k string) (s []string, es []error) {
+ v, ok := i.(string)
+ if !ok {
+ es = append(es, fmt.Errorf("expected type of %s to be string", k))
+ return
+ }
+
+ switch caseType {
+ case "upper":
+ if strings.ToUpper(v) != v {
+ es = append(es, fmt.Errorf(
+ "expected string to be upper cased, got: %s", v))
+ }
+ case "lower":
+ if strings.ToLower(v) != v {
+ es = append(es, fmt.Errorf(
+ "expected string to be lower cased, got: %s", v))
+ }
+ default:
+ panic("unsupported validation type for validateCase() function")
+ }
+ return
+ }
+}
+
+func resourceVcdLbServiceMonitorCreate(d *schema.ResourceData, meta interface{}) error {
+ vcdClient := meta.(*VCDClient)
+ vcdClient.lockParentEdgeGtw(d)
+ defer vcdClient.unLockParentEdgeGtw(d)
+
+ edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d)
+ if err != nil {
+ return fmt.Errorf(errorUnableToFindEdgeGateway, err)
+ }
+
+ lbMonitor, err := getLBMonitorType(d)
+ if err != nil {
+ return fmt.Errorf("unable to expand load balancer service monitor: %s", err)
+ }
+
+ createdMonitor, err := edgeGateway.CreateLBServiceMonitor(lbMonitor)
+ if err != nil {
+ return fmt.Errorf("error creating new load balancer service monitor: %s", err)
+ }
+
+ d.SetId(createdMonitor.ID)
+ return nil
+}
+
+func resourceVcdLbServiceMonitorRead(d *schema.ResourceData, meta interface{}) error {
+ vcdClient := meta.(*VCDClient)
+
+ edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d)
+ if err != nil {
+ return fmt.Errorf(errorUnableToFindEdgeGateway, err)
+ }
+
+ readLBMonitor, err := edgeGateway.ReadLBServiceMonitor(&types.LBMonitor{ID: d.Id()})
+ if err != nil {
+ d.SetId("")
+ return fmt.Errorf("unable to find load balancer service monitor with ID %s: %s", d.Id(), err)
+ }
+
+ return setLBMonitorData(d, readLBMonitor)
+}
+
+func resourceVcdLbServiceMonitorUpdate(d *schema.ResourceData, meta interface{}) error {
+ vcdClient := meta.(*VCDClient)
+ vcdClient.lockParentEdgeGtw(d)
+ defer vcdClient.unLockParentEdgeGtw(d)
+
+ edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d)
+ if err != nil {
+ return fmt.Errorf(errorUnableToFindEdgeGateway, err)
+ }
+
+ updateLBMonitorConfig, err := getLBMonitorType(d)
+ if err != nil {
+ return fmt.Errorf("could not expand monitor for update: %s", err)
+ }
+
+ updatedLBMonitor, err := edgeGateway.UpdateLBServiceMonitor(updateLBMonitorConfig)
+ if err != nil {
+ return fmt.Errorf("unable to update load balancer service monitor with ID %s: %s", d.Id(), err)
+ }
+
+ return setLBMonitorData(d, updatedLBMonitor)
+}
+
+func resourceVcdLbServiceMonitorDelete(d *schema.ResourceData, meta interface{}) error {
+ vcdClient := meta.(*VCDClient)
+ vcdClient.lockParentEdgeGtw(d)
+ defer vcdClient.unLockParentEdgeGtw(d)
+
+ edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d)
+ if err != nil {
+ return fmt.Errorf(errorUnableToFindEdgeGateway, err)
+ }
+
+ err = edgeGateway.DeleteLBServiceMonitor(&types.LBMonitor{ID: d.Id()})
+ if err != nil {
+ return fmt.Errorf("error deleting load balancer service monitor: %s", err)
+ }
+
+ d.SetId("")
+ return nil
+}
+
+// resourceVcdLbServiceMonitorImport expects dot formatted path to Load Balancer Service Monitor
+// i.e. my-org.my-org-vdc.my-edge-gw.my-lb-service-monitor
+func resourceVcdLbServiceMonitorImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
+
+ resourceURI := strings.Split(d.Id(), ".")
+ if len(resourceURI) != 4 {
+ return nil, fmt.Errorf("resource name must be specified as org.VDC.edge-gw.lb-service-monitor")
+ }
+ orgName, vdcName, edgeName, monitorName := resourceURI[0], resourceURI[1], resourceURI[2], resourceURI[3]
+
+ vcdClient := meta.(*VCDClient)
+ edgeGateway, err := vcdClient.GetEdgeGateway(orgName, vdcName, edgeName)
+ if err != nil {
+ return nil, fmt.Errorf(errorUnableToFindEdgeGateway, err)
+ }
+
+ readLBMonitor, err := edgeGateway.ReadLBServiceMonitor(&types.LBMonitor{Name: monitorName})
+ if err != nil {
+ return []*schema.ResourceData{}, fmt.Errorf("unable to find load balancer service monitor with ID %s: %s", d.Id(), err)
+ }
+
+ d.Set("org", orgName)
+ d.Set("vdc", vdcName)
+ d.Set("edge_gateway", edgeName)
+ d.Set("name", monitorName)
+
+ d.SetId(readLBMonitor.ID)
+ return []*schema.ResourceData{d}, nil
+}
+
+// getLBMonitorType converts schema.ResourceData to *types.LBMonitor and is useful
+// for creating API requests
+func getLBMonitorType(d *schema.ResourceData) (*types.LBMonitor, error) {
+ lbMonitor := &types.LBMonitor{
+ Name: d.Get("name").(string),
+ Interval: d.Get("interval").(int),
+ Timeout: d.Get("timeout").(int),
+ Type: d.Get("type").(string),
+ MaxRetries: d.Get("max_retries").(int),
+ Expected: d.Get("expected").(string),
+ Method: d.Get("method").(string),
+ URL: d.Get("url").(string),
+ Send: d.Get("send").(string),
+ Receive: d.Get("receive").(string),
+ Extension: getLBMonitorExtensionType(d),
+ }
+
+ return lbMonitor, nil
+}
+
+// getLBMonitorExtensionType expands the specified map for sending via API. It appends newline to every extension as
+// per API requirement. Based on the research the underlying structure should not cause problems because duplicate keys
+// are not needed and order of the keys does not matter for API.
+// Example API call string for Extension field:
+// delay=2
+// critical=3
+// escape
+func getLBMonitorExtensionType(d *schema.ResourceData) string {
+ var extensionString string
+ extension := d.Get("extension").(map[string]interface{})
+ for k, v := range extension {
+ if k != "" && v != "" { // When key and value are given it must look like "content-type=STRING"
+ extensionString += k + "=" + v.(string) + "\n"
+ } else { // If only key is specified it does not need equals sign. Like "no-body" extension
+ extensionString += k + "\n"
+ }
+ }
+ return extensionString
+}
+
+// setLBMonitorData sets object state from *types.LBMonitor
+func setLBMonitorData(d *schema.ResourceData, lBmonitor *types.LBMonitor) error {
+ d.Set("interval", lBmonitor.Interval)
+ d.Set("timeout", lBmonitor.Timeout)
+ d.Set("max_retries", lBmonitor.MaxRetries)
+ d.Set("type", lBmonitor.Type)
+ // Optional attributes may not be necessary
+ d.Set("method", lBmonitor.Method)
+ d.Set("url", lBmonitor.URL)
+ d.Set("send", lBmonitor.Send)
+ d.Set("receive", lBmonitor.Receive)
+ d.Set("expected", lBmonitor.Expected)
+
+ if err := setLBMonitorExtensionData(d, lBmonitor); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// setLBMonitorExtensionData is responsible for parsing response extension field from API and
+// store it in the map. It supports flattening `key=value` or `key` notations. Each of them must be
+// separated by newline.
+func setLBMonitorExtensionData(d *schema.ResourceData, lBmonitor *types.LBMonitor) error {
+ extensionStorage := make(map[string]string)
+
+ if lBmonitor.Extension != "" {
+ kvList := strings.Split(lBmonitor.Extension, "\n")
+ for _, extensionLine := range kvList {
+ // Skip empty lines
+ if extensionLine == "" {
+ continue
+ }
+
+ // When key=extensionLine format is present
+ if strings.Contains(extensionLine, "=") {
+ keyValue := strings.Split(extensionLine, "=")
+ if len(keyValue) != 2 {
+ return fmt.Errorf("unable to flatten extension field %s", extensionLine)
+ }
+ // Populate extension data with key value
+ extensionStorage[keyValue[0]] = keyValue[1]
+ // If there was no "=" sign then it means whole line is just key. Like `no-body`, `linespan`
+ } else {
+ extensionStorage[extensionLine] = ""
+ }
+ }
+
+ }
+
+ d.Set("extension", extensionStorage)
+ return nil
+}
diff --git a/vcd/resource_vcd_lb_service_monitor_test.go b/vcd/resource_vcd_lb_service_monitor_test.go
new file mode 100644
index 000000000..a561768ef
--- /dev/null
+++ b/vcd/resource_vcd_lb_service_monitor_test.go
@@ -0,0 +1,184 @@
+// +build gateway lb lbServiceMonitor ALL functional
+
+package vcd
+
+import (
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/vmware/go-vcloud-director/v2/types/v56"
+
+ "github.com/hashicorp/terraform/terraform"
+
+ "github.com/hashicorp/terraform/helper/resource"
+)
+
+func TestAccVcdLbServiceMonitor(t *testing.T) {
+
+ // String map to fill the template
+ var params = StringMap{
+ "Org": testConfig.VCD.Org,
+ "Vdc": testConfig.VCD.Vdc,
+ "EdgeGateway": testConfig.Networking.EdgeGateway,
+ "ServiceMonitorName": t.Name(),
+ "Interval": 5,
+ "Timeout": 10,
+ "MaxRetries": 3,
+ "Method": "POST",
+ "Tags": "lb lbServiceMonitor",
+ }
+
+ configText := templateFill(testAccVcdLbServiceMonitor_Basic, params)
+ debugPrintf("#[DEBUG] CONFIGURATION for step 0: %s", configText)
+
+ params["FuncName"] = t.Name() + "-step1"
+ configTextStep1 := templateFill(testAccVcdLbServiceMonitor_Basic2, params)
+ debugPrintf("#[DEBUG] CONFIGURATION for step 1: %s", configTextStep1)
+
+ if vcdShortTest {
+ t.Skip(acceptanceTestsSkipped)
+ return
+ }
+
+ if !edgeGatewayIsAdvanced() {
+ t.Skip(t.Name() + "requires advanced edge gateway to work")
+ }
+
+ resource.Test(t, resource.TestCase{
+ Providers: testAccProviders,
+ PreCheck: func() { testAccPreCheck(t) },
+ CheckDestroy: testAccCheckVcdLbServiceMonitorDestroy(params["ServiceMonitorName"].(string)),
+ Steps: []resource.TestStep{
+ resource.TestStep{
+ Config: configText,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("vcd_lb_service_monitor.lb-service-monitor", "name", params["ServiceMonitorName"].(string)),
+ resource.TestMatchResourceAttr("vcd_lb_service_monitor.lb-service-monitor", "id", regexp.MustCompile(`^monitor-\d*$`)),
+ resource.TestCheckResourceAttr("vcd_lb_service_monitor.lb-service-monitor", "method", params["Method"].(string)),
+ resource.TestCheckResourceAttr("vcd_lb_service_monitor.lb-service-monitor", "type", "http"),
+ resource.TestCheckResourceAttr("vcd_lb_service_monitor.lb-service-monitor", "url", "/health"),
+ // Data source testing - it must expose all fields which resource has
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "interval", strconv.Itoa(params["Interval"].(int))),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "timeout", strconv.Itoa(params["Timeout"].(int))),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "max_retries", strconv.Itoa(params["MaxRetries"].(int))),
+ resource.TestMatchResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "id", regexp.MustCompile(`^monitor-\d*$`)),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "extension.content-type", "application/json"),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "extension.no-body", ""),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "method", params["Method"].(string)),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "type", "http"),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "send", "{\"key\": \"value\"}"),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "expected", "HTTP/1.1"),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "receive", "OK"),
+ resource.TestCheckResourceAttr("data.vcd_lb_service_monitor.ds-lb-service-monitor", "url", "/health"),
+ ),
+ },
+ resource.TestStep{
+ Config: configTextStep1,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("vcd_lb_service_monitor.lb-service-monitor", "name", params["ServiceMonitorName"].(string)),
+ resource.TestMatchResourceAttr("vcd_lb_service_monitor.lb-service-monitor", "id", regexp.MustCompile(`^monitor-\d*$`)),
+ resource.TestCheckResourceAttr("vcd_lb_service_monitor.lb-service-monitor", "type", "tcp"),
+ ),
+ },
+ // Check that import works
+ resource.TestStep{
+ ResourceName: "vcd_lb_service_monitor.service-monitor-import",
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateIdFunc: importStateIdByOrgVdcEdge(testConfig, params["ServiceMonitorName"].(string)),
+ },
+ },
+ })
+}
+
+func testAccCheckVcdLbServiceMonitorDestroy(serviceMonitorName string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ conn := testAccProvider.Meta().(*VCDClient)
+
+ edgeGateway, err := conn.GetEdgeGateway(testConfig.VCD.Org, testConfig.VCD.Vdc, testConfig.Networking.EdgeGateway)
+ if err != nil {
+ return fmt.Errorf(errorUnableToFindEdgeGateway, err)
+ }
+
+ monitor, err := edgeGateway.ReadLBServiceMonitor(&types.LBMonitor{Name: serviceMonitorName})
+ if !strings.Contains(err.Error(), "could not find load balancer") || monitor != nil {
+ return fmt.Errorf("load balancer service monitor was not deleted: %s", err)
+ }
+ return nil
+ }
+}
+
+// importStateIdByOrgVdcEdge constructs an import path (ID in Terraform import terms) in the format of:
+// organization.vdc.edge-gateway-nane.import-object-name (i.e. my-org.my-vdc.my-edge-gw.objectName) from TestConfig and
+// object state.
+func importStateIdByOrgVdcEdge(vcd TestConfig, objectName string) resource.ImportStateIdFunc {
+ return func(*terraform.State) (string, error) {
+ importId := testConfig.VCD.Org + "." + testConfig.VCD.Vdc + "." + testConfig.Networking.EdgeGateway + "." + objectName
+ if testConfig.VCD.Org == "" || testConfig.VCD.Vdc == "" || testConfig.Networking.EdgeGateway == "" || objectName == "" {
+ return "", fmt.Errorf("missing information to generate import path: %s", importId)
+ }
+
+ return importId, nil
+ }
+}
+
+// edgeGatewayIsAdvanced checks if edge gateway for testing is an advanced one
+func edgeGatewayIsAdvanced() bool {
+ conn := createTemporaryVCDConnection()
+
+ edgeGateway, err := conn.GetEdgeGateway(testConfig.VCD.Org, testConfig.VCD.Vdc, testConfig.Networking.EdgeGateway)
+ if err != nil {
+ panic("unable to find edge gateway")
+ }
+
+ return edgeGateway.HasAdvancedNetworking()
+}
+
+const testAccVcdLbServiceMonitor_Basic = `
+resource "vcd_lb_service_monitor" "lb-service-monitor" {
+ org = "{{.Org}}"
+ vdc = "{{.Vdc}}"
+ edge_gateway = "{{.EdgeGateway}}"
+
+ name = "{{.ServiceMonitorName}}"
+ interval = {{.Interval}}
+ timeout = {{.Timeout}}
+ max_retries = {{.MaxRetries}}
+ type = "http"
+ method = "{{.Method}}"
+ send = "{\"key\": \"value\"}"
+ expected = "HTTP/1.1"
+ receive = "OK"
+ url = "/health"
+
+ extension = {
+ "content-type" = "application/json"
+ "no-body" = ""
+ }
+}
+
+
+data "vcd_lb_service_monitor" "ds-lb-service-monitor" {
+ org = "{{.Org}}"
+ vdc = "{{.Vdc}}"
+ edge_gateway = "{{.EdgeGateway}}"
+ name = "${vcd_lb_service_monitor.lb-service-monitor.name}"
+}
+`
+
+const testAccVcdLbServiceMonitor_Basic2 = `
+resource "vcd_lb_service_monitor" "lb-service-monitor" {
+ org = "{{.Org}}"
+ vdc = "{{.Vdc}}"
+ edge_gateway = "{{.EdgeGateway}}"
+
+ name = "{{.ServiceMonitorName}}"
+ type = "tcp"
+ interval = {{.Interval}}
+ timeout = {{.Timeout}}
+ max_retries = {{.MaxRetries}}
+}
+`
diff --git a/vcd/test-templates/load-balancer1.tf b/vcd/test-templates/load-balancer1.tf
new file mode 100644
index 000000000..2f0975e14
--- /dev/null
+++ b/vcd/test-templates/load-balancer1.tf
@@ -0,0 +1,45 @@
+# Remove the first '#' from the next two lines to enable options for terraform executable
+## apply-options -parallelism=1
+## destroy-options -parallelism=1
+
+# Edge gateway load balancer configuration. It will get more details, but for now is used to
+# make sure that locks work fine and it does not fail
+
+# v2.4.0+
+
+variable "service_monitor_count" {
+ default = 20
+}
+
+resource "vcd_lb_service_monitor" "test" {
+ count = "${var.service_monitor_count}"
+
+ org = "{{.Org}}"
+ vdc = "{{.Vdc}}"
+ edge_gateway = "{{.EdgeGateway}}"
+
+ name = "test-monitor-${count.index}"
+ interval = 5
+ timeout = 5
+ max_retries = 3
+ type = "http"
+ method = "POST"
+ send = "{\"key\": \"value\"}"
+ expected = "HTTP/1.1"
+ receive = "OK"
+
+ extension = {
+ "content-type" = "application/json"
+ "no-body" = ""
+ }
+}
+
+
+data "vcd_lb_service_monitor" "ds-lb" {
+ count = "${var.service_monitor_count}"
+
+ org = "{{.Org}}"
+ vdc = "{{.Vdc}}"
+ edge_gateway = "{{.EdgeGateway}}"
+ name = "${vcd_lb_service_monitor.test[count.index].name}"
+}
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/api.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/api.go
index 76268796a..ae376927e 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/api.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/api.go
@@ -9,6 +9,7 @@ import (
"bytes"
"encoding/xml"
"fmt"
+
"io"
"io/ioutil"
"net/http"
@@ -96,18 +97,16 @@ func (cli *Client) NewRequest(params map[string]string, method string, reqUrl ur
return cli.NewRequestWitNotEncodedParams(params, nil, method, reqUrl, body)
}
-// ParseErr takes an error XML resp and returns a single string for use in error messages.
-func ParseErr(resp *http.Response) error {
-
- errBody := new(types.Error)
-
+// ParseErr takes an error XML resp, error interface for unmarshaling and returns a single string for
+// use in error messages.
+func ParseErr(resp *http.Response, errType error) error {
// if there was an error decoding the body, just return that
- if err := decodeBody(resp, errBody); err != nil {
+ if err := decodeBody(resp, errType); err != nil {
util.Logger.Printf("[ParseErr]: unhandled response <--\n%+v\n-->\n", resp)
return fmt.Errorf("[ParseErr]: error parsing error body for non-200 request: %s (%+v)", err, resp)
}
- return fmt.Errorf("API Error: %d: %s", errBody.MajorErrorCode, errBody.Message)
+ return errType
}
// decodeBody is used to XML decode a response body
@@ -133,6 +132,12 @@ func decodeBody(resp *http.Response, out interface{}) error {
// parses the resultant XML error and returns a descriptive error, if the
// status code is not handled it returns a generic error with the status code.
func checkResp(resp *http.Response, err error) (*http.Response, error) {
+ return checkRespWithErrType(resp, err, &types.Error{})
+}
+
+// checkRespWithErrType allows to specify custom error errType for checkResp unmarshaling
+// the error.
+func checkRespWithErrType(resp *http.Response, err, errType error) (*http.Response, error) {
if err != nil {
return resp, err
}
@@ -172,7 +177,7 @@ func checkResp(resp *http.Response, err error) (*http.Response, error) {
http.StatusInternalServerError, // 500
http.StatusServiceUnavailable, // 503
http.StatusGatewayTimeout: // 504
- return nil, ParseErr(resp)
+ return nil, ParseErr(resp, errType)
// Unhandled response.
default:
return nil, fmt.Errorf("unhandled API response, please report this issue, status code: %s", resp.Status)
@@ -230,6 +235,9 @@ func (client *Client) ExecuteRequestWithoutResponse(pathURL, requestType, conten
return fmt.Errorf(errorMessage, err)
}
+ // log response explicitly because decodeBody() was not triggered
+ util.ProcessResponseOutput(util.FuncNameCallStack(), resp, fmt.Sprintf("%s", resp.Body))
+
err = resp.Body.Close()
if err != nil {
return fmt.Errorf("error closing response body: %s", err)
@@ -272,7 +280,30 @@ func (client *Client) ExecuteRequest(pathURL, requestType, contentType, errorMes
return resp, nil
}
+// ExecuteRequestWithCustomError sends the request and checks for 2xx response. If the returned status code
+// was not as expected - the returned error will be unmarshaled to `errType` which implements Go's standard `error`
+// interface.
+func (client *Client) ExecuteRequestWithCustomError(pathURL, requestType, contentType, errorMessage string,
+ payload interface{}, errType error) (*http.Response, error) {
+ if !isMessageWithPlaceHolder(errorMessage) {
+ return &http.Response{}, fmt.Errorf("error message has to include place holder for error")
+ }
+
+ resp, err := executeRequestCustomErr(pathURL, requestType, contentType, payload, client, errType)
+ if err != nil {
+ return &http.Response{}, fmt.Errorf(errorMessage, err)
+ }
+
+ return resp, nil
+}
+
+// executeRequest does executeRequestCustomErr and checks for vCD errors in API response
func executeRequest(pathURL, requestType, contentType string, payload interface{}, client *Client) (*http.Response, error) {
+ return executeRequestCustomErr(pathURL, requestType, contentType, payload, client, &types.Error{})
+}
+
+// executeRequestCustomErr performs request and unmarshals API error to errType if not 2xx status was returned
+func executeRequestCustomErr(pathURL, requestType, contentType string, payload interface{}, client *Client, errType error) (*http.Response, error) {
url, _ := url.ParseRequestURI(pathURL)
var req *http.Request
@@ -295,13 +326,27 @@ func executeRequest(pathURL, requestType, contentType string, payload interface{
req.Header.Add("Content-Type", contentType)
}
- return checkResp(client.Http.Do(req))
+ resp, err := client.Http.Do(req)
+ if err != nil {
+ return resp, err
+ }
+
+ return checkRespWithErrType(resp, err, errType)
}
func isMessageWithPlaceHolder(message string) bool {
err := fmt.Errorf(message, "test error")
- if strings.Contains(err.Error(), "%!(EXTRA") {
- return false
+ return !strings.Contains(err.Error(), "%!(EXTRA")
+}
+
+// combinedTaskErrorMessage is a general purpose function
+// that returns the contents of the operation error and, if found, the error
+// returned by the associated task
+func combinedTaskErrorMessage(task *types.Task, err error) string {
+ extendedError := err.Error()
+ if task.Error != nil {
+ extendedError = fmt.Sprintf("operation error: %s - task error: [%d - %s] %s",
+ err, task.Error.MajorErrorCode, task.Error.MinorErrorCode, task.Error.Message)
}
- return true
+ return extendedError
}
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/catalog.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/catalog.go
index 56e199df4..d9fe804c2 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/catalog.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/catalog.go
@@ -66,14 +66,21 @@ func NewAdminCatalog(client *Client) *AdminCatalog {
func (catalog *Catalog) Delete(force, recursive bool) error {
adminCatalogHREF := catalog.client.VCDHREF
- adminCatalogHREF.Path += "/admin/catalog/" + getEntityNumericId(catalog.Catalog.ID)
+ catalogID, err := getBareEntityUuid(catalog.Catalog.ID)
+ if err != nil {
+ return err
+ }
+ if catalogID == "" {
+ return fmt.Errorf("empty ID returned for catalog ID %s", catalog.Catalog.ID)
+ }
+ adminCatalogHREF.Path += "/admin/catalog/" + catalogID
req := catalog.client.NewRequest(map[string]string{
"force": strconv.FormatBool(force),
"recursive": strconv.FormatBool(recursive),
}, http.MethodDelete, adminCatalogHREF, nil)
- _, err := checkResp(catalog.client.Http.Do(req))
+ _, err = checkResp(catalog.client.Http.Do(req))
if err != nil {
return fmt.Errorf("error deleting Catalog %s: %s", catalog.Catalog.ID, err)
@@ -82,14 +89,6 @@ func (catalog *Catalog) Delete(force, recursive bool) error {
return nil
}
-// slice only numeric part from ID:"urn:vcloud:catalog:97384890-180c-4563-b9b7-0dc50a2430b0"
-func getEntityNumericId(catalogId string) string {
- if catalogId != "" && (len(catalogId) > 19) {
- return catalogId[19:]
- }
- return ""
-}
-
// Deletes the Catalog, returning an error if the vCD call fails.
// Link to API call: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/DELETE-Catalog.html
func (adminCatalog *AdminCatalog) Delete(force, recursive bool) error {
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/edgegateway.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/edgegateway.go
index 5a2fa8775..ace1efc3b 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/edgegateway.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/edgegateway.go
@@ -11,6 +11,7 @@ import (
"net/http"
"net/url"
"regexp"
+ "strconv"
"strings"
"time"
@@ -23,6 +24,12 @@ type EdgeGateway struct {
client *Client
}
+// Simplified structure used to list networks connected to an edge gateway
+type SimpleNetworkIdentifier struct {
+ Name string
+ InterfaceType string
+}
+
var reErrorBusy = regexp.MustCompile(`is busy completing an operation.$`)
func NewEdgeGateway(cli *Client) *EdgeGateway {
@@ -634,3 +641,114 @@ func (eGW *EdgeGateway) RemoveIpsecVPN() (Task, error) {
}
return eGW.AddIpsecVPN(ipsecVPNConfig)
}
+
+// Deletes the edge gateway, returning a task and an error with the operation result.
+// https://code.vmware.com/apis/442/vcloud-director/doc/doc/operations/DELETE-EdgeGateway.html
+func (egw *EdgeGateway) DeleteAsync(force bool, recursive bool) (Task, error) {
+ util.Logger.Printf("[TRACE] EdgeGateway.Delete - deleting edge gateway with force: %t, recursive: %t", force, recursive)
+
+ if egw.EdgeGateway.HREF == "" {
+ return Task{}, fmt.Errorf("cannot delete, HREF is missing")
+ }
+
+ egwUrl, err := url.ParseRequestURI(egw.EdgeGateway.HREF)
+ if err != nil {
+ return Task{}, fmt.Errorf("error parsing edge gateway url: %s", err)
+ }
+
+ req := egw.client.NewRequest(map[string]string{
+ "force": strconv.FormatBool(force),
+ "recursive": strconv.FormatBool(recursive),
+ }, http.MethodDelete, *egwUrl, nil)
+ resp, err := checkResp(egw.client.Http.Do(req))
+ if err != nil {
+ return Task{}, fmt.Errorf("error deleting edge gateway: %s", err)
+ }
+ task := NewTask(egw.client)
+ if err = decodeBody(resp, task.Task); err != nil {
+ return Task{}, fmt.Errorf("error decoding task response: %s", err)
+ }
+ return *task, err
+}
+
+// Deletes the edge gateway, returning an error with the operation result.
+// https://code.vmware.com/apis/442/vcloud-director/doc/doc/operations/DELETE-EdgeGateway.html
+func (egw *EdgeGateway) Delete(force bool, recursive bool) error {
+
+ task, err := egw.DeleteAsync(force, recursive)
+ if err != nil {
+ return err
+ }
+ if task.Task.Status == "error" {
+ return fmt.Errorf(combinedTaskErrorMessage(task.Task, fmt.Errorf("edge gateway not properly destroyed")))
+ }
+
+ err = task.WaitTaskCompletion()
+ if err != nil {
+ return fmt.Errorf(combinedTaskErrorMessage(task.Task, err))
+ }
+
+ return nil
+}
+
+// GetNetworks returns the list of networks associated with an edge gateway
+// In the return structure, an interfaceType of "uplink" indicates an external network,
+// while "internal" is for Org VDC routed networks
+func (egw *EdgeGateway) GetNetworks() ([]SimpleNetworkIdentifier, error) {
+ var networks []SimpleNetworkIdentifier
+ err := egw.Refresh()
+ if err != nil {
+ return networks, err
+ }
+ for _, net := range egw.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface {
+ netIdentifier := SimpleNetworkIdentifier{
+ Name: net.Name,
+ InterfaceType: net.InterfaceType,
+ }
+ networks = append(networks, netIdentifier)
+ }
+
+ return networks, nil
+}
+
+// HasDefaultGateway returns true if the edge gateway uses one of the external
+// networks as default gateway
+func (egw *EdgeGateway) HasDefaultGateway() bool {
+ if egw.EdgeGateway.Configuration != nil &&
+ egw.EdgeGateway.Configuration.GatewayInterfaces != nil {
+ for _, gw := range egw.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface {
+ if gw.UseForDefaultRoute &&
+ gw.SubnetParticipation != nil &&
+ gw.SubnetParticipation.Gateway != "" &&
+ gw.SubnetParticipation.Netmask != "" {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// HasAdvancedNetworking returns true if the edge gateway has advanced network configuration enabled
+func (egw *EdgeGateway) HasAdvancedNetworking() bool {
+ return egw.EdgeGateway.Configuration != nil && egw.EdgeGateway.Configuration.AdvancedNetworkingEnabled
+}
+
+// buildProxiedEdgeEndpointURL helps to get root endpoint for Edge Gateway using the
+// NSX API Proxy and can append optionalSuffix which must have its own leading /
+func (eGW *EdgeGateway) buildProxiedEdgeEndpointURL(optionalSuffix string) (string, error) {
+ apiEndpoint, err := url.ParseRequestURI(eGW.EdgeGateway.HREF)
+ if err != nil {
+ return "", fmt.Errorf("unable to process edge gateway URL: %s", err)
+ }
+ edgeID := strings.Split(eGW.EdgeGateway.ID, ":")
+ if len(edgeID) != 4 {
+ return "", fmt.Errorf("unable to find edge gateway id: %s", eGW.EdgeGateway.ID)
+ }
+ hostname := apiEndpoint.Scheme + "://" + apiEndpoint.Host + "/network/edges/" + edgeID[3]
+
+ if optionalSuffix != "" {
+ return hostname + optionalSuffix, nil
+ }
+
+ return hostname, nil
+}
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/lbservicemonitor.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/lbservicemonitor.go
new file mode 100644
index 000000000..cea3a4ead
--- /dev/null
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/lbservicemonitor.go
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
+ */
+
+package govcd
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/vmware/go-vcloud-director/v2/types/v56"
+)
+
+// CreateLBServiceMonitor creates a load balancer service monitor based on mandatory fields. It is a synchronous
+// operation. It returns created object with all fields (including ID) populated or an error.
+func (eGW *EdgeGateway) CreateLBServiceMonitor(lbMonitorConfig *types.LBMonitor) (*types.LBMonitor, error) {
+ if err := validateCreateLBServiceMonitor(lbMonitorConfig); err != nil {
+ return nil, err
+ }
+
+ if !eGW.HasAdvancedNetworking() {
+ return nil, fmt.Errorf("edge gateway does not have advanced networking enabled")
+ }
+
+ httpPath, err := eGW.buildProxiedEdgeEndpointURL(types.LBMonitorPath)
+ if err != nil {
+ return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
+ }
+ // We expect to get http.StatusCreated or if not an error of type types.NSXError
+ resp, err := eGW.client.ExecuteRequestWithCustomError(httpPath, http.MethodPost, types.AnyXMLMime,
+ "error creating load balancer service monitor: %s", lbMonitorConfig, &types.NSXError{})
+ if err != nil {
+ return nil, err
+ }
+ location := resp.Header.Get("Location")
+
+ // Last element in location header is the service monitor ID
+ // i.e. Location: [/network/edges/edge-3/loadbalancer/config/monitors/monitor-5]
+ // The code below extracts that ID from the last segment
+ if location == "" {
+ return nil, fmt.Errorf("unable to retrieve ID for new load balancer service monitor with name %s", lbMonitorConfig.Name)
+ }
+ splitLocation := strings.Split(location, "/")
+ lbMonitorID := splitLocation[len(splitLocation)-1]
+
+ readMonitor, err := eGW.ReadLBServiceMonitor(&types.LBMonitor{ID: lbMonitorID})
+ if err != nil {
+ return nil, fmt.Errorf("unable to retrieve monitor with ID (%s) after creation: %s", readMonitor.ID, err)
+ }
+ return readMonitor, nil
+}
+
+// ReadLBServiceMonitor is able to find the types.LBMonitor type by Name and/or ID.
+// If both - Name and ID are specified it performs a lookup by ID and returns an error if the specified name and found
+// name do not match.
+func (eGW *EdgeGateway) ReadLBServiceMonitor(lbMonitorConfig *types.LBMonitor) (*types.LBMonitor, error) {
+ if err := validateReadLBServiceMonitor(lbMonitorConfig); err != nil {
+ return nil, err
+ }
+
+ httpPath, err := eGW.buildProxiedEdgeEndpointURL(types.LBMonitorPath)
+ if err != nil {
+ return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
+ }
+
+ // Anonymous struct to unwrap "monitor response"
+ lbMonitorResponse := &struct {
+ LBMonitors []*types.LBMonitor `xml:"monitor"`
+ }{}
+
+ // This query returns all service monitors as the API does not have filtering options
+ _, err = eGW.client.ExecuteRequest(httpPath, http.MethodGet, types.AnyXMLMime, "unable to read Load Balancer monitor: %s", nil, lbMonitorResponse)
+ if err != nil {
+ return nil, err
+ }
+
+ // Search for monitor by ID or by Name
+ for _, monitor := range lbMonitorResponse.LBMonitors {
+ // If ID was specified for lookup - look for the same ID
+ if lbMonitorConfig.ID != "" && monitor.ID == lbMonitorConfig.ID {
+ return monitor, nil
+ }
+
+ // If Name was specified for lookup - look for the same Name
+ if lbMonitorConfig.Name != "" && monitor.Name == lbMonitorConfig.Name {
+ // We found it by name. Let's verify if search ID was specified and it matches the lookup object
+ if lbMonitorConfig.ID != "" && monitor.ID != lbMonitorConfig.ID {
+ return nil, fmt.Errorf("load balancer monitor was found by name (%s), but it's ID (%s) does not match specified ID (%s)",
+ monitor.Name, monitor.ID, lbMonitorConfig.ID)
+ }
+ return monitor, nil
+ }
+ }
+
+ return nil, fmt.Errorf("could not find load balancer service monitor (name: %s, ID: %s)",
+ lbMonitorConfig.Name, lbMonitorConfig.ID)
+}
+
+// UpdateLBServiceMonitor
+func (eGW *EdgeGateway) UpdateLBServiceMonitor(lbMonitorConfig *types.LBMonitor) (*types.LBMonitor, error) {
+ if err := validateUpdateLBServiceMonitor(lbMonitorConfig); err != nil {
+ return nil, err
+ }
+
+ // if only name was specified for update, ID must be found, because ID is mandatory for update
+ if lbMonitorConfig.ID == "" {
+ readLBMonitor, err := eGW.ReadLBServiceMonitor(&types.LBMonitor{Name: lbMonitorConfig.Name})
+ if err != nil {
+ return nil, err
+ }
+ lbMonitorConfig.ID = readLBMonitor.ID
+ }
+
+ httpPath, err := eGW.buildProxiedEdgeEndpointURL(types.LBMonitorPath + lbMonitorConfig.ID)
+ if err != nil {
+ return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
+ }
+
+ // Result should be 204, if not we expect an error of type types.NSXError
+ _, err = eGW.client.ExecuteRequestWithCustomError(httpPath, http.MethodPut, types.AnyXMLMime,
+ "error while updating load balancer service monitor : %s", lbMonitorConfig, &types.NSXError{})
+ if err != nil {
+ return nil, err
+ }
+
+ readMonitor, err := eGW.ReadLBServiceMonitor(&types.LBMonitor{ID: lbMonitorConfig.ID})
+ if err != nil {
+ return nil, fmt.Errorf("unable to retrieve monitor with ID (%s) after update: %s", readMonitor.ID, err)
+ }
+ return readMonitor, nil
+}
+
+// DeleteLBServiceMonitor is able to delete the types.LBMonitor type by Name and/or ID.
+// If both - Name and ID are specified it performs a lookup by ID and returns an error if the specified name and found
+// name do not match.
+func (eGW *EdgeGateway) DeleteLBServiceMonitor(lbMonitorConfig *types.LBMonitor) error {
+ if err := validateDeleteLBServiceMonitor(lbMonitorConfig); err != nil {
+ return err
+ }
+
+ lbMonitorID := lbMonitorConfig.ID
+ // if only name was specified for deletion, ID must be found, because only ID can be used for deletion
+ if lbMonitorConfig.ID == "" {
+ readLBMonitor, err := eGW.ReadLBServiceMonitor(&types.LBMonitor{Name: lbMonitorConfig.Name})
+ if err != nil {
+ return fmt.Errorf("unable to find load balancer monitor by name for deletion: %s", err)
+ }
+ lbMonitorID = readLBMonitor.ID
+ }
+
+ httpPath, err := eGW.buildProxiedEdgeEndpointURL(types.LBMonitorPath + lbMonitorID)
+ if err != nil {
+ return fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
+ }
+ return eGW.client.ExecuteRequestWithoutResponse(httpPath, http.MethodDelete, types.AnyXMLMime,
+ "unable to delete Service Monitor: %s", nil)
+}
+
+func validateCreateLBServiceMonitor(lbMonitorConfig *types.LBMonitor) error {
+ if lbMonitorConfig.Name == "" {
+ return fmt.Errorf("load balancer monitor Name cannot be empty")
+ }
+
+ if lbMonitorConfig.Timeout == 0 {
+ return fmt.Errorf("load balancer monitor Timeout cannot be 0")
+ }
+
+ if lbMonitorConfig.Interval == 0 {
+ return fmt.Errorf("load balancer monitor Interval cannot be 0")
+ }
+
+ if lbMonitorConfig.MaxRetries == 0 {
+ return fmt.Errorf("load balancer monitor MaxRetries cannot be 0")
+ }
+
+ if lbMonitorConfig.Type == "" {
+ return fmt.Errorf("load balancer monitor Type cannot be empty")
+ }
+
+ return nil
+}
+
+func validateReadLBServiceMonitor(lbMonitorConfig *types.LBMonitor) error {
+ if lbMonitorConfig.ID == "" && lbMonitorConfig.Name == "" {
+ return fmt.Errorf("to read load balancer service monitor at least one of `ID`, `Name` fields must be specified")
+ }
+
+ return nil
+}
+
+func validateUpdateLBServiceMonitor(lbMonitorConfig *types.LBMonitor) error {
+ // Update and create have the same requirements for now
+ return validateCreateLBServiceMonitor(lbMonitorConfig)
+}
+
+func validateDeleteLBServiceMonitor(lbMonitorConfig *types.LBMonitor) error {
+ // Read and delete have the same requirements for now
+ return validateReadLBServiceMonitor(lbMonitorConfig)
+}
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/monitor.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/monitor.go
index 7d7000cec..995250a9b 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/monitor.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/monitor.go
@@ -117,7 +117,7 @@ func prettyDisk(disk types.Disk) string {
}
// Returns an External Network structure as JSON
-func prettyExternalNetwork(network types.ExternalNetworkReference) string {
+func prettyExternalNetwork(network types.ExternalNetwork) string {
byteBuf, err := json.MarshalIndent(network, " ", " ")
if err == nil {
return fmt.Sprintf("%s\n", string(byteBuf))
@@ -144,12 +144,14 @@ func prettyTask(task *types.Task) string {
}
// Returns an Edge Gateway service configuration structure as JSON
-func prettyEdgeGatewayServiceConfiguration(conf *types.EdgeGatewayServiceConfiguration) string {
- byteBuf, err := json.MarshalIndent(conf, " ", " ")
+//func prettyEdgeGatewayServiceConfiguration(conf types.EdgeGatewayServiceConfiguration) string {
+func prettyEdgeGateway(egw types.EdgeGateway) string {
+ result := ""
+ byteBuf, err := json.MarshalIndent(egw, " ", " ")
if err == nil {
- return fmt.Sprintf("%s\n", string(byteBuf))
+ result += fmt.Sprintf("%s\n", string(byteBuf))
}
- return ""
+ return result
}
func LogNetwork(conf types.OrgVDCNetwork) {
@@ -160,11 +162,11 @@ func ShowNetwork(conf types.OrgVDCNetwork) {
out("screen", prettyNetworkConf(conf))
}
-func LogExternalNetwork(network types.ExternalNetworkReference) {
+func LogExternalNetwork(network types.ExternalNetwork) {
out("log", prettyExternalNetwork(network))
}
-func ShowExternalNetwork(network types.ExternalNetworkReference) {
+func ShowExternalNetwork(network types.ExternalNetwork) {
out("screen", prettyExternalNetwork(network))
}
@@ -244,6 +246,14 @@ func outTask(destination string, task *types.Task, howManyTimes int, elapsed tim
out(destination, "-------------------------------\n")
}
+func simpleOutTask(destination string, task *types.Task, howManyTimes int, elapsed time.Duration, first, last bool) {
+ if task == nil {
+ out(destination, "Task is null\n")
+ return
+ }
+ out(destination, "%s (%s) - elapsed: [%s:%d] - progress: %d%%\n", task.OperationName, task.Status, elapsed.Round(1*time.Second), howManyTimes, task.Progress)
+}
+
func LogTask(task *types.Task, howManyTimes int, elapsed time.Duration, first, last bool) {
outTask("log", task, howManyTimes, elapsed, first, last)
}
@@ -251,3 +261,11 @@ func LogTask(task *types.Task, howManyTimes int, elapsed time.Duration, first, l
func ShowTask(task *types.Task, howManyTimes int, elapsed time.Duration, first, last bool) {
outTask("screen", task, howManyTimes, elapsed, first, last)
}
+
+func SimpleShowTask(task *types.Task, howManyTimes int, elapsed time.Duration, first, last bool) {
+ simpleOutTask("screen", task, howManyTimes, elapsed, first, last)
+}
+
+func SimpleLogTask(task *types.Task, howManyTimes int, elapsed time.Duration, first, last bool) {
+ simpleOutTask("log", task, howManyTimes, elapsed, first, last)
+}
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/system.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/system.go
index 7f909223a..071547872 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/system.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/system.go
@@ -8,11 +8,27 @@ import (
"fmt"
"net/http"
"net/url"
+ "regexp"
"strings"
"github.com/vmware/go-vcloud-director/v2/types/v56"
)
+// Simple structure to pass Edge Gateway creation parameters.
+type EdgeGatewayCreation struct {
+ ExternalNetworks []string // List of external networks to be linked to this gateway
+ DefaultGateway string // Which network should be used as default gateway (empty name = no default gateway)
+ OrgName string // parent Org
+ VdcName string // parent VDC
+ Name string // edge gateway name
+ Description string // Optional description
+ BackingConfiguration string // Type of backing configuration (compact, full)
+ AdvancedNetworkingEnabled bool // enable advanced gateway
+ HAEnabled bool // enable HA
+ UseDefaultRouteForDNSRelay bool // True if the default gateway should be used as the DNS relay
+ DistributedRoutingEnabled bool // If advanced networking enabled, also enable distributed routing
+}
+
// Creates an Organization based on settings, network, and org name.
// The Organization created will have these settings specified in the
// settings parameter. The settings variable is defined in types.go.
@@ -43,6 +59,216 @@ func CreateOrg(vcdClient *VCDClient, name string, fullName string, description s
}
+// Returns the UUID part of an entity ID
+// From "urn:vcloud:vdc:72fefde7-4fed-45b8-a774-79b72c870325",
+// will return "72fefde7-4fed-45b8-a774-79b72c870325"
+// From "urn:vcloud:catalog:97384890-180c-4563-b9b7-0dc50a2430b0"
+// will return "97384890-180c-4563-b9b7-0dc50a2430b0"
+func getBareEntityUuid(entityId string) (string, error) {
+ // Regular expression to match an ID:
+ // 3 strings (alphanumeric + "-") separated by a colon (:)
+ // 1 group of 8 hexadecimal digits
+ // 3 groups of 4 hexadecimal
+ // 1 group of 12 hexadecimal digits
+ reGetID := regexp.MustCompile(`^[\w-]+:[\w-]+:[\w-]+:([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$`)
+ matchList := reGetID.FindAllStringSubmatch(entityId, -1)
+
+ // matchList has the format
+ // [][]string{[]string{"TOTAL MATCHED STRING", "CAPTURED TEXT"}}
+ // such as
+ // [][]string{[]string{"urn:vcloud:catalog:97384890-180c-4563-b9b7-0dc50a2430b0", "97384890-180c-4563-b9b7-0dc50a2430b0"}}
+ if len(matchList) == 0 || len(matchList[0]) < 2 {
+ return "", fmt.Errorf("error extracting ID from '%s'", entityId)
+ }
+ return matchList[0][1], nil
+}
+
+// CreateEdgeGatewayAsync creates an edge gateway using a simplified configuration structure
+// https://code.vmware.com/apis/442/vcloud-director/doc/doc/operations/POST-CreateEdgeGateway.html
+func CreateEdgeGatewayAsync(vcdClient *VCDClient, egwc EdgeGatewayCreation) (Task, error) {
+
+ distributed := egwc.DistributedRoutingEnabled
+ if !egwc.AdvancedNetworkingEnabled {
+ distributed = false
+ }
+ // This is the main configuration structure
+ egwConfiguration := &types.EdgeGateway{
+ Xmlns: types.XMLNamespaceVCloud,
+ Name: egwc.Name,
+ Description: egwc.Description,
+ Configuration: &types.GatewayConfiguration{
+ UseDefaultRouteForDNSRelay: egwc.UseDefaultRouteForDNSRelay,
+ HaEnabled: egwc.HAEnabled,
+ GatewayBackingConfig: egwc.BackingConfiguration,
+ AdvancedNetworkingEnabled: egwc.AdvancedNetworkingEnabled,
+ DistributedRoutingEnabled: distributed,
+ GatewayInterfaces: &types.GatewayInterfaces{
+ GatewayInterface: []*types.GatewayInterface{},
+ },
+ EdgeGatewayServiceConfiguration: &types.GatewayFeatures{},
+ },
+ }
+
+ if len(egwc.ExternalNetworks) == 0 {
+ return Task{}, fmt.Errorf("no external networks provided. At least one is needed")
+ }
+
+ // If the user has indicated a default gateway, we make sure that it matches
+ // a name in the list of external networks
+ if egwc.DefaultGateway != "" {
+ defaultGatewayFound := false
+ for _, name := range egwc.ExternalNetworks {
+ if egwc.DefaultGateway == name {
+ defaultGatewayFound = true
+ }
+ }
+ if !defaultGatewayFound {
+ return Task{}, fmt.Errorf("default gateway (%s) selected, but its name is not among the external networks (%v)", egwc.DefaultGateway, egwc.ExternalNetworks)
+ }
+ }
+ // Add external networks inside the configuration structure
+ for _, extNetName := range egwc.ExternalNetworks {
+ extNet, err := GetExternalNetwork(vcdClient, extNetName)
+ if err != nil {
+ return Task{}, err
+ }
+
+ // Populate the subnet participation only if default gateway was set
+ var subnetParticipation *types.SubnetParticipation
+ if egwc.DefaultGateway != "" && extNet.ExternalNetwork.Name == egwc.DefaultGateway {
+ for _, net := range extNet.ExternalNetwork.Configuration.IPScopes.IPScope {
+ if net.IsEnabled {
+ subnetParticipation = &types.SubnetParticipation{
+ Gateway: net.Gateway,
+ Netmask: net.Netmask,
+ }
+ break
+ }
+ }
+ }
+ networkConf := &types.GatewayInterface{
+ Name: extNet.ExternalNetwork.Name,
+ DisplayName: extNet.ExternalNetwork.Name,
+ InterfaceType: "uplink",
+ Network: &types.Reference{
+ HREF: extNet.ExternalNetwork.HREF,
+ ID: extNet.ExternalNetwork.ID,
+ Type: "application/vnd.vmware.admin.network+xml",
+ Name: extNet.ExternalNetwork.Name,
+ },
+ UseForDefaultRoute: egwc.DefaultGateway == extNet.ExternalNetwork.Name,
+ SubnetParticipation: subnetParticipation,
+ }
+
+ egwConfiguration.Configuration.GatewayInterfaces.GatewayInterface =
+ append(egwConfiguration.Configuration.GatewayInterfaces.GatewayInterface, networkConf)
+ }
+
+ // Once the configuration structure has been filled using the simplified data, we delegate
+ // the edge gateway creation to the main configuration function.
+ return CreateAndConfigureEdgeGatewayAsync(vcdClient, egwc.OrgName, egwc.VdcName, egwc.Name, egwConfiguration)
+}
+
+// CreateAndConfigureEdgeGatewayAsync creates an edge gateway using a full configuration structure
+func CreateAndConfigureEdgeGatewayAsync(vcdClient *VCDClient, orgName, vdcName, egwName string, egwConfiguration *types.EdgeGateway) (Task, error) {
+
+ if egwConfiguration.Name != egwName {
+ return Task{}, fmt.Errorf("name mismatch: '%s' used as parameter but '%s' in the configuration structure", egwName, egwConfiguration.Name)
+ }
+ adminOrg, err := GetAdminOrgByName(vcdClient, orgName)
+ if err != nil {
+ return Task{}, err
+ }
+ vdc, err := adminOrg.GetVdcByName(vdcName)
+ if err != nil {
+ return Task{}, err
+ }
+
+ egwCreateHREF := vcdClient.Client.VCDHREF
+
+ vdcId, err := getBareEntityUuid(vdc.Vdc.ID)
+ if err != nil {
+ return Task{}, fmt.Errorf("error retrieving ID from Vdc %s: %s", vdcName, err)
+ }
+ if vdcId == "" {
+ return Task{}, fmt.Errorf("error retrieving ID from Vdc %s - empty ID returned", vdcName)
+ }
+ egwCreateHREF.Path += fmt.Sprintf("/admin/vdc/%s/edgeGateways", vdcId)
+
+ // The first task is the creation task. It is quick, and does only create the vCD entity,
+ // but not yet deploy the underlying VM
+ creationTask, err := vcdClient.Client.ExecuteTaskRequest(egwCreateHREF.String(), http.MethodPost,
+ "application/vnd.vmware.admin.edgeGateway+xml", "error instantiating a new Edge Gateway: %s", egwConfiguration)
+
+ if err != nil {
+ return Task{}, err
+ }
+
+ err = creationTask.WaitTaskCompletion()
+
+ if err != nil {
+ return Task{}, err
+ }
+
+ // After creation, there is a build task that supervises the gateway deployment
+ for _, innerTask := range creationTask.Task.Tasks.Task {
+ if innerTask.OperationName == "networkEdgeGatewayCreate" {
+ deployTask := Task{
+ Task: innerTask,
+ client: &vcdClient.Client,
+ }
+ return deployTask, nil
+ }
+ }
+ return Task{}, fmt.Errorf("no deployment task found for edge gateway %s - The edge gateway might have been created, but not deployed properly", egwName)
+}
+
+// Private convenience function used by CreateAndConfigureEdgeGateway and CreateEdgeGateway to
+// process the task and return the object that was created.
+// It should not be invoked directly.
+func createEdgeGateway(vcdClient *VCDClient, egwc EdgeGatewayCreation, egwConfiguration *types.EdgeGateway) (EdgeGateway, error) {
+ var task Task
+ var err error
+ if egwConfiguration != nil {
+ task, err = CreateAndConfigureEdgeGatewayAsync(vcdClient, egwc.OrgName, egwc.VdcName, egwc.Name, egwConfiguration)
+ } else {
+ task, err = CreateEdgeGatewayAsync(vcdClient, egwc)
+ }
+
+ if err != nil {
+ return EdgeGateway{}, err
+ }
+ err = task.WaitTaskCompletion()
+ if err != nil {
+ return EdgeGateway{}, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err))
+ }
+
+ // The edge gateway is created. Now we retrieve it from the server
+ org, err := GetAdminOrgByName(vcdClient, egwc.OrgName)
+ if err != nil {
+ return EdgeGateway{}, err
+ }
+ vdc, err := org.GetVdcByName(egwc.VdcName)
+ if err != nil {
+ return EdgeGateway{}, err
+ }
+ egw, err := vdc.FindEdgeGateway(egwc.Name)
+ if err != nil {
+ return EdgeGateway{}, err
+ }
+ return egw, nil
+}
+
+// CreateAndConfigureEdgeGateway creates an edge gateway using a full configuration structure
+func CreateAndConfigureEdgeGateway(vcdClient *VCDClient, orgName, vdcName, egwName string, egwConfiguration *types.EdgeGateway) (EdgeGateway, error) {
+ return createEdgeGateway(vcdClient, EdgeGatewayCreation{OrgName: orgName, VdcName: vdcName, Name: egwName}, egwConfiguration)
+}
+
+// CreateEdgeGateway creates an edge gateway using a simplified configuration structure
+func CreateEdgeGateway(vcdClient *VCDClient, egwc EdgeGatewayCreation) (EdgeGateway, error) {
+ return createEdgeGateway(vcdClient, egwc, nil)
+}
+
// If user specifies a valid organization name, then this returns a
// organization object. If no valid org is found, it returns an empty
// org and no error. Otherwise it returns an error and an empty
@@ -149,10 +375,8 @@ func QueryPortGroups(vcdCli *VCDClient, filter string) ([]*types.PortGroupRecord
return results.Results.PortGroupRecord, nil
}
-// GetExternalNetwork returns ExternalNetwork object if user specifies a valid external network name.
-// If no valid external network is found, it returns an empty
-// ExternalNetwork and no error. Otherwise it returns an error and an empty
-// ExternalNetwork object
+// GetExternalNetwork returns an ExternalNetwork reference if user the network name matches an existing one.
+// If no valid external network is found, it returns an empty ExternalNetwork reference and an error
func GetExternalNetwork(vcdClient *VCDClient, networkName string) (*ExternalNetwork, error) {
if !vcdClient.Client.IsSysAdmin {
@@ -173,17 +397,22 @@ func GetExternalNetwork(vcdClient *VCDClient, networkName string) (*ExternalNetw
externalNetwork := NewExternalNetwork(&vcdClient.Client)
+ found := false
for _, netRef := range extNetworkRefs.ExternalNetworkReference {
if netRef.Name == networkName {
externalNetwork.ExternalNetwork.HREF = netRef.HREF
err = externalNetwork.Refresh()
+ found = true
if err != nil {
return &ExternalNetwork{}, err
}
}
}
- return externalNetwork, nil
+ if found {
+ return externalNetwork, nil
+ }
+ return externalNetwork, fmt.Errorf("could not find external network named %s", networkName)
}
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/task.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/task.go
index 33fb05304..edc7e820b 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/task.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/task.go
@@ -8,6 +8,7 @@ import (
"fmt"
"net/http"
"net/url"
+ "os"
"strconv"
"time"
@@ -92,6 +93,7 @@ func (task *Task) WaitInspectTaskCompletion(inspectionFunc InspectionFunc, delay
return fmt.Errorf("cannot refresh, Object is empty")
}
+ taskMonitor := os.Getenv("GOVCD_TASK_MONITOR")
howManyTimesRefreshed := 0
startTime := time.Now()
for {
@@ -126,6 +128,22 @@ func (task *Task) WaitInspectTaskCompletion(inspectionFunc InspectionFunc, delay
return nil
}
+ // If the environment variable "GOVCD_TASK_MONITOR" is set, its value
+ // will be used to choose among pre-defined InspectionFunc
+ if inspectionFunc == nil {
+ if taskMonitor != "" {
+ switch taskMonitor {
+ case "log":
+ inspectionFunc = LogTask // writes full task details to the log
+ case "show":
+ inspectionFunc = ShowTask // writes full task details to the screen
+ case "simple_log":
+ inspectionFunc = SimpleLogTask // writes a summary line for the task to the log
+ case "simple_show":
+ inspectionFunc = SimpleShowTask // writes a summary line for the task to the screen
+ }
+ }
+ }
if inspectionFunc != nil {
inspectionFunc(task.Task,
howManyTimesRefreshed,
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/vm.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/vm.go
index faa702023..37e357e54 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/vm.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/vm.go
@@ -539,7 +539,7 @@ func (vm *VM) GetQuestion() (types.VmPendingQuestion, error) {
}
if http.StatusOK != resp.StatusCode {
- return types.VmPendingQuestion{}, fmt.Errorf("error getting question: %s", ParseErr(resp))
+ return types.VmPendingQuestion{}, fmt.Errorf("error getting question: %s", ParseErr(resp, &types.Error{}))
}
question := &types.VmPendingQuestion{}
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/constants.go b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/constants.go
index 53def40f7..474242b6f 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/constants.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/constants.go
@@ -15,6 +15,7 @@ const (
JSONMimeV57 = "application/json;version=5.7"
// AnyXMLMime511 the wildcard xml mime for version 5.11 of the API
AnyXMLMime511 = "application/*+xml;version=5.11"
+ AnyXMLMime = "application/xml"
// Version511 the 5.11 version
Version511 = "5.11"
// Version is the default version number
@@ -144,3 +145,7 @@ const (
XMLNamespaceVSSD = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
XMLNamespaceExtension = "http://www.vmware.com/vcloud/extension/v1.5"
)
+
+const (
+ LBMonitorPath = "/loadbalancer/config/monitors/"
+)
diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/types.go b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/types.go
index b19fd7379..7ac2b8ae5 100644
--- a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/types.go
+++ b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/types.go
@@ -6,6 +6,7 @@ package types
import (
"encoding/xml"
+ "fmt"
)
// Maps status Attribute Values for VAppTemplate, VApp, Vm, and Media Objects
@@ -120,7 +121,6 @@ type NetworkFeatures struct {
DhcpService *DhcpService `xml:"DhcpService,omitempty"` // Substitute for NetworkService. DHCP service settings
FirewallService *FirewallService `xml:"FirewallService,omitempty"` // Substitute for NetworkService. Firewall service settings
NatService *NatService `xml:"NatService,omitempty"` // Substitute for NetworkService. NAT service settings
- LoadBalancerService *LoadBalancerService `xml:"LoadBalancerService,omitempty"` // Substitute for NetworkService. Load Balancer service settings
StaticRoutingService *StaticRoutingService `xml:"StaticRoutingService,omitempty"` // Substitute for NetworkService. Static Routing service settings
// TODO: Not Implemented
// IpsecVpnService IpsecVpnService `xml:"IpsecVpnService,omitempty"` // Substitute for NetworkService. Ipsec Vpn service settings
@@ -931,6 +931,25 @@ type Error struct {
StackTrace string `xml:"stackTrace,attr,omitempty"`
}
+func (err Error) Error() string {
+ return fmt.Sprintf("API Error: %d: %s", err.MajorErrorCode, err.Message)
+}
+
+// NSXError is the standard error message type used in the NSX API which is proxied by vCD.
+// It has attached method `Error() string` and implements Go's default `type error` interface.
+type NSXError struct {
+ XMLName xml.Name `xml:"error"`
+ ErrorCode string `xml:"errorCode"`
+ Details string `xml:"details"`
+ ModuleName string `xml:"moduleName"`
+}
+
+// Error method implements Go's default `error` interface for NSXError and formats NSX error
+// output for human readable output.
+func (nsxErr NSXError) Error() string {
+ return fmt.Sprintf("%s %s (API error: %s)", nsxErr.ModuleName, nsxErr.Details, nsxErr.ErrorCode)
+}
+
// File represents a file to be transferred (uploaded or downloaded).
// Type: FileType
// Namespace: http://www.vmware.com/vcloud/v1.5
@@ -1486,6 +1505,7 @@ type InstantiateVAppTemplateParams struct {
// Since: 5.1
type EdgeGateway struct {
// Attributes
+ Xmlns string `xml:"xmlns,attr,omitempty"`
HREF string `xml:"href,attr,omitempty"` // The URI of the entity.
Type string `xml:"type,attr,omitempty"` // The MIME type of the entity.
ID string `xml:"id,attr,omitempty"` // The entity identifier, expressed in URN format. The value of this attribute uniquely identifies the entity, persists for the life of the entity, and is never reused
@@ -1512,6 +1532,8 @@ type GatewayConfiguration struct {
GatewayInterfaces *GatewayInterfaces `xml:"GatewayInterfaces"` // List of Gateway interfaces.
EdgeGatewayServiceConfiguration *GatewayFeatures `xml:"EdgeGatewayServiceConfiguration,omitempty"` // Represents Gateway Features.
HaEnabled bool `xml:"HaEnabled,omitempty"` // True if this gateway is highly available. (Requires two vShield edge VMs.)
+ AdvancedNetworkingEnabled bool `xml:"AdvancedNetworkingEnabled,omitempty"` // True if the gateway uses advanced networking
+ DistributedRoutingEnabled bool `xml:"DistributedRoutingEnabled,omitempty"` // True if gateway is attached to a Distributed Logical Router
UseDefaultRouteForDNSRelay bool `xml:"UseDefaultRouteForDnsRelay,omitempty"` // True if the default gateway on the external network selected for default route should be used as the DNS relay.
}
@@ -1575,7 +1597,6 @@ type GatewayFeatures struct {
NatService *NatService `xml:"NatService,omitempty"` // Substitute for NetworkService. NAT service settings
GatewayDhcpService *GatewayDhcpService `xml:"GatewayDhcpService,omitempty"` // Substitute for NetworkService. Gateway DHCP service settings
GatewayIpsecVpnService *GatewayIpsecVpnService `xml:"GatewayIpsecVpnService,omitempty"` // Substitute for NetworkService. Gateway Ipsec VPN service settings
- LoadBalancerService *LoadBalancerService `xml:"LoadBalancerService,omitempty"` // Substitute for NetworkService. Load Balancer service settings
StaticRoutingService *StaticRoutingService `xml:"StaticRoutingService,omitempty"` // Substitute for NetworkService. Static Routing service settings
}
@@ -1602,70 +1623,26 @@ type StaticRoute struct {
GatewayInterface *Reference `xml:"GatewayInterface,omitempty"` // Gateway interface to which static route is bound.
}
-// LoadBalancerService represents gateway load balancer service.
-// Type: LoadBalancerServiceType
-// Namespace: http://www.vmware.com/vcloud/v1.5
-// Description: Represents gateway load balancer service.
-// Since: 5.1
-type LoadBalancerService struct {
- IsEnabled bool `xml:"IsEnabled"` // Enable or disable the service using this flag
- Pool *LoadBalancerPool `xml:"Pool,omitempty"` // List of load balancer pools.
- VirtualServer *LoadBalancerVirtualServer `xml:"VirtualServer,omitempty"` // List of load balancer virtual servers.
-}
-
-// LoadBalancerPool represents a load balancer pool.
-// Type: LoadBalancerPoolType
-// Namespace: http://www.vmware.com/vcloud/v1.5
-// Description: Represents a load balancer pool.
-// Since: 5.1
-type LoadBalancerPool struct {
- ID string `xml:"Id,omitempty"` // Load balancer pool id.
- Name string `xml:"Name"` // Load balancer pool name.
- Description string `xml:"Description,omitempty"` // Load balancer pool description.
- ServicePort *LBPoolServicePort `xml:"ServicePort"` // Load balancer pool service port.
- Member *LBPoolMember `xml:"Member"` // Load balancer pool member.
- Operational bool `xml:"Operational,omitempty"` // True if the load balancer pool is operational.
- ErrorDetails string `xml:"ErrorDetails,omitempty"` // Error details for this pool.
-}
-
-// LBPoolServicePort represents a service port in a load balancer pool.
-// Type: LBPoolServicePortType
-// Namespace: http://www.vmware.com/vcloud/v1.5
-// Description: Represents a service port in a load balancer pool.
-// Since: 5.1
-type LBPoolServicePort struct {
- IsEnabled bool `xml:"IsEnabled,omitempty"` // True if this service port is enabled.
- Protocol string `xml:"Protocol"` // Load balancer protocol type. One of: HTTP, HTTPS, TCP.
- Algorithm string `xml:"Algorithm"` // Load Balancer algorithm type. One of: IP_HASH, ROUND_ROBIN, URI, LEAST_CONN.
- Port string `xml:"Port"` // Port for this service profile.
- HealthCheckPort string `xml:"HealthCheckPort,omitempty"` // Health check port for this profile.
- HealthCheck *LBPoolHealthCheck `xml:"HealthCheck,omitempty"` // Health check list.
-}
-
-// LBPoolHealthCheck represents a service port health check list.
-// Type: LBPoolHealthCheckType
-// Namespace: http://www.vmware.com/vcloud/v1.5
-// Description: Represents a service port health check list.
-// Since: 5.1
-type LBPoolHealthCheck struct {
- Mode string `xml:"Mode"` // Load balancer service port health check mode. One of: TCP, HTTP, SSL.
- URI string `xml:"Uri,omitempty"` // Load balancer service port health check URI.
- HealthThreshold string `xml:"HealthThreshold,omitempty"` // Health threshold for this service port.
- UnhealthThreshold string `xml:"UnhealthThreshold,omitempty"` // Unhealth check port for this profile.
- Interval string `xml:"Interval,omitempty"` // Interval between health checks.
- Timeout string `xml:"Timeout,omitempty"` // Health check timeout.
-}
-
-// LBPoolMember represents a member in a load balancer pool.
-// Type: LBPoolMemberType
-// Namespace: http://www.vmware.com/vcloud/v1.5
-// Description: Represents a member in a load balancer pool.
-// Since: 5.1
-type LBPoolMember struct {
- IPAddress string `xml:"IpAddress"` // Ip Address for load balancer member.
- Weight string `xml:"Weight"` // Weight of this member.
- ServicePort *LBPoolServicePort `xml:"ServicePort,omitempty"` // Load balancer member service port.
-}
+// LBMonitor defines health check parameters for a particular type of network traffic
+// Reference: vCloud Director API for NSX Programming Guide
+// https://code.vmware.com/docs/6900/vcloud-director-api-for-nsx-programming-guide
+type LBMonitor struct {
+ XMLName xml.Name `xml:"monitor"`
+ ID string `xml:"monitorId,omitempty"`
+ Type string `xml:"type"`
+ Interval int `xml:"interval,omitempty"`
+ Timeout int `xml:"timeout,omitempty"`
+ MaxRetries int `xml:"maxRetries,omitempty"`
+ Method string `xml:"method,omitempty"`
+ URL string `xml:"url,omitempty"`
+ Expected string `xml:"expected,omitempty"`
+ Name string `xml:"name,omitempty"`
+ Send string `xml:"send,omitempty"`
+ Receive string `xml:"receive,omitempty"`
+ Extension string `xml:"extension,omitempty"`
+}
+
+type LBMonitors []LBMonitor
// LoadBalancerVirtualServer represents a load balancer virtual server.
// Type: LoadBalancerVirtualServerType
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 2562fd530..23e7fea2b 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -216,7 +216,7 @@ github.com/ulikunitz/xz/internal/hash
# github.com/vmihailenco/msgpack v4.0.1+incompatible
github.com/vmihailenco/msgpack
github.com/vmihailenco/msgpack/codes
-# github.com/vmware/go-vcloud-director/v2 v2.3.0-alpha.1
+# github.com/vmware/go-vcloud-director/v2 v2.3.0-alpha.3
github.com/vmware/go-vcloud-director/v2/govcd
github.com/vmware/go-vcloud-director/v2/types/v56
github.com/vmware/go-vcloud-director/v2/util
diff --git a/website/docs/d/lb_service_monitor.html.markdown b/website/docs/d/lb_service_monitor.html.markdown
new file mode 100644
index 000000000..dad0504d4
--- /dev/null
+++ b/website/docs/d/lb_service_monitor.html.markdown
@@ -0,0 +1,42 @@
+---
+layout: "vcd"
+page_title: "vCloudDirector: vcd_lb_service_monitor"
+sidebar_current: "docs-vcd-data-source-lb-service-monitor"
+description: |-
+ Provides a NSX load balancer service monitor data source.
+---
+
+# vcd\_lb\_service\_monitor
+
+Provides a vCloud Director Edge Gateway Load Balancer Service Monitor data source. A service monitor
+defines health check parameters for a particular type of network traffic. It can be associated with
+a pool. Pool members are monitored according to the service monitor parameters.
+
+~> **Note:** See additional support notes in [service monitor resource page](/docs/providers/vcd/r/lb_service_monitor.html).
+
+Supported in provider *v2.4+*
+
+## Example Usage
+
+```hcl
+data "vcd_lb_service_monitor" "my-monitor" {
+ org = "my-org"
+ vdc = "my-org-vdc"
+ edge_gateway = "my-edge-gw"
+
+ name = "not-managed"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `edge_gateway` - (Required) The name of the edge gateway on which the service monitor is defined
+* `vdc` - (Optional) The name of VDC to use, optional if defined at provider level
+* `org` - (Optional) The name of organization to use, optional if defined at provider level. Useful when connected as sysadmin working across different organisations
+* `name` - (Required) Service Monitor name for identifying the exact service monitor
+
+## Attribute Reference
+
+All the attributes defined in `vcd_lb_service_monitor` resource are be available.
diff --git a/website/docs/r/lb_service_monitor.html.markdown b/website/docs/r/lb_service_monitor.html.markdown
new file mode 100644
index 000000000..a6c7534a6
--- /dev/null
+++ b/website/docs/r/lb_service_monitor.html.markdown
@@ -0,0 +1,99 @@
+---
+layout: "vcd"
+page_title: "vCloudDirector: vcd_lb_service_monitor"
+sidebar_current: "docs-vcd-resource-lb-service-monitor"
+description: |-
+ Provides a NSX load balancer service monitor resource.
+---
+
+# vcd\_lb\_service\_monitor
+
+Provides a vCloud Director Edge Gateway Load Balancer Service Monitor resource. A service monitor
+defines health check parameters for a particular type of network traffic. It can be associated with
+a pool. Pool members are monitored according to the service monitor parameters.
+
+~> **Note:** To make load balancing work one must ensure that load balancing is enabled on edge gateway (edge gateway must be advanced).
+This depends on NSX version to work properly. Please refer to [VMware Product Interoperability Matrices](https://www.vmware.com/resources/compatibility/sim/interop_matrix.php#interop&29=&93=)
+to check supported vCloud director and NSX for vSphere configurations.
+
+~> **Note:** The vCloud Director API for NSX supports a subset of the operations and objects defined in the NSX vSphere
+API Guide. The API supports NSX 6.2, 6.3, and 6.4.
+
+Supported in provider *v2.4+*
+
+## Example Usage
+
+```hcl
+provider "vcd" {
+ user = "${var.admin_user}"
+ password = "${var.admin_password}"
+ org = "System"
+ url = "https://AcmeVcd/api"
+}
+
+resource "vcd_lb_service_monitor" "monitor" {
+ org = "my-org"
+ vdc = "my-org-vdc"
+ edge_gateway = "my-edge-gw"
+
+ name = "http-monitor"
+ interval = "5"
+ timeout = "20"
+ max_retries = "3"
+ type = "http"
+ method = "GET"
+ url = "/health"
+ send = "{\"key\": \"value\"}"
+ extension = {
+ content-type = "application/json"
+ linespan = ""
+ }
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `edge_gateway` - (Required) The name of the edge gateway on which the service monitor is to be created
+* `vdc` - (Optional) The name of VDC to use, optional if defined at provider level
+* `org` - (Optional) The name of organization to use, optional if defined at provider level. Useful when connected as sysadmin working across different organisations
+* `name` - (Required) Service Monitor name
+
+* `interval` - (Required) Interval in seconds at which a server is to be monitored using the specified Method.
+* `timeout` - (Required) Maximum time in seconds within which a response from the server must be received
+* `max_retries` - (Required) Number of times the specified monitoring Method must fail sequentially before the server is declared down
+* `type` - (Required) Select the way in which you want to send the health check request to the server — `http`, `https`,
+`tcp`, `icmp`, or `udp`. Depending on the type selected, the remaining attributes are allowed or not
+* `method` - (Optional) For types `http` and `https`. Select http method to be used to detect server status. One of OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, or CONNECT
+* `url` - (Optional) For types `http` and `https`. URL to be used in the server status request
+* `send` - (Optional) For types `http`, `https`, and `udp`. The data to be sent.
+* `expected` - (Optional) For types `http` and `https`. String that the monitor expects to match in the status line of
+the HTTP or HTTPS response (for example, `HTTP/1.1`)
+* `receive` - (Optional) For types `http`, `https`, and `udp`. The string to be matched in the response content.
+**Note**: When `expected` is not matched, the monitor does not try to match the Receive content
+* `extension` - (Required) A map of advanced monitor parameters as key=value pairs (i.e. `max-age=SECONDS`, `invert-regex`)
+**Note**: When you need a value of `key` only format just set value to empty string (i.e. `linespan = ""`)
+
+## Attribute Reference
+
+The following attributes are exported on the base level of this resource:
+
+* `id` - The ID of the load balancer service monitor
+
+## Importing
+
+~> **Note:** The current implementation of Terraform import can only import resources into the state. It does not generate
+configuration. [More information.](https://www.terraform.io/docs/import/)
+
+An existing load balancer service monitor can be [imported][docs-import] into this resource
+via supplying the full dot separated path for load balancer service monitor. An example is below:
+
+[docs-import]: /docs/import/index.html
+
+```
+terraform import vcd_lb_service_monitor.imported my-org.my-org-vdc.my-edge-gw.my-lb-service-monitor
+```
+
+The above would import the service monitor named `my-lb-service-monitor` that is defined on edge gateway
+`my-edge-gw` which is configured in organization named `my-org` and vDC named `my-org-vdc`.
diff --git a/website/vcd.erb b/website/vcd.erb
index 53d3c515a..839fb84f8 100644
--- a/website/vcd.erb
+++ b/website/vcd.erb
@@ -10,6 +10,15 @@
VMware vCloudDirector Provider
+
>
+ Data Sources
+
+
+
>
Resources