Backport compute availabilityzones extension from the upstream
Change-Id: I2b824285af94669e1e57f61e4787b398e0db94d0
Related-PROD: PROD-34531
diff --git a/openstack/compute/v2/extensions/availabilityzones/doc.go b/openstack/compute/v2/extensions/availabilityzones/doc.go
new file mode 100644
index 0000000..29b554d
--- /dev/null
+++ b/openstack/compute/v2/extensions/availabilityzones/doc.go
@@ -0,0 +1,61 @@
+/*
+Package availabilityzones provides the ability to get lists and detailed
+availability zone information and to extend a server result with
+availability zone information.
+
+Example of Extend server result with Availability Zone Information:
+
+ type ServerWithAZ struct {
+ servers.Server
+ availabilityzones.ServerAvailabilityZoneExt
+ }
+
+ var allServers []ServerWithAZ
+
+ allPages, err := servers.List(client, nil).AllPages()
+ if err != nil {
+ panic("Unable to retrieve servers: %s", err)
+ }
+
+ err = servers.ExtractServersInto(allPages, &allServers)
+ if err != nil {
+ panic("Unable to extract servers: %s", err)
+ }
+
+ for _, server := range allServers {
+ fmt.Println(server.AvailabilityZone)
+ }
+
+Example of Get Availability Zone Information
+
+ allPages, err := availabilityzones.List(computeClient).AllPages()
+ if err != nil {
+ panic(err)
+ }
+
+ availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages)
+ if err != nil {
+ panic(err)
+ }
+
+ for _, zoneInfo := range availabilityZoneInfo {
+ fmt.Printf("%+v\n", zoneInfo)
+ }
+
+Example of Get Detailed Availability Zone Information
+
+ allPages, err := availabilityzones.ListDetail(computeClient).AllPages()
+ if err != nil {
+ panic(err)
+ }
+
+ availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages)
+ if err != nil {
+ panic(err)
+ }
+
+ for _, zoneInfo := range availabilityZoneInfo {
+ fmt.Printf("%+v\n", zoneInfo)
+ }
+*/
+package availabilityzones
diff --git a/openstack/compute/v2/extensions/availabilityzones/requests.go b/openstack/compute/v2/extensions/availabilityzones/requests.go
new file mode 100644
index 0000000..f9a2e86
--- /dev/null
+++ b/openstack/compute/v2/extensions/availabilityzones/requests.go
@@ -0,0 +1,20 @@
+package availabilityzones
+
+import (
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+// List will return the existing availability zones.
+func List(client *gophercloud.ServiceClient) pagination.Pager {
+ return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
+ return AvailabilityZonePage{pagination.SinglePageBase(r)}
+ })
+}
+
+// ListDetail will return the existing availability zones with detailed information.
+func ListDetail(client *gophercloud.ServiceClient) pagination.Pager {
+ return pagination.NewPager(client, listDetailURL(client), func(r pagination.PageResult) pagination.Page {
+ return AvailabilityZonePage{pagination.SinglePageBase(r)}
+ })
+}
diff --git a/openstack/compute/v2/extensions/availabilityzones/results.go b/openstack/compute/v2/extensions/availabilityzones/results.go
index 96a6a50..d48a0ea 100644
--- a/openstack/compute/v2/extensions/availabilityzones/results.go
+++ b/openstack/compute/v2/extensions/availabilityzones/results.go
@@ -1,12 +1,76 @@
package availabilityzones
-// ServerExt is an extension to the base Server object
-type ServerExt struct {
+import (
+ "encoding/json"
+ "time"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+// ServerAvailabilityZoneExt is an extension to the base Server object.
+type ServerAvailabilityZoneExt struct {
// AvailabilityZone is the availabilty zone the server is in.
AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"`
}
+// ServiceState represents the state of a service in an AvailabilityZone.
+type ServiceState struct {
+ Active bool `json:"active"`
+ Available bool `json:"available"`
+ UpdatedAt time.Time `json:"-"`
+}
+
// UnmarshalJSON to override default
-func (r *ServerExt) UnmarshalJSON(b []byte) error {
+func (r *ServiceState) UnmarshalJSON(b []byte) error {
+ type tmp ServiceState
+ var s struct {
+ tmp
+ UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
+ }
+ err := json.Unmarshal(b, &s)
+ if err != nil {
+ return err
+ }
+ *r = ServiceState(s.tmp)
+
+ r.UpdatedAt = time.Time(s.UpdatedAt)
+
return nil
}
+
+// Services is a map of services contained in an AvailabilityZone.
+type Services map[string]ServiceState
+
+// Hosts is map of hosts/nodes contained in an AvailabilityZone.
+// Each host can have multiple services.
+type Hosts map[string]Services
+
+// ZoneState represents the current state of the availability zone.
+type ZoneState struct {
+ // Returns true if the availability zone is available
+ Available bool `json:"available"`
+}
+
+// AvailabilityZone contains all the information associated with an OpenStack
+// AvailabilityZone.
+type AvailabilityZone struct {
+ Hosts Hosts `json:"hosts"`
+ // The availability zone name
+ ZoneName string `json:"zoneName"`
+ ZoneState ZoneState `json:"zoneState"`
+}
+
+type AvailabilityZonePage struct {
+ pagination.SinglePageBase
+}
+
+// ExtractAvailabilityZones returns a slice of AvailabilityZones contained in a
+// single page of results.
+func ExtractAvailabilityZones(r pagination.Page) ([]AvailabilityZone, error) {
+ var s struct {
+ AvailabilityZoneInfo []AvailabilityZone `json:"availabilityZoneInfo"`
+ }
+ err := (r.(AvailabilityZonePage)).ExtractInto(&s)
+ return s.AvailabilityZoneInfo, err
+}
diff --git a/openstack/compute/v2/extensions/availabilityzones/testing/doc.go b/openstack/compute/v2/extensions/availabilityzones/testing/doc.go
new file mode 100644
index 0000000..a4408d7
--- /dev/null
+++ b/openstack/compute/v2/extensions/availabilityzones/testing/doc.go
@@ -0,0 +1,2 @@
+// availabilityzones unittests
+package testing
diff --git a/openstack/compute/v2/extensions/availabilityzones/testing/fixtures.go b/openstack/compute/v2/extensions/availabilityzones/testing/fixtures.go
new file mode 100644
index 0000000..9cc6d46
--- /dev/null
+++ b/openstack/compute/v2/extensions/availabilityzones/testing/fixtures.go
@@ -0,0 +1,197 @@
+package testing
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ az "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones"
+ th "github.com/gophercloud/gophercloud/testhelper"
+ "github.com/gophercloud/gophercloud/testhelper/client"
+)
+
+const GetOutput = `
+{
+ "availabilityZoneInfo": [
+ {
+ "hosts": null,
+ "zoneName": "nova",
+ "zoneState": {
+ "available": true
+ }
+ }
+ ]
+}
+`
+
+const GetDetailOutput = `
+{
+ "availabilityZoneInfo": [
+ {
+ "hosts": {
+ "localhost": {
+ "nova-cert": {
+ "active": true,
+ "available": false,
+ "updated_at": "2017-10-14T17:03:39.000000"
+ },
+ "nova-conductor": {
+ "active": true,
+ "available": false,
+ "updated_at": "2017-10-14T17:04:09.000000"
+ },
+ "nova-consoleauth": {
+ "active": true,
+ "available": false,
+ "updated_at": "2017-10-14T17:04:18.000000"
+ },
+ "nova-scheduler": {
+ "active": true,
+ "available": false,
+ "updated_at": "2017-10-14T17:04:30.000000"
+ }
+ },
+ "openstack-acc-tests.novalocal": {
+ "nova-cert": {
+ "active": true,
+ "available": true,
+ "updated_at": "2018-01-04T04:11:19.000000"
+ },
+ "nova-conductor": {
+ "active": true,
+ "available": true,
+ "updated_at": "2018-01-04T04:11:22.000000"
+ },
+ "nova-consoleauth": {
+ "active": true,
+ "available": true,
+ "updated_at": "2018-01-04T04:11:20.000000"
+ },
+ "nova-scheduler": {
+ "active": true,
+ "available": true,
+ "updated_at": "2018-01-04T04:11:23.000000"
+ }
+ }
+ },
+ "zoneName": "internal",
+ "zoneState": {
+ "available": true
+ }
+ },
+ {
+ "hosts": {
+ "openstack-acc-tests.novalocal": {
+ "nova-compute": {
+ "active": true,
+ "available": true,
+ "updated_at": "2018-01-04T04:11:23.000000"
+ }
+ }
+ },
+ "zoneName": "nova",
+ "zoneState": {
+ "available": true
+ }
+ }
+ ]
+}`
+
+var AZResult = []az.AvailabilityZone{
+ {
+ Hosts: nil,
+ ZoneName: "nova",
+ ZoneState: az.ZoneState{Available: true},
+ },
+}
+
+var AZDetailResult = []az.AvailabilityZone{
+ {
+ Hosts: az.Hosts{
+ "localhost": az.Services{
+ "nova-cert": az.ServiceState{
+ Active: true,
+ Available: false,
+ UpdatedAt: time.Date(2017, 10, 14, 17, 3, 39, 0, time.UTC),
+ },
+ "nova-conductor": az.ServiceState{
+ Active: true,
+ Available: false,
+ UpdatedAt: time.Date(2017, 10, 14, 17, 4, 9, 0, time.UTC),
+ },
+ "nova-consoleauth": az.ServiceState{
+ Active: true,
+ Available: false,
+ UpdatedAt: time.Date(2017, 10, 14, 17, 4, 18, 0, time.UTC),
+ },
+ "nova-scheduler": az.ServiceState{
+ Active: true,
+ Available: false,
+ UpdatedAt: time.Date(2017, 10, 14, 17, 4, 30, 0, time.UTC),
+ },
+ },
+ "openstack-acc-tests.novalocal": az.Services{
+ "nova-cert": az.ServiceState{
+ Active: true,
+ Available: true,
+ UpdatedAt: time.Date(2018, 1, 4, 4, 11, 19, 0, time.UTC),
+ },
+ "nova-conductor": az.ServiceState{
+ Active: true,
+ Available: true,
+ UpdatedAt: time.Date(2018, 1, 4, 4, 11, 22, 0, time.UTC),
+ },
+ "nova-consoleauth": az.ServiceState{
+ Active: true,
+ Available: true,
+ UpdatedAt: time.Date(2018, 1, 4, 4, 11, 20, 0, time.UTC),
+ },
+ "nova-scheduler": az.ServiceState{
+ Active: true,
+ Available: true,
+ UpdatedAt: time.Date(2018, 1, 4, 4, 11, 23, 0, time.UTC),
+ },
+ },
+ },
+ ZoneName: "internal",
+ ZoneState: az.ZoneState{Available: true},
+ },
+ {
+ Hosts: az.Hosts{
+ "openstack-acc-tests.novalocal": az.Services{
+ "nova-compute": az.ServiceState{
+ Active: true,
+ Available: true,
+ UpdatedAt: time.Date(2018, 1, 4, 4, 11, 23, 0, time.UTC),
+ },
+ },
+ },
+ ZoneName: "nova",
+ ZoneState: az.ZoneState{Available: true},
+ },
+}
+
+// HandleGetSuccessfully configures the test server to respond to a Get request
+// for availability zone information.
+func HandleGetSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, GetOutput)
+ })
+}
+
+// HandleGetDetailSuccessfully configures the test server to respond to a Get request
+// for detailed availability zone information.
+func HandleGetDetailSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/os-availability-zone/detail", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, GetDetailOutput)
+ })
+}
diff --git a/openstack/compute/v2/extensions/availabilityzones/testing/requests_test.go b/openstack/compute/v2/extensions/availabilityzones/testing/requests_test.go
new file mode 100644
index 0000000..8996d36
--- /dev/null
+++ b/openstack/compute/v2/extensions/availabilityzones/testing/requests_test.go
@@ -0,0 +1,41 @@
+package testing
+
+import (
+ "testing"
+
+ az "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones"
+ th "github.com/gophercloud/gophercloud/testhelper"
+ "github.com/gophercloud/gophercloud/testhelper/client"
+)
+
+// Verifies that availability zones can be listed correctly
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleGetSuccessfully(t)
+
+ allPages, err := az.List(client.ServiceClient()).AllPages()
+ th.AssertNoErr(t, err)
+
+ actual, err := az.ExtractAvailabilityZones(allPages)
+ th.AssertNoErr(t, err)
+
+ th.CheckDeepEquals(t, AZResult, actual)
+}
+
+// Verifies that detailed availability zones can be listed correctly
+func TestListDetail(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleGetDetailSuccessfully(t)
+
+ allPages, err := az.ListDetail(client.ServiceClient()).AllPages()
+ th.AssertNoErr(t, err)
+
+ actual, err := az.ExtractAvailabilityZones(allPages)
+ th.AssertNoErr(t, err)
+
+ th.CheckDeepEquals(t, AZDetailResult, actual)
+}
diff --git a/openstack/compute/v2/extensions/availabilityzones/urls.go b/openstack/compute/v2/extensions/availabilityzones/urls.go
new file mode 100644
index 0000000..9d99ec7
--- /dev/null
+++ b/openstack/compute/v2/extensions/availabilityzones/urls.go
@@ -0,0 +1,11 @@
+package availabilityzones
+
+import "github.com/gophercloud/gophercloud"
+
+func listURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("os-availability-zone")
+}
+
+func listDetailURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("os-availability-zone", "detail")
+}