DNS Zones: List / Get (#271)

* Add Zone List / Get Support

* Addressing code review comments

* Adding v2 to DNS client

* List / Get unit tests plus updates to results for unit tests to work.

* DNS v2 List acceptance tests

* add failing unit test for dns v2 allpages

* Changing acceptance test for DNS v2 to use AllPages

* Adding empty zones.go file for package requirements

* Change ttl back to int

* DNS v2 Zones ListOpts
diff --git a/openstack/dns/v2/zones/results.go b/openstack/dns/v2/zones/results.go
new file mode 100644
index 0000000..4693b09
--- /dev/null
+++ b/openstack/dns/v2/zones/results.go
@@ -0,0 +1,144 @@
+package zones
+
+import (
+	"encoding/json"
+	"strconv"
+	"time"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+type commonResult struct {
+	gophercloud.Result
+}
+
+// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete Zone.
+// An error is returned if the original call or the extraction failed.
+func (r commonResult) Extract() (*Zone, error) {
+	var s *Zone
+	err := r.ExtractInto(&s)
+	return s, err
+}
+
+// GetResult is the deferred result of a Get call.
+type GetResult struct {
+	commonResult
+}
+
+// ZonePage is a single page of Zone results.
+type ZonePage struct {
+	pagination.LinkedPageBase
+}
+
+// IsEmpty returns true if the page contains no results.
+func (r ZonePage) IsEmpty() (bool, error) {
+	s, err := ExtractZones(r)
+	return len(s) == 0, err
+}
+
+// ExtractZones extracts a slice of Services from a Collection acquired from List.
+func ExtractZones(r pagination.Page) ([]Zone, error) {
+	var s struct {
+		Zones []Zone `json:"zones"`
+	}
+	err := (r.(ZonePage)).ExtractInto(&s)
+	return s.Zones, err
+}
+
+// Zone represents a DNS zone.
+type Zone struct {
+	// ID uniquely identifies this zone amongst all other zones, including those not accessible to the current tenant.
+	ID string `json:"id"`
+
+	// PoolID is the ID for the pool hosting this zone.
+	PoolID string `json:"pool_id"`
+
+	// ProjectID identifies the project/tenant owning this resource.
+	ProjectID string `json:"project_id"`
+
+	// Name is the DNS Name for the zone.
+	Name string `json:"name"`
+
+	// Email for the zone. Used in SOA records for the zone.
+	Email string `json:"email"`
+
+	// Description for this zone.
+	Description string `json:"description"`
+
+	// TTL is the Time to Live for the zone.
+	TTL int `json:"ttl"`
+
+	// Serial is the current serial number for the zone.
+	Serial int `json:"-"`
+
+	// Status is the status of the resource.
+	Status string `json:"status"`
+
+	// Action is the current action in progress on the resource.
+	Action string `json:"action"`
+
+	// Version of the resource.
+	Version int `json:"version"`
+
+	// Attributes for the zone.
+	Attributes map[string]string `json:"attributes"`
+
+	// Type of zone. Primary is controlled by Designate.
+	// Secondary zones are slaved from another DNS Server.
+	// Defaults to Primary.
+	Type string `json:"type"`
+
+	// Masters is the servers for slave servers to get DNS information from.
+	Masters []string `json:"masters"`
+
+	// CreatedAt is the date when the zone was created.
+	CreatedAt time.Time `json:"-"`
+
+	// UpdatedAt is the date when the last change was made to the zone.
+	UpdatedAt time.Time `json:"-"`
+
+	// TransferredAt is the last time an update was retrieved from the master servers.
+	TransferredAt time.Time `json:"-"`
+
+	// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
+	Links map[string]interface{} `json:"links"`
+}
+
+func (r *Zone) UnmarshalJSON(b []byte) error {
+	type tmp Zone
+	var s struct {
+		tmp
+		CreatedAt     gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
+		UpdatedAt     gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
+		TransferredAt gophercloud.JSONRFC3339MilliNoZ `json:"transferred_at"`
+		Serial        interface{}                     `json:"serial"`
+	}
+	err := json.Unmarshal(b, &s)
+	if err != nil {
+		return err
+	}
+	*r = Zone(s.tmp)
+
+	r.CreatedAt = time.Time(s.CreatedAt)
+	r.UpdatedAt = time.Time(s.UpdatedAt)
+	r.TransferredAt = time.Time(s.TransferredAt)
+
+	switch t := s.Serial.(type) {
+	case float64:
+		r.Serial = int(t)
+	case string:
+		switch t {
+		case "":
+			r.Serial = 0
+		default:
+			serial, err := strconv.ParseFloat(t, 64)
+			if err != nil {
+				return err
+			}
+			r.Serial = int(serial)
+		}
+	}
+
+	return err
+}