Adding health monitors :mag_right:
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/requests.go b/openstack/networking/v2/extensions/lbaas/monitors/requests.go
index 99fa30a..dcdb0cc 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/requests.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/requests.go
@@ -1 +1,320 @@
package monitors
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/openstack/utils"
+ "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 floating IP attributes you want to see returned. SortKey allows you to
+// sort by a particular network attribute. SortDir sets the direction, and is
+// either `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+ ID string
+ TenantID string
+ Type string
+ Delay int
+ Timeout int
+ MaxRetries int
+ HttpMethod string
+ UrlPath string
+ ExpectedCodes string
+ AdminStateUp *bool
+ Status string
+ Limit int
+ Marker string
+ SortKey string
+ SortDir string
+}
+
+// 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 := make(map[string]string)
+ if opts.ID != "" {
+ q["id"] = opts.ID
+ }
+ if opts.TenantID != "" {
+ q["tenant_id"] = opts.TenantID
+ }
+ if opts.Type != "" {
+ q["type"] = opts.Type
+ }
+ if opts.Delay != 0 {
+ q["delay"] = strconv.Itoa(opts.Delay)
+ }
+ if opts.Timeout != 0 {
+ q["timeout"] = strconv.Itoa(opts.Timeout)
+ }
+ if opts.MaxRetries != 0 {
+ q["max_retries"] = strconv.Itoa(opts.MaxRetries)
+ }
+ if opts.HttpMethod != "" {
+ q["http_method"] = opts.HttpMethod
+ }
+ if opts.UrlPath != "" {
+ q["url_path"] = opts.UrlPath
+ }
+ if opts.AdminStateUp != nil {
+ q["admin_state_up"] = strconv.FormatBool(*opts.AdminStateUp)
+ }
+ if opts.Status != "" {
+ q["status"] = opts.Status
+ }
+ if opts.ExpectedCodes != "" {
+ q["expected_codes"] = opts.ExpectedCodes
+ }
+ if opts.Marker != "" {
+ q["marker"] = opts.Marker
+ }
+ if opts.Limit != 0 {
+ q["limit"] = strconv.Itoa(opts.Limit)
+ }
+ if opts.SortKey != "" {
+ q["sort_key"] = opts.SortKey
+ }
+ if opts.SortDir != "" {
+ q["sort_dir"] = opts.SortDir
+ }
+
+ u := rootURL(c) + utils.BuildQuery(q)
+
+ return pagination.NewPager(c, u, func(r pagination.LastHTTPResponse) pagination.Page {
+ return MonitorPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
+ })
+}
+
+const (
+ TypePING = "PING"
+ TypeTCP = "TCP"
+ TypeHTTP = "HTTP"
+ TypeHTTPS = "HTTPS"
+)
+
+var (
+ 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")
+)
+
+// CreateOpts contains all the values needed to create a new health monitor.
+type CreateOpts struct {
+ // Required for admins. Indicates the owner of the VIP.
+ 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"}
+//
+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.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 res.Err != nil {
+ return res
+ }
+
+ type monitor struct {
+ Type string `json:"type"`
+ 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:"healthmonitor"`
+ }
+
+ reqBody := request{Monitor: monitor{
+ Type: opts.Type,
+ 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 = perigee.Request("POST", rootURL(c), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res.Resp,
+ OkCodes: []int{201},
+ })
+
+ return res
+}
+
+// Get retrieves a particular health monitor based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) GetResult {
+ var res GetResult
+ _, err := perigee.Request("GET", resourceURL(c, id), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ Results: &res.Resp,
+ OkCodes: []int{200},
+ })
+ res.Err = err
+ return res
+}
+
+// UpdateOpts contains all the values needed to update an existing virtual IP.
+// 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 {
+ 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:"healthmonitor"`
+ }
+
+ 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,
+ }}
+
+ var res UpdateResult
+
+ _, res.Err = perigee.Request("PUT", resourceURL(c, id), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res.Resp,
+ OkCodes: []int{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
+ _, err := perigee.Request("DELETE", resourceURL(c, id), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ OkCodes: []int{204},
+ })
+ res.Err = err
+ return res
+}
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go b/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
index 99fa30a..3124729 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
@@ -1 +1,286 @@
package monitors
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const tokenID = "123"
+
+func serviceClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{
+ Provider: &gophercloud.ProviderClient{TokenID: tokenID},
+ Endpoint: th.Endpoint(),
+ }
+}
+
+func TestURLs(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ th.AssertEquals(t, th.Endpoint()+"v2.0/lb/healthmonitors", rootURL(serviceClient()))
+}
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lb/healthmonitors", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", tokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "healthmonitors":[
+ {
+ "admin_state_up":true,
+ "tenant_id":"83657cfcdfe44cd5920adaf26c48ceea",
+ "delay":10,
+ "max_retries":1,
+ "timeout":1,
+ "type":"PING",
+ "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",
+ "id":"5d4b5228-33b0-4e60-b225-9b727c1a20e7"
+ }
+ ]
+}
+ `)
+ })
+
+ count := 0
+
+ List(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{
+ Monitor{
+ AdminStateUp: true,
+ TenantID: "83657cfcdfe44cd5920adaf26c48ceea",
+ Delay: 10,
+ MaxRetries: 1,
+ Timeout: 1,
+ Type: "PING",
+ ID: "466c8345-28d8-4f84-a246-e04380b0461d",
+ },
+ Monitor{
+ AdminStateUp: true,
+ TenantID: "83657cfcdfe44cd5920adaf26c48ceea",
+ Delay: 5,
+ ExpectedCodes: "200",
+ MaxRetries: 2,
+ Timeout: 2,
+ URLPath: "/",
+ Type: "HTTP",
+ HTTPMethod: "GET",
+ ID: "5d4b5228-33b0-4e60-b225-9b727c1a20e7",
+ },
+ }
+
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lb/healthmonitors", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", tokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "healthmonitor":{
+ "type":"HTTP",
+ "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, `
+{
+ "healthmonitor":{
+ "id":"f3eeab00-8367-4524-b662-55e64d4cacb5",
+ "tenant_id":"453105b9-1754-413f-aab1-55f1af620750",
+ "type":"HTTP",
+ "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(serviceClient(), CreateOpts{
+ Type: "HTTP",
+ TenantID: "453105b9-1754-413f-aab1-55f1af620750",
+ Delay: 20,
+ Timeout: 10,
+ MaxRetries: 5,
+ URLPath: "/check",
+ ExpectedCodes: "200-299",
+ }).Extract()
+
+ th.AssertNoErr(t, err)
+}
+
+func TestGet(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/lb/healthmonitors/f3eeab00-8367-4524-b662-55e64d4cacb5", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", tokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "healthmonitor":{
+ "id":"f3eeab00-8367-4524-b662-55e64d4cacb5",
+ "tenant_id":"453105b9-1754-413f-aab1-55f1af620750",
+ "type":"HTTP",
+ "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(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, 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/lb/healthmonitors/b05e44b5-81f9-4551-b474-711a722698f7", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", tokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "healthmonitor":{
+ "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, `
+{
+ "healthmonitor": {
+ "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",
+ "id": "b05e44b5-81f9-4551-b474-711a722698f7"
+ }
+}
+ `)
+ })
+
+ _, err := Update(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/lb/healthmonitors/b05e44b5-81f9-4551-b474-711a722698f7", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "DELETE")
+ th.TestHeader(t, r, "X-Auth-Token", tokenID)
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ res := Delete(serviceClient(), "b05e44b5-81f9-4551-b474-711a722698f7")
+ th.AssertNoErr(t, res.Err)
+}
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/results.go b/openstack/networking/v2/extensions/lbaas/monitors/results.go
index 99fa30a..44c1fff 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/results.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/results.go
@@ -1 +1,167 @@
package monitors
+
+import (
+ "fmt"
+
+ "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
+}
+
+// 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 link struct {
+ Href string `mapstructure:"href"`
+ Rel string `mapstructure:"rel"`
+ }
+ type resp struct {
+ Links []link `mapstructure:"healthmonitors_links"`
+ }
+
+ var r resp
+ err := mapstructure.Decode(p.Body, &r)
+ if err != nil {
+ return "", err
+ }
+
+ var url string
+ for _, l := range r.Links {
+ if l.Rel == "next" {
+ url = l.Href
+ }
+ }
+ if url == "" {
+ return "", nil
+ }
+
+ return url, nil
+}
+
+// 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
+}
+
+// ExtractPools accepts a Page struct, specifically a RouterPage struct,
+// and extracts the elements into a slice of Router 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:"healthmonitors" json:"healthmonitors"`
+ }
+
+ err := mapstructure.Decode(page.(MonitorPage).Body, &resp)
+ if err != nil {
+ return nil, err
+ }
+
+ return resp.Monitors, nil
+}
+
+type commonResult struct {
+ gophercloud.CommonResult
+}
+
+// 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:"healthmonitor" mapstructure:"healthmonitor"`
+ }
+
+ err := mapstructure.Decode(r.Resp, &res)
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding Neutron monitor: %v", err)
+ }
+
+ return res.Monitor, nil
+}
+
+// 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 commonResult
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/urls.go b/openstack/networking/v2/extensions/lbaas/monitors/urls.go
index 99fa30a..b6eeb24 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/urls.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/urls.go
@@ -1 +1,17 @@
package monitors
+
+import "github.com/rackspace/gophercloud"
+
+const (
+ version = "v2.0"
+ rootPath = "lb"
+ resourcePath = "healthmonitors"
+)
+
+func rootURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL(version, rootPath, resourcePath)
+}
+
+func resourceURL(c *gophercloud.ServiceClient, id string) string {
+ return c.ServiceURL(version, rootPath, resourcePath, id)
+}
diff --git a/openstack/networking/v2/extensions/lbaas/pools/results.go b/openstack/networking/v2/extensions/lbaas/pools/results.go
index 6cec6df..76f8f5d 100644
--- a/openstack/networking/v2/extensions/lbaas/pools/results.go
+++ b/openstack/networking/v2/extensions/lbaas/pools/results.go
@@ -53,6 +53,9 @@
// The ID of the virtual IP associated with this pool
VIPID string `json:"vip_id" mapstructure:"vip_id"`
+
+ // The provider
+ Provider string
}
// PoolPage is the page returned by a pager when traversing over a