Compute v2: Extended Availability Zone Status (#282)

* Compute v2: Extended Availability Zone Status API

* Compute v2: Extended Availability Zone Status unit tests

* Compute v2: Extended Availability Zone Status acceptance tests
diff --git a/acceptance/openstack/compute/v2/servers_test.go b/acceptance/openstack/compute/v2/servers_test.go
index 22b6580..f43c94d 100644
--- a/acceptance/openstack/compute/v2/servers_test.go
+++ b/acceptance/openstack/compute/v2/servers_test.go
@@ -9,6 +9,7 @@
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 	th "github.com/gophercloud/gophercloud/testhelper"
 )
@@ -88,6 +89,30 @@
 	}
 }
 
+func TestServersCreateDestroyWithExtensions(t *testing.T) {
+	var extendedServer struct {
+		servers.Server
+		availabilityzones.ServerExt
+	}
+
+	client, err := clients.NewComputeV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	server, err := CreateServer(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create server: %v", err)
+	}
+	defer DeleteServer(t, client, server)
+
+	err = servers.Get(client, server.ID).ExtractInto(&extendedServer)
+	if err != nil {
+		t.Errorf("Unable to retrieve server: %v", err)
+	}
+	tools.PrintResource(t, extendedServer)
+}
+
 func TestServersWithoutImageRef(t *testing.T) {
 	client, err := clients.NewComputeV2Client()
 	if err != nil {
diff --git a/openstack/compute/v2/extensions/availabilityzones/results.go b/openstack/compute/v2/extensions/availabilityzones/results.go
new file mode 100644
index 0000000..96a6a50
--- /dev/null
+++ b/openstack/compute/v2/extensions/availabilityzones/results.go
@@ -0,0 +1,12 @@
+package availabilityzones
+
+// ServerExt is an extension to the base Server object
+type ServerExt struct {
+	// AvailabilityZone is the availabilty zone the server is in.
+	AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"`
+}
+
+// UnmarshalJSON to override default
+func (r *ServerExt) UnmarshalJSON(b []byte) error {
+	return nil
+}
diff --git a/openstack/compute/v2/servers/results.go b/openstack/compute/v2/servers/results.go
index c121a6b..1ae1e91 100644
--- a/openstack/compute/v2/servers/results.go
+++ b/openstack/compute/v2/servers/results.go
@@ -19,11 +19,17 @@
 
 // Extract interprets any serverResult as a Server, if possible.
 func (r serverResult) Extract() (*Server, error) {
-	var s struct {
-		Server *Server `json:"server"`
-	}
+	var s Server
 	err := r.ExtractInto(&s)
-	return s.Server, err
+	return &s, err
+}
+
+func (r serverResult) ExtractInto(v interface{}) error {
+	return r.Result.ExtractIntoStructPtr(v, "server")
+}
+
+func ExtractServersInto(r pagination.Page, v interface{}) error {
+	return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers")
 }
 
 // CreateResult temporarily contains the response from a Create call.
@@ -221,11 +227,9 @@
 
 // ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
 func ExtractServers(r pagination.Page) ([]Server, error) {
-	var s struct {
-		Servers []Server `json:"servers"`
-	}
-	err := (r.(ServerPage)).ExtractInto(&s)
-	return s.Servers, err
+	var s []Server
+	err := ExtractServersInto(r, &s)
+	return s, err
 }
 
 // MetadataResult contains the result of a call for (potentially) multiple key-value pairs.
diff --git a/openstack/compute/v2/servers/testing/requests_test.go b/openstack/compute/v2/servers/testing/requests_test.go
index 15d6eb5..05712f7 100644
--- a/openstack/compute/v2/servers/testing/requests_test.go
+++ b/openstack/compute/v2/servers/testing/requests_test.go
@@ -6,6 +6,7 @@
 	"net/http"
 	"testing"
 
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 	"github.com/gophercloud/gophercloud/pagination"
 	th "github.com/gophercloud/gophercloud/testhelper"
@@ -56,6 +57,26 @@
 	th.CheckDeepEquals(t, ServerDerp, actual[1])
 }
 
+func TestListAllServersWithExtensions(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleServerListSuccessfully(t)
+
+	type ServerWithExt struct {
+		servers.Server
+		availabilityzones.ServerExt
+	}
+
+	allPages, err := servers.List(client.ServiceClient(), servers.ListOpts{}).AllPages()
+	th.AssertNoErr(t, err)
+
+	var actual []ServerWithExt
+	err = servers.ExtractServersInto(allPages, &actual)
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, 3, len(actual))
+	th.AssertEquals(t, "nova", actual[0].AvailabilityZone)
+}
+
 func TestCreateServer(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -189,6 +210,26 @@
 	th.CheckDeepEquals(t, ServerDerp, *actual)
 }
 
+func TestGetServerWithExtensions(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleServerGetSuccessfully(t)
+
+	var s struct {
+		servers.Server
+		availabilityzones.ServerExt
+	}
+
+	err := servers.Get(client.ServiceClient(), "1234asdf").ExtractInto(&s)
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, "nova", s.AvailabilityZone)
+
+	err = servers.Get(client.ServiceClient(), "1234asdf").ExtractInto(s)
+	if err == nil {
+		t.Errorf("Expected error when providing non-pointer struct")
+	}
+}
+
 func TestUpdateServer(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()