Merge pull request #13 from carolynvs/fix-v3-authscope

Fix v3 auth with project scope
diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md
index 4cc84f6..18f6dc4 100644
--- a/STYLEGUIDE.md
+++ b/STYLEGUIDE.md
@@ -11,7 +11,9 @@
   are not good enough. The link(s) should be to a non-`master` branch. For example,
   a pull request implementing the creation of a Neutron v2 subnet might put the
   following link in the description:
+
   https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
+
   From that link, a reviewer (or user) can verify the fields in the request/response
   objects in the PR.
 
@@ -37,3 +39,30 @@
   own infrastructure and see an example of usage.
 
 - If in doubt, ask in-line on the PR.
+
+### File Structure
+
+- The following should be used in most cases:
+
+  - `requests.go`: contains all the functions that make HTTP requests and the
+    types associated with the HTTP request (parameters for URL, body, etc)
+  - `results.go`: contains all the response objects and their methods
+  - `urls.go`: contains the endpoints to which the requests are made
+
+### Naming
+
+- For methods on a type in `response.go`, the receiver should be named `r` and the
+  variable into which it will be unmarshalled `s`.
+
+- Functions in `requests.go`, with the exception of functions that return a
+  `pagination.Pager`, should be named returns of the name `r`.
+
+- Functions in `requests.go` that accept request bodies should accept as their
+  last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
+  This `interface` should have at the least a method named `To<Resource><Action>Map`
+  (eg `ToPortCreateMap`).
+
+- Functions in `requests.go` that accept query strings should accept as their
+  last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
+  This `interface` should have at the least a method named `To<Resource><Action>Query`
+  (eg `ToServerListQuery`).
diff --git a/openstack/compute/v2/flavors/results.go b/openstack/compute/v2/flavors/results.go
index 0edc7d2..a49de0d 100644
--- a/openstack/compute/v2/flavors/results.go
+++ b/openstack/compute/v2/flavors/results.go
@@ -1,15 +1,13 @@
 package flavors
 
 import (
-	"errors"
+	"encoding/json"
+	"strconv"
 
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
 
-// ErrCannotInterpret is returned by an Extract call if the response body doesn't have the expected structure.
-var ErrCannotInterpet = errors.New("Unable to interpret a response body.")
-
 // GetResult temporarily holds the response from a Get call.
 type GetResult struct {
 	gophercloud.Result
@@ -29,24 +27,60 @@
 	// The Id field contains the flavor's unique identifier.
 	// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
 	ID string `json:"id"`
-
 	// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
 	Disk int `json:"disk"`
 	RAM  int `json:"ram"`
-
 	// The Name field provides a human-readable moniker for the flavor.
-	Name string `json:"name"`
-
+	Name       string  `json:"name"`
 	RxTxFactor float64 `json:"rxtx_factor"`
-
 	// Swap indicates how much space is reserved for swap.
 	// If not provided, this field will be set to 0.
 	Swap int `json:"swap"`
-
 	// VCPUs indicates how many (virtual) CPUs are available for this flavor.
 	VCPUs int `json:"vcpus"`
 }
 
+func (f *Flavor) UnmarshalJSON(b []byte) error {
+	var flavor struct {
+		ID         string      `json:"id"`
+		Disk       int         `json:"disk"`
+		RAM        int         `json:"ram"`
+		Name       string      `json:"name"`
+		RxTxFactor float64     `json:"rxtx_factor"`
+		Swap       interface{} `json:"swap"`
+		VCPUs      int         `json:"vcpus"`
+	}
+	err := json.Unmarshal(b, &flavor)
+	if err != nil {
+		return err
+	}
+
+	f.ID = flavor.ID
+	f.Disk = flavor.Disk
+	f.RAM = flavor.RAM
+	f.Name = flavor.Name
+	f.RxTxFactor = flavor.RxTxFactor
+	f.VCPUs = flavor.VCPUs
+
+	switch t := flavor.Swap.(type) {
+	case float64:
+		f.Swap = int(t)
+	case string:
+		switch t {
+		case "":
+			f.Swap = 0
+		default:
+			swap, err := strconv.ParseFloat(t, 64)
+			if err != nil {
+				return err
+			}
+			f.Swap = int(swap)
+		}
+	}
+
+	return nil
+}
+
 // FlavorPage contains a single page of the response from a List call.
 type FlavorPage struct {
 	pagination.LinkedPageBase
diff --git a/openstack/compute/v2/flavors/testing/requests_test.go b/openstack/compute/v2/flavors/testing/requests_test.go
index e86512a..1b96933 100644
--- a/openstack/compute/v2/flavors/testing/requests_test.go
+++ b/openstack/compute/v2/flavors/testing/requests_test.go
@@ -35,14 +35,16 @@
 								"name": "m1.tiny",
 								"disk": 1,
 								"ram": 512,
-								"vcpus": 1
+								"vcpus": 1,
+								"swap":""
 							},
 							{
 								"id": "2",
 								"name": "m2.small",
 								"disk": 10,
 								"ram": 1024,
-								"vcpus": 2
+								"vcpus": 2,
+								"swap": 1000
 							}
 						],
 						"flavors_links": [
@@ -70,8 +72,8 @@
 		}
 
 		expected := []flavors.Flavor{
-			{ID: "1", Name: "m1.tiny", Disk: 1, RAM: 512, VCPUs: 1},
-			{ID: "2", Name: "m2.small", Disk: 10, RAM: 1024, VCPUs: 2},
+			{ID: "1", Name: "m1.tiny", Disk: 1, RAM: 512, VCPUs: 1, Swap: 0},
+			{ID: "2", Name: "m2.small", Disk: 10, RAM: 1024, VCPUs: 2, Swap: 1000},
 		}
 
 		if !reflect.DeepEqual(expected, actual) {
@@ -105,7 +107,8 @@
 					"disk": 1,
 					"ram": 512,
 					"vcpus": 1,
-					"rxtx_factor": 1
+					"rxtx_factor": 1,
+					"swap": ""
 				}
 			}
 		`)
@@ -123,6 +126,7 @@
 		RAM:        512,
 		VCPUs:      1,
 		RxTxFactor: 1,
+		Swap:       0,
 	}
 	if !reflect.DeepEqual(expected, actual) {
 		t.Errorf("Expected %#v, but was %#v", expected, actual)
diff --git a/openstack/compute/v2/servers/results.go b/openstack/compute/v2/servers/results.go
index 023d0dd..7659c75 100644
--- a/openstack/compute/v2/servers/results.go
+++ b/openstack/compute/v2/servers/results.go
@@ -3,13 +3,13 @@
 import (
 	"crypto/rsa"
 	"encoding/base64"
+	"encoding/json"
 	"fmt"
 	"net/url"
 	"path"
 
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
-	"github.com/mitchellh/mapstructure"
 )
 
 type serverResult struct {
@@ -75,20 +75,14 @@
 // If privateKey == nil the encrypted password is returned and can be decrypted with:
 //   echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
 func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
-
-	if r.Err != nil {
-		return "", r.Err
+	var s struct {
+		Password string `json:"password"`
 	}
-
-	var response struct {
-		Password string `mapstructure:"password"`
+	err := r.ExtractInto(&s)
+	if err == nil && privateKey != nil && s.Password != "" {
+		return decryptPassword(s.Password, privateKey)
 	}
-
-	err := mapstructure.Decode(r.Body, &response)
-	if err == nil && privateKey != nil && response.Password != "" {
-		return decryptPassword(response.Password, privateKey)
-	}
-	return response.Password, err
+	return s.Password, err
 }
 
 func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
@@ -136,58 +130,68 @@
 type Server struct {
 	// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
 	ID string `json:"id"`
-
 	// TenantID identifies the tenant owning this server resource.
 	TenantID string `json:"tenant_id"`
-
 	// UserID uniquely identifies the user account owning the tenant.
 	UserID string `json:"user_id"`
-
 	// Name contains the human-readable name for the server.
 	Name string `json:"name"`
-
 	// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
 	Updated string
 	Created string
-
-	HostID string
-
+	HostID  string
 	// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
 	Status string
-
 	// Progress ranges from 0..100.
 	// A request made against the server completes only once Progress reaches 100.
 	Progress int
-
 	// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
 	AccessIPv4, AccessIPv6 string
-
 	// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
 	Image map[string]interface{}
-
 	// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
 	Flavor map[string]interface{}
-
 	// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
 	Addresses map[string]interface{}
-
 	// Metadata includes a list of all user-specified key-value pairs attached to the server.
 	Metadata map[string]interface{}
-
 	// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
 	Links []interface{}
-
 	// KeyName indicates which public key was injected into the server on launch.
 	KeyName string `json:"key_name"`
-
 	// AdminPass will generally be empty ("").  However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
 	// Note that this is the ONLY time this field will be valid.
 	AdminPass string `json:"adminPass"`
-
 	// SecurityGroups includes the security groups that this instance has applied to it
 	SecurityGroups []map[string]interface{} `json:"security_groups"`
 }
 
+func (s *Server) UnmarshalJSON(b []byte) error {
+	type tmp Server
+	var server *struct {
+		tmp
+		Image interface{}
+	}
+	err := json.Unmarshal(b, &server)
+	if err != nil {
+		return err
+	}
+
+	*s = Server(server.tmp)
+
+	switch t := server.Image.(type) {
+	case map[string]interface{}:
+		s.Image = t
+	case string:
+		switch t {
+		case "":
+			s.Image = nil
+		}
+	}
+
+	return nil
+}
+
 // ServerPage abstracts the raw results of making a List() request against the API.
 // As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
 // data provided through the ExtractServers call.
diff --git a/openstack/compute/v2/servers/testing/fixtures.go b/openstack/compute/v2/servers/testing/fixtures.go
index b4fb7ff..613dc1e 100644
--- a/openstack/compute/v2/servers/testing/fixtures.go
+++ b/openstack/compute/v2/servers/testing/fixtures.go
@@ -154,7 +154,69 @@
 			"OS-EXT-STS:power_state": 1,
 			"config_drive": "",
 			"metadata": {}
-		}
+		},
+		{
+		"status": "ACTIVE",
+		"updated": "2014-09-25T13:04:49Z",
+		"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+		"OS-EXT-SRV-ATTR:host": "devstack",
+		"addresses": {
+			"private": [
+				{
+					"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+					"version": 4,
+					"addr": "10.0.0.31",
+					"OS-EXT-IPS:type": "fixed"
+				}
+			]
+		},
+		"links": [
+			{
+				"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel": "self"
+			},
+			{
+				"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel": "bookmark"
+			}
+		],
+		"key_name": null,
+		"image": "",
+		"OS-EXT-STS:task_state": null,
+		"OS-EXT-STS:vm_state": "active",
+		"OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
+		"OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
+		"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
+		"flavor": {
+			"id": "1",
+			"links": [
+				{
+					"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+					"rel": "bookmark"
+				}
+			]
+		},
+		"id": "9e5476bd-a4ec-4653-93d6-72c93aa682bb",
+		"security_groups": [
+			{
+				"name": "default"
+			}
+		],
+		"OS-SRV-USG:terminated_at": null,
+		"OS-EXT-AZ:availability_zone": "nova",
+		"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
+		"name": "merp",
+		"created": "2014-09-25T13:04:41Z",
+		"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
+		"OS-DCF:diskConfig": "MANUAL",
+		"os-extended-volumes:volumes_attached": [],
+		"accessIPv4": "",
+		"accessIPv6": "",
+		"progress": 0,
+		"OS-EXT-STS:power_state": 1,
+		"config_drive": "",
+		"metadata": {}
+	}
 	]
 }
 `
