Adding Support for LBaaS v2 - Health Monitors
diff --git a/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go b/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go
new file mode 100644
index 0000000..71d0ade
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go
@@ -0,0 +1,274 @@
+package monitors
+
+import (
+ "fmt"
+
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the Monitor attributes you want to see returned. SortKey allows you to
+// sort by a particular Monitor attribute. SortDir sets the direction, and is
+// either `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+ ID string `q:"id"`
+ TenantID string `q:"tenant_id"`
+ Type string `q:"type"`
+ Delay int `q:"delay"`
+ Timeout int `q:"timeout"`
+ MaxRetries int `q:"max_retries"`
+ HTTPMethod string `q:"http_method"`
+ URLPath string `q:"url_path"`
+ ExpectedCodes string `q:"expected_codes"`
+ AdminStateUp *bool `q:"admin_state_up"`
+ Status string `q:"status"`
+ Limit int `q:"limit"`
+ Marker string `q:"marker"`
+ SortKey string `q:"sort_key"`
+ SortDir string `q:"sort_dir"`
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// routers. It accepts a ListOpts struct, which allows you to filter and sort
+// the returned collection for greater efficiency.
+//
+// Default policy settings return only those routers that are owned by the
+// tenant who submits the request, unless an admin user submits the request.
+func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
+ q, err := gophercloud.BuildQueryString(&opts)
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ u := rootURL(c) + q.String()
+
+ return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page {
+ return MonitorPage{pagination.LinkedPageBase{PageResult: r}}
+ })
+}
+
+// Constants that represent approved monitoring types.
+const (
+ TypePING = "PING"
+ TypeTCP = "TCP"
+ TypeHTTP = "HTTP"
+ TypeHTTPS = "HTTPS"
+)
+
+var (
+ errPoolIDRequired = fmt.Errorf("Pool ID to monitor is required")
+ errValidTypeRequired = fmt.Errorf("A valid Type is required. Supported values are PING, TCP, HTTP and HTTPS")
+ errDelayRequired = fmt.Errorf("Delay is required")
+ errTimeoutRequired = fmt.Errorf("Timeout is required")
+ errMaxRetriesRequired = fmt.Errorf("MaxRetries is required")
+ errURLPathRequired = fmt.Errorf("URL path is required")
+ errExpectedCodesRequired = fmt.Errorf("ExpectedCodes is required")
+ errDelayMustGETimeout = fmt.Errorf("Delay must be greater than or equal to timeout")
+)
+
+// CreateOpts contains all the values needed to create a new Health Monitor.
+type CreateOpts struct {
+ // The Pool to Monitor.
+ PoolID string
+
+ // Required for admins. Indicates the owner of the Loadbalancer.
+ TenantID string
+
+ // Required. The type of probe, which is PING, TCP, HTTP, or HTTPS, that is
+ // sent by the load balancer to verify the member state.
+ Type string
+
+ // Required. The time, in seconds, between sending probes to members.
+ Delay int
+
+ // Required. Maximum number of seconds for a Monitor to wait for a ping reply
+ // before it times out. The value must be less than the delay value.
+ Timeout int
+
+ // Required. Number of permissible ping failures before changing the member's
+ // status to INACTIVE. Must be a number between 1 and 10.
+ MaxRetries int
+
+ // Required for HTTP(S) types. URI path that will be accessed if Monitor type
+ // is HTTP or HTTPS.
+ URLPath string
+
+ // Required for HTTP(S) types. The HTTP method used for requests by the
+ // Monitor. If this attribute is not specified, it defaults to "GET".
+ HTTPMethod string
+
+ // Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
+ // Monitor. You can either specify a single status like "200", or a range
+ // like "200-202".
+ ExpectedCodes string
+
+ AdminStateUp *bool
+}
+
+// Create is an operation which provisions a new Health Monitor. There are
+// different types of Monitor you can provision: PING, TCP or HTTP(S). Below
+// are examples of how to create each one.
+//
+// Here is an example config struct to use when creating a PING or TCP Monitor:
+//
+// CreateOpts{Type: TypePING, Delay: 20, Timeout: 10, MaxRetries: 3}
+// CreateOpts{Type: TypeTCP, Delay: 20, Timeout: 10, MaxRetries: 3}
+//
+// Here is an example config struct to use when creating a HTTP(S) Monitor:
+//
+// CreateOpts{Type: TypeHTTP, Delay: 20, Timeout: 10, MaxRetries: 3,
+// HttpMethod: "HEAD", ExpectedCodes: "200". PoolID: "2c946bfc-1804-43ab-a2ff-58f6a762b505"}
+//
+func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
+ var res CreateResult
+
+ // Validate inputs
+ allowed := map[string]bool{TypeHTTP: true, TypeHTTPS: true, TypeTCP: true, TypePING: true}
+ if opts.Type == "" || allowed[opts.Type] == false {
+ res.Err = errValidTypeRequired
+ }
+ if opts.PoolID == "" {
+ res.Err = errPoolIDRequired
+ }
+ if opts.Delay == 0 {
+ res.Err = errDelayRequired
+ }
+ if opts.Timeout == 0 {
+ res.Err = errTimeoutRequired
+ }
+ if opts.MaxRetries == 0 {
+ res.Err = errMaxRetriesRequired
+ }
+ if opts.Type == TypeHTTP || opts.Type == TypeHTTPS {
+ if opts.URLPath == "" {
+ res.Err = errURLPathRequired
+ }
+ if opts.ExpectedCodes == "" {
+ res.Err = errExpectedCodesRequired
+ }
+ }
+ if opts.Delay < opts.Timeout {
+ res.Err = errDelayMustGETimeout
+ }
+ if res.Err != nil {
+ return res
+ }
+
+ type monitor struct {
+ Type string `json:"type"`
+ PoolID string `json:"pool_id"`
+ Delay int `json:"delay"`
+ Timeout int `json:"timeout"`
+ MaxRetries int `json:"max_retries"`
+ TenantID *string `json:"tenant_id,omitempty"`
+ URLPath *string `json:"url_path,omitempty"`
+ ExpectedCodes *string `json:"expected_codes,omitempty"`
+ HTTPMethod *string `json:"http_method,omitempty"`
+ AdminStateUp *bool `json:"admin_state_up,omitempty"`
+ }
+
+ type request struct {
+ Monitor monitor `json:"health_monitor"`
+ }
+
+ reqBody := request{Monitor: monitor{
+ Type: opts.Type,
+ PoolID: opts.PoolID,
+ Delay: opts.Delay,
+ Timeout: opts.Timeout,
+ MaxRetries: opts.MaxRetries,
+ TenantID: gophercloud.MaybeString(opts.TenantID),
+ URLPath: gophercloud.MaybeString(opts.URLPath),
+ ExpectedCodes: gophercloud.MaybeString(opts.ExpectedCodes),
+ HTTPMethod: gophercloud.MaybeString(opts.HTTPMethod),
+ AdminStateUp: opts.AdminStateUp,
+ }}
+
+ _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
+ return res
+}
+
+// Get retrieves a particular Health Monitor based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) GetResult {
+ var res GetResult
+ _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil)
+ return res
+}
+
+// UpdateOpts contains all the values needed to update an existing Monitor.
+// Attributes not listed here but appear in CreateOpts are immutable and cannot
+// be updated.
+type UpdateOpts struct {
+ // Required. The time, in seconds, between sending probes to members.
+ Delay int
+
+ // Required. Maximum number of seconds for a Monitor to wait for a ping reply
+ // before it times out. The value must be less than the delay value.
+ Timeout int
+
+ // Required. Number of permissible ping failures before changing the member's
+ // status to INACTIVE. Must be a number between 1 and 10.
+ MaxRetries int
+
+ // Required for HTTP(S) types. URI path that will be accessed if Monitor type
+ // is HTTP or HTTPS.
+ URLPath string
+
+ // Required for HTTP(S) types. The HTTP method used for requests by the
+ // Monitor. If this attribute is not specified, it defaults to "GET".
+ HTTPMethod string
+
+ // Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
+ // Monitor. You can either specify a single status like "200", or a range
+ // like "200-202".
+ ExpectedCodes string
+
+ AdminStateUp *bool
+}
+
+// Update is an operation which modifies the attributes of the specified Monitor.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
+ var res UpdateResult
+
+ if opts.Delay > 0 && opts.Timeout > 0 && opts.Delay < opts.Timeout {
+ res.Err = errDelayMustGETimeout
+ }
+
+ type monitor struct {
+ Delay int `json:"delay"`
+ Timeout int `json:"timeout"`
+ MaxRetries int `json:"max_retries"`
+ URLPath *string `json:"url_path,omitempty"`
+ ExpectedCodes *string `json:"expected_codes,omitempty"`
+ HTTPMethod *string `json:"http_method,omitempty"`
+ AdminStateUp *bool `json:"admin_state_up,omitempty"`
+ }
+
+ type request struct {
+ Monitor monitor `json:"health_monitor"`
+ }
+
+ reqBody := request{Monitor: monitor{
+ Delay: opts.Delay,
+ Timeout: opts.Timeout,
+ MaxRetries: opts.MaxRetries,
+ URLPath: gophercloud.MaybeString(opts.URLPath),
+ ExpectedCodes: gophercloud.MaybeString(opts.ExpectedCodes),
+ HTTPMethod: gophercloud.MaybeString(opts.HTTPMethod),
+ AdminStateUp: opts.AdminStateUp,
+ }}
+
+ _, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{200, 202},
+ })
+
+ return res
+}
+
+// Delete will permanently delete a particular Monitor based on its unique ID.
+func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
+ var res DeleteResult
+ _, res.Err = c.Delete(resourceURL(c, id), nil)
+ return res
+}
diff --git a/openstack/networking/v2/extensions/lbaas_v2/monitors/requests_test.go b/openstack/networking/v2/extensions/lbaas_v2/monitors/requests_test.go
new file mode 100644
index 0000000..4e7df2a
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/monitors/requests_test.go
@@ -0,0 +1,323 @@
+package monitors
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestURLs(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ th.AssertEquals(t, th.Endpoint()+"v2.0/lbaas/health_monitors", rootURL(fake.ServiceClient()))
+}
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lbaas/health_monitors", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "health_monitors":[
+ {
+ "admin_state_up":true,
+ "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea",
+ "delay":10,
+ "max_retries":1,
+ "timeout":1,
+ "type":"PING",
+ "pools": [{"id": "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d"}],
+ "id":"466c8345-28d8-4f84-a246-e04380b0461d"
+ },
+ {
+ "admin_state_up":true,
+ "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea",
+ "delay":5,
+ "expected_codes":"200",
+ "max_retries":2,
+ "http_method":"GET",
+ "timeout":2,
+ "url_path":"/",
+ "type":"HTTP",
+ "pools": [{"id": "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}],
+ "id":"5d4b5228-33b0-4e60-b225-9b727c1a20e7"
+ }
+ ]
+}
+ `)
+ })
+
+ count := 0
+
+ List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractMonitors(page)
+ if err != nil {
+ t.Errorf("Failed to extract monitors: %v", err)
+ return false, err
+ }
+
+ expected := []Monitor{
+ {
+ AdminStateUp: true,
+ TenantID: "83657cfcdfe44cd5920adaf26c48ceea",
+ Delay: 10,
+ MaxRetries: 1,
+ Timeout: 1,
+ Type: "PING",
+ ID: "466c8345-28d8-4f84-a246-e04380b0461d",
+ Pools: []map[string]interface{}{{"id": "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d"}},
+ },
+ {
+ AdminStateUp: true,
+ TenantID: "83657cfcdfe44cd5920adaf26c48ceea",
+ Delay: 5,
+ ExpectedCodes: "200",
+ MaxRetries: 2,
+ Timeout: 2,
+ URLPath: "/",
+ Type: "HTTP",
+ HTTPMethod: "GET",
+ ID: "5d4b5228-33b0-4e60-b225-9b727c1a20e7",
+ Pools: []map[string]interface{}{{"id": "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}},
+ },
+ }
+
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
+
+func TestDelayMustBeGreaterOrEqualThanTimeout(t *testing.T) {
+ _, err := Create(fake.ServiceClient(), CreateOpts{
+ Type: "HTTP",
+ PoolID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d",
+ Delay: 1,
+ Timeout: 10,
+ MaxRetries: 5,
+ URLPath: "/check",
+ ExpectedCodes: "200-299",
+ }).Extract()
+
+ if err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+
+ _, err = Update(fake.ServiceClient(), "453105b9-1754-413f-aab1-55f1af620750", UpdateOpts{
+ Delay: 1,
+ Timeout: 10,
+ }).Extract()
+
+ if err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lbaas/health_monitors", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "health_monitor":{
+ "type":"HTTP",
+ "pool_id": "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d",
+ "tenant_id":"453105b9-1754-413f-aab1-55f1af620750",
+ "delay":20,
+ "timeout":10,
+ "max_retries":5,
+ "url_path":"/check",
+ "expected_codes":"200-299"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "health_monitor":{
+ "id":"f3eeab00-8367-4524-b662-55e64d4cacb5",
+ "tenant_id":"453105b9-1754-413f-aab1-55f1af620750",
+ "type":"HTTP",
+ "pool_id": "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d",
+ "delay":20,
+ "timeout":10,
+ "max_retries":5,
+ "http_method":"GET",
+ "url_path":"/check",
+ "expected_codes":"200-299",
+ "admin_state_up":true,
+ "status":"ACTIVE"
+ }
+}
+ `)
+ })
+
+ _, err := Create(fake.ServiceClient(), CreateOpts{
+ Type: "HTTP",
+ PoolID: "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d",
+ TenantID: "453105b9-1754-413f-aab1-55f1af620750",
+ Delay: 20,
+ Timeout: 10,
+ MaxRetries: 5,
+ URLPath: "/check",
+ ExpectedCodes: "200-299",
+ }).Extract()
+
+ th.AssertNoErr(t, err)
+}
+
+func TestRequiredCreateOpts(t *testing.T) {
+ res := Create(fake.ServiceClient(), CreateOpts{})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), CreateOpts{Type: TypeHTTP})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+}
+
+func TestGet(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lbaas/health_monitors/f3eeab00-8367-4524-b662-55e64d4cacb5", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "health_monitor":{
+ "id":"f3eeab00-8367-4524-b662-55e64d4cacb5",
+ "tenant_id":"453105b9-1754-413f-aab1-55f1af620750",
+ "type":"HTTP",
+ "pools": [{"id": "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d"}],
+ "delay":20,
+ "timeout":10,
+ "max_retries":5,
+ "http_method":"GET",
+ "url_path":"/check",
+ "expected_codes":"200-299",
+ "admin_state_up":true,
+ "status":"ACTIVE"
+ }
+}
+ `)
+ })
+
+ hm, err := Get(fake.ServiceClient(), "f3eeab00-8367-4524-b662-55e64d4cacb5").Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, "f3eeab00-8367-4524-b662-55e64d4cacb5", hm.ID)
+ th.AssertEquals(t, "453105b9-1754-413f-aab1-55f1af620750", hm.TenantID)
+ th.AssertEquals(t, "HTTP", hm.Type)
+ th.AssertEquals(t, "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d", hm.Pools[0]["id"])
+ th.AssertEquals(t, 20, hm.Delay)
+ th.AssertEquals(t, 10, hm.Timeout)
+ th.AssertEquals(t, 5, hm.MaxRetries)
+ th.AssertEquals(t, "GET", hm.HTTPMethod)
+ th.AssertEquals(t, "/check", hm.URLPath)
+ th.AssertEquals(t, "200-299", hm.ExpectedCodes)
+ th.AssertEquals(t, true, hm.AdminStateUp)
+ th.AssertEquals(t, "ACTIVE", hm.Status)
+}
+
+func TestUpdate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lbaas/health_monitors/b05e44b5-81f9-4551-b474-711a722698f7", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "health_monitor":{
+ "delay": 3,
+ "timeout": 20,
+ "max_retries": 10,
+ "url_path": "/another_check",
+ "expected_codes": "301"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusAccepted)
+
+ fmt.Fprintf(w, `
+{
+ "health_monitor": {
+ "admin_state_up": true,
+ "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+ "delay": 3,
+ "max_retries": 10,
+ "http_method": "GET",
+ "timeout": 20,
+ "pools": [
+ {
+ "status": "PENDING_CREATE",
+ "status_description": null,
+ "pool_id": "6e55751f-6ad4-4e53-b8d4-02e442cd21df"
+ }
+ ],
+ "type": "PING",
+ "pool_id": "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d",
+ "id": "b05e44b5-81f9-4551-b474-711a722698f7"
+ }
+}
+ `)
+ })
+
+ _, err := Update(fake.ServiceClient(), "b05e44b5-81f9-4551-b474-711a722698f7", UpdateOpts{
+ Delay: 3,
+ Timeout: 20,
+ MaxRetries: 10,
+ URLPath: "/another_check",
+ ExpectedCodes: "301",
+ }).Extract()
+
+ th.AssertNoErr(t, err)
+}
+
+func TestDelete(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lbaas/health_monitors/b05e44b5-81f9-4551-b474-711a722698f7", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "DELETE")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ res := Delete(fake.ServiceClient(), "b05e44b5-81f9-4551-b474-711a722698f7")
+ th.AssertNoErr(t, res.Err)
+}
diff --git a/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go b/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go
new file mode 100644
index 0000000..599c130
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go
@@ -0,0 +1,150 @@
+package monitors
+
+import (
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// Monitor represents a load balancer health monitor. A health monitor is used
+// to determine whether or not back-end members of the VIP's pool are usable
+// for processing a request. A pool can have several health monitors associated
+// with it. There are different types of health monitors supported:
+//
+// PING: used to ping the members using ICMP.
+// TCP: used to connect to the members using TCP.
+// HTTP: used to send an HTTP request to the member.
+// HTTPS: used to send a secure HTTP request to the member.
+//
+// When a pool has several monitors associated with it, each member of the pool
+// is monitored by all these monitors. If any monitor declares the member as
+// unhealthy, then the member status is changed to INACTIVE and the member
+// won't participate in its pool's load balancing. In other words, ALL monitors
+// must declare the member to be healthy for it to stay ACTIVE.
+type Monitor struct {
+ // The unique ID for the VIP.
+ ID string
+
+ // Owner of the VIP. Only an administrative user can specify a tenant ID
+ // other than its own.
+ TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
+
+ // The type of probe sent by the load balancer to verify the member state,
+ // which is PING, TCP, HTTP, or HTTPS.
+ Type string
+
+ // The time, in seconds, between sending probes to members.
+ Delay int
+
+ // The maximum number of seconds for a monitor to wait for a connection to be
+ // established before it times out. This value must be less than the delay value.
+ Timeout int
+
+ // Number of allowed connection failures before changing the status of the
+ // member to INACTIVE. A valid value is from 1 to 10.
+ MaxRetries int `json:"max_retries" mapstructure:"max_retries"`
+
+ // The HTTP method that the monitor uses for requests.
+ HTTPMethod string `json:"http_method" mapstructure:"http_method"`
+
+ // The HTTP path of the request sent by the monitor to test the health of a
+ // member. Must be a string beginning with a forward slash (/).
+ URLPath string `json:"url_path" mapstructure:"url_path"`
+
+ // Expected HTTP codes for a passing HTTP(S) monitor.
+ ExpectedCodes string `json:"expected_codes" mapstructure:"expected_codes"`
+
+ // The administrative state of the health monitor, which is up (true) or down (false).
+ AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
+
+ // The status of the health monitor. Indicates whether the health monitor is
+ // operational.
+ Status string
+
+ // List of pools that are associated with the health monitor.
+ Pools []map[string]interface{} `mapstructure:"pools" json:"pools"`
+}
+
+// MonitorPage is the page returned by a pager when traversing over a
+// collection of health monitors.
+type MonitorPage struct {
+ pagination.LinkedPageBase
+}
+
+// NextPageURL is invoked when a paginated collection of monitors has reached
+// the end of a page and the pager seeks to traverse over a new one. In order
+// to do this, it needs to construct the next page's URL.
+func (p MonitorPage) NextPageURL() (string, error) {
+ type resp struct {
+ Links []gophercloud.Link `mapstructure:"health_monitors_links"`
+ }
+
+ var r resp
+ err := mapstructure.Decode(p.Body, &r)
+ if err != nil {
+ return "", err
+ }
+
+ return gophercloud.ExtractNextURL(r.Links)
+}
+
+// IsEmpty checks whether a PoolPage struct is empty.
+func (p MonitorPage) IsEmpty() (bool, error) {
+ is, err := ExtractMonitors(p)
+ if err != nil {
+ return true, nil
+ }
+ return len(is) == 0, nil
+}
+
+// ExtractMonitors accepts a Page struct, specifically a MonitorPage struct,
+// and extracts the elements into a slice of Monitor structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractMonitors(page pagination.Page) ([]Monitor, error) {
+ var resp struct {
+ Monitors []Monitor `mapstructure:"health_monitors" json:"health_monitors"`
+ }
+
+ err := mapstructure.Decode(page.(MonitorPage).Body, &resp)
+
+ return resp.Monitors, err
+}
+
+type commonResult struct {
+ gophercloud.Result
+}
+
+// Extract is a function that accepts a result and extracts a monitor.
+func (r commonResult) Extract() (*Monitor, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Monitor *Monitor `json:"health_monitor" mapstructure:"health_monitor"`
+ }
+
+ err := mapstructure.Decode(r.Body, &res)
+
+ return res.Monitor, err
+}
+
+// CreateResult represents the result of a create operation.
+type CreateResult struct {
+ commonResult
+}
+
+// GetResult represents the result of a get operation.
+type GetResult struct {
+ commonResult
+}
+
+// UpdateResult represents the result of an update operation.
+type UpdateResult struct {
+ commonResult
+}
+
+// DeleteResult represents the result of a delete operation.
+type DeleteResult struct {
+ gophercloud.ErrResult
+}
diff --git a/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go b/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go
new file mode 100644
index 0000000..5446a2d
--- /dev/null
+++ b/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go
@@ -0,0 +1,16 @@
+package monitors
+
+import "github.com/rackspace/gophercloud"
+
+const (
+ rootPath = "lbaas"
+ resourcePath = "health_monitors"
+)
+
+func rootURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL(rootPath, resourcePath)
+}
+
+func resourceURL(c *gophercloud.ServiceClient, id string) string {
+ return c.ServiceURL(rootPath, resourcePath, id)
+}