@@ -353,6 +415,54 @@
 			},
 		},
 	}
+
+	// ServerMerp is a Server struct that should correspond to the second server in ServerListBody.
+	ServerMerp = servers.Server{
+		Status:  "ACTIVE",
+		Updated: "2014-09-25T13:04:49Z",
+		HostID:  "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+		Addresses: map[string]interface{}{
+			"private": []interface{}{
+				map[string]interface{}{
+					"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+					"version":                 float64(4),
+					"addr":                    "10.0.0.31",
+					"OS-EXT-IPS:type":         "fixed",
+				},
+			},
+		},
+		Links: []interface{}{
+			map[string]interface{}{
+				"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel":  "self",
+			},
+			map[string]interface{}{
+				"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel":  "bookmark",
+			},
+		},
+		Image: nil,
+		Flavor: map[string]interface{}{
+			"id": "1",
+			"links": []interface{}{
+				map[string]interface{}{
+					"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+					"rel":  "bookmark",
+				},
+			},
+		},
+		ID:       "9e5476bd-a4ec-4653-93d6-72c93aa682bb",
+		UserID:   "9349aff8be7545ac9d2f1d00999a23cd",
+		Name:     "merp",
+		Created:  "2014-09-25T13:04:41Z",
+		TenantID: "fcad67a6189847c4aecfa3c81a05783b",
+		Metadata: map[string]interface{}{},
+		SecurityGroups: []map[string]interface{}{
+			map[string]interface{}{
+				"name": "default",
+			},
+		},
+	}
 )
 
 type CreateOptsWithCustomField struct {
diff --git a/openstack/compute/v2/servers/testing/requests_test.go b/openstack/compute/v2/servers/testing/requests_test.go
index 7db6b93..6d792d5 100644
--- a/openstack/compute/v2/servers/testing/requests_test.go
+++ b/openstack/compute/v2/servers/testing/requests_test.go
@@ -26,11 +26,12 @@
 			return false, err
 		}
 
-		if len(actual) != 2 {
-			t.Fatalf("Expected 2 servers, got %d", len(actual))
+		if len(actual) != 3 {
+			t.Fatalf("Expected 3 servers, got %d", len(actual))
 		}
 		th.CheckDeepEquals(t, ServerHerp, actual[0])
 		th.CheckDeepEquals(t, ServerDerp, actual[1])
+		th.CheckDeepEquals(t, ServerMerp, actual[2])
 
 		return true, nil
 	})
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
index ce493c9..bc4a3c6 100644
--- a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
@@ -22,6 +22,7 @@
 	TenantID           string `q:"tenant_id"`
 	ProvisioningStatus string `q:"provisioning_status"`
 	VipAddress         string `q:"vip_address"`
+	VipPortID          string `q:"vip_port_id"`
 	VipSubnetID        string `q:"vip_subnet_id"`
 	ID                 string `q:"id"`
 	OperatingStatus    string `q:"operating_status"`
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
index 168e531..4423c24 100644
--- a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
@@ -20,6 +20,8 @@
 	ProvisioningStatus string `json:"provisioning_status"`
 	// The IP address of the Loadbalancer.
 	VipAddress string `json:"vip_address"`
+	// The UUID of the port associated with the IP address.
+	VipPortID string `json:"vip_port_id"`
 	// The UUID of the subnet on which to allocate the virtual IP for the Loadbalancer address.
 	VipSubnetID string `json:"vip_subnet_id"`
 	// The unique ID for the LoadBalancer.
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go
index f882949..a452236 100644
--- a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go
@@ -25,6 +25,7 @@
 			"description": "lb config for the web tier",
 			"vip_subnet_id": "8a49c438-848f-467b-9655-ea1548708154",
 			"vip_address": "10.30.176.47",
+			"vip_port_id": "2a22e552-a347-44fd-b530-1f2b1b2a6735",
 			"flavor": "small",
 			"provider": "haproxy",
 			"admin_state_up": true,
@@ -38,6 +39,7 @@
 			"description": "lb config for the db tier",
 			"vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 			"vip_address": "10.30.176.48",
+			"vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 			"flavor": "medium",
 			"provider": "haproxy",
 			"admin_state_up": true,
@@ -58,6 +60,7 @@
 		"description": "lb config for the db tier",
 		"vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 		"vip_address": "10.30.176.48",
+		"vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 		"flavor": "medium",
 		"provider": "haproxy",
 		"admin_state_up": true,
@@ -77,6 +80,7 @@
 		"description": "lb config for the db tier",
 		"vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 		"vip_address": "10.30.176.48",
+		"vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 		"flavor": "medium",
 		"provider": "haproxy",
 		"admin_state_up": true,
@@ -126,6 +130,7 @@
 		Description:        "lb config for the web tier",
 		VipSubnetID:        "8a49c438-848f-467b-9655-ea1548708154",
 		VipAddress:         "10.30.176.47",
+		VipPortID:          "2a22e552-a347-44fd-b530-1f2b1b2a6735",
 		Flavor:             "small",
 		Provider:           "haproxy",
 		AdminStateUp:       true,
@@ -139,6 +144,7 @@
 		Description:        "lb config for the db tier",
 		VipSubnetID:        "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 		VipAddress:         "10.30.176.48",
+		VipPortID:          "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 		Flavor:             "medium",
 		Provider:           "haproxy",
 		AdminStateUp:       true,
@@ -152,6 +158,7 @@
 		Description:        "lb config for the db tier",
 		VipSubnetID:        "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 		VipAddress:         "10.30.176.48",
+		VipPortID:          "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 		Flavor:             "medium",
 		Provider:           "haproxy",
 		AdminStateUp:       true,