add rackpsace/gophercloud commits
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index becaf44..2cf355b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -11,7 +11,8 @@
 way than just downloading it. Here are the basic installation instructions:
 
 1. Configure your `$GOPATH` and run `go get` as described in the main
-[README](/README.md#how-to-install).
+[README](/README.md#how-to-install) but add `-tags "fixtures acceptance"` to
+get dependencies for unit and acceptance tests.
 
 2. Move into the directory that houses your local repository:
 
@@ -158,25 +159,25 @@
 To run all tests:
 
 ```bash
-go test ./...
+go test -tags fixtures ./...
 ```
 
 To run all tests with verbose output:
 
 ```bash
-go test -v ./...
+go test -v -tags fixtures ./...
 ```
 
 To run tests that match certain [build tags]():
 
 ```bash
-go test -tags "foo bar" ./...
+go test -tags "fixtures foo bar" ./...
 ```
 
 To run tests for a particular sub-package:
 
 ```bash
-cd ./path/to/package && go test .
+cd ./path/to/package && go test -tags fixtures .
 ```
 
 ## Style guide
diff --git a/acceptance/openstack/compute/v2/quotaset_test.go b/acceptance/openstack/compute/v2/quotaset_test.go
new file mode 100644
index 0000000..3851edf
--- /dev/null
+++ b/acceptance/openstack/compute/v2/quotaset_test.go
@@ -0,0 +1,60 @@
+// +build acceptance compute
+
+package v2
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack"
+	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets"
+	"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestGetQuotaset(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	idclient := openstack.NewIdentityV2(client.ProviderClient)
+	quotaset, err := quotasets.Get(client, findTenant(t, idclient)).Extract()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Logf("QuotaSet details:\n")
+	t.Logf("                   instances=[%d]\n", quotaset.Instances)
+	t.Logf("                       cores=[%d]\n", quotaset.Cores)
+	t.Logf("                         ram=[%d]\n", quotaset.Ram)
+	t.Logf("                   key_pairs=[%d]\n", quotaset.KeyPairs)
+	t.Logf("              metadata_items=[%d]\n", quotaset.MetadataItems)
+	t.Logf("             security_groups=[%d]\n", quotaset.SecurityGroups)
+	t.Logf("        security_group_rules=[%d]\n", quotaset.SecurityGroupRules)
+	t.Logf("                   fixed_ips=[%d]\n", quotaset.FixedIps)
+	t.Logf("                floating_ips=[%d]\n", quotaset.FloatingIps)
+	t.Logf(" injected_file_content_bytes=[%d]\n", quotaset.InjectedFileContentBytes)
+	t.Logf("    injected_file_path_bytes=[%d]\n", quotaset.InjectedFilePathBytes)
+	t.Logf("              injected_files=[%d]\n", quotaset.InjectedFiles)
+
+}
+
+func findTenant(t *testing.T, client *gophercloud.ServiceClient) string {
+	var tenantID string
+	err := tenants.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
+		tenantList, err := tenants.ExtractTenants(page)
+		th.AssertNoErr(t, err)
+
+		for _, t := range tenantList {
+			tenantID = t.ID
+			break
+		}
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	return tenantID
+}
diff --git a/acceptance/openstack/compute/v2/secgroup_test.go b/acceptance/openstack/compute/v2/secgroup_test.go
index 3100b1f..da06c35 100644
--- a/acceptance/openstack/compute/v2/secgroup_test.go
+++ b/acceptance/openstack/compute/v2/secgroup_test.go
@@ -107,6 +107,24 @@
 	th.AssertNoErr(t, err)
 
 	t.Logf("Deleted rule %s from group %s", rule.ID, id)
+
+	icmpOpts := secgroups.CreateRuleOpts{
+		ParentGroupID: id,
+		FromPort:      0,
+		ToPort:        0,
+		IPProtocol:    "ICMP",
+		CIDR:          "0.0.0.0/0",
+	}
+
+	icmpRule, err := secgroups.CreateRule(client, icmpOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	t.Logf("Adding ICMP rule %s to group %s", icmpRule.ID, id)
+
+	err = secgroups.DeleteRule(client, icmpRule.ID).ExtractErr()
+	th.AssertNoErr(t, err)
+
+	t.Logf("Deleted ICMP rule %s from group %s", icmpRule.ID, id)
 }
 
 func findServer(t *testing.T, client *gophercloud.ServiceClient) (string, bool) {
diff --git a/acceptance/openstack/networking/v2/subnet_test.go b/acceptance/openstack/networking/v2/subnet_test.go
index f6ce17e..c9efd5c 100644
--- a/acceptance/openstack/networking/v2/subnet_test.go
+++ b/acceptance/openstack/networking/v2/subnet_test.go
@@ -11,7 +11,7 @@
 	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func TestList(t *testing.T) {
+func TestSubnetList(t *testing.T) {
 	Setup(t)
 	defer Teardown()
 
@@ -32,7 +32,7 @@
 	th.CheckNoErr(t, err)
 }
 
-func TestCRUD(t *testing.T) {
+func TestSubnetCRUD(t *testing.T) {
 	Setup(t)
 	defer Teardown()
 
@@ -61,6 +61,7 @@
 	th.AssertEquals(t, s.IPVersion, 4)
 	th.AssertEquals(t, s.Name, "my_subnet")
 	th.AssertEquals(t, s.EnableDHCP, false)
+	th.AssertEquals(t, s.GatewayIP, "192.168.199.1")
 	subnetID := s.ID
 
 	// Get subnet
@@ -79,6 +80,60 @@
 	t.Log("Delete subnet")
 	res := subnets.Delete(Client, subnetID)
 	th.AssertNoErr(t, res.Err)
+
+	// Create subnet with no gateway
+	t.Log("Create subnet with no gateway")
+	opts = subnets.CreateOpts{
+		NetworkID:  networkID,
+		CIDR:       "192.168.199.0/24",
+		IPVersion:  subnets.IPv4,
+		Name:       "my_subnet",
+		EnableDHCP: &enable,
+		NoGateway:  true,
+	}
+	s, err = subnets.Create(Client, opts).Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, s.NetworkID, networkID)
+	th.AssertEquals(t, s.CIDR, "192.168.199.0/24")
+	th.AssertEquals(t, s.IPVersion, 4)
+	th.AssertEquals(t, s.Name, "my_subnet")
+	th.AssertEquals(t, s.EnableDHCP, false)
+	th.AssertEquals(t, s.GatewayIP, "")
+	subnetID = s.ID
+
+	// Get subnet
+	t.Log("Getting subnet with no gateway")
+	s, err = subnets.Get(Client, subnetID).Extract()
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, s.ID, subnetID)
+
+	// Update subnet
+	t.Log("Update subnet with no gateway")
+	s, err = subnets.Update(Client, subnetID, subnets.UpdateOpts{Name: "new_subnet_name"}).Extract()
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, s.Name, "new_subnet_name")
+
+	// Delete subnet
+	t.Log("Delete subnet with no gateway")
+	res = subnets.Delete(Client, subnetID)
+	th.AssertNoErr(t, res.Err)
+
+	// Create subnet with invalid gateway configuration
+	t.Log("Create subnet with invalid gateway configuration")
+	opts = subnets.CreateOpts{
+		NetworkID:  networkID,
+		CIDR:       "192.168.199.0/24",
+		IPVersion:  subnets.IPv4,
+		Name:       "my_subnet",
+		EnableDHCP: &enable,
+		NoGateway:  true,
+		GatewayIP:  "192.168.199.1",
+	}
+	_, err = subnets.Create(Client, opts).Extract()
+	if err == nil {
+		t.Fatalf("Expected an error, got none")
+	}
 }
 
 func TestBatchCreate(t *testing.T) {
diff --git a/openstack/blockstorage/v1/apiversions/urls.go b/openstack/blockstorage/v1/apiversions/urls.go
index 936f1c9..c9cf895 100644
--- a/openstack/blockstorage/v1/apiversions/urls.go
+++ b/openstack/blockstorage/v1/apiversions/urls.go
@@ -2,6 +2,7 @@
 
 import (
 	"strings"
+	"net/url"
 
 	"github.com/gophercloud/gophercloud"
 )
@@ -11,5 +12,7 @@
 }
 
 func listURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL("")
+	u, _ := url.Parse(c.ServiceURL(""))
+	u.Path = "/"
+	return u.String()
 }
diff --git a/openstack/blockstorage/v1/apiversions/urls_test.go b/openstack/blockstorage/v1/apiversions/urls_test.go
new file mode 100644
index 0000000..68cfb8c
--- /dev/null
+++ b/openstack/blockstorage/v1/apiversions/urls_test.go
@@ -0,0 +1,31 @@
+package apiversions
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+const endpoint2 = "http://localhost:57909/v1/3a02ee0b5cf14816b41b17e851d29a94"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func endpointClient2() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint2}
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "v1")
+	expected := endpoint + "v1/"
+	th.AssertEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+	actual := listURL(endpointClient2())
+	expected := endpoint
+	th.AssertEquals(t, expected, actual)
+}
diff --git a/openstack/blockstorage/v1/snapshots/fixtures.go b/openstack/blockstorage/v1/snapshots/fixtures.go
index b1bfef8..1dcdfca 100644
--- a/openstack/blockstorage/v1/snapshots/fixtures.go
+++ b/openstack/blockstorage/v1/snapshots/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package snapshots
 
 import (
diff --git a/openstack/blockstorage/v1/volumes/testing/fixtures.go b/openstack/blockstorage/v1/volumes/testing/fixtures.go
index 421cbf4..0d34d5e 100644
--- a/openstack/blockstorage/v1/volumes/testing/fixtures.go
+++ b/openstack/blockstorage/v1/volumes/testing/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package testing
 
 import (
diff --git a/openstack/blockstorage/v1/volumetypes/fixtures.go b/openstack/blockstorage/v1/volumetypes/fixtures.go
index 1969120..cb6fadf 100644
--- a/openstack/blockstorage/v1/volumetypes/fixtures.go
+++ b/openstack/blockstorage/v1/volumetypes/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package volumetypes
 
 import (
diff --git a/openstack/cdn/v1/base/fixtures.go b/openstack/cdn/v1/base/fixtures.go
index f95d893..226edae 100644
--- a/openstack/cdn/v1/base/fixtures.go
+++ b/openstack/cdn/v1/base/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package base
 
 import (
diff --git a/openstack/cdn/v1/flavors/fixtures.go b/openstack/cdn/v1/flavors/fixtures.go
index 285075e..5d07491 100644
--- a/openstack/cdn/v1/flavors/fixtures.go
+++ b/openstack/cdn/v1/flavors/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package flavors
 
 import (
diff --git a/openstack/cdn/v1/serviceassets/fixtures.go b/openstack/cdn/v1/serviceassets/fixtures.go
index 9c62514..a66c503 100644
--- a/openstack/cdn/v1/serviceassets/fixtures.go
+++ b/openstack/cdn/v1/serviceassets/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package serviceassets
 
 import (
diff --git a/openstack/cdn/v1/services/fixtures.go b/openstack/cdn/v1/services/fixtures.go
index c882f8c..12d260e 100644
--- a/openstack/cdn/v1/services/fixtures.go
+++ b/openstack/cdn/v1/services/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package services
 
 import (
diff --git a/openstack/client.go b/openstack/client.go
index 2fa4750..3e11508 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -151,13 +151,17 @@
 		v3Client.Endpoint = endpoint
 	}
 
+	// copy the auth options to a local variable that we can change. `options`
+	// needs to stay as-is for reauth purposes
+	v3Options := options
+
 	var scope *tokens3.Scope
 	if options.TenantID != "" {
 		scope = &tokens3.Scope{
 			ProjectID: options.TenantID,
 		}
-		options.TenantID = ""
-		options.TenantName = ""
+		v3Options.TenantID = ""
+		v3Options.TenantName = ""
 	} else {
 		if options.TenantName != "" {
 			scope = &tokens3.Scope{
@@ -165,7 +169,7 @@
 				DomainID:    options.DomainID,
 				DomainName:  options.DomainName,
 			}
-			options.TenantName = ""
+			v3Options.TenantName = ""
 		}
 	}
 
diff --git a/openstack/compute/v2/extensions/defsecrules/fixtures.go b/openstack/compute/v2/extensions/defsecrules/fixtures.go
index d35af0b..4fee896 100644
--- a/openstack/compute/v2/extensions/defsecrules/fixtures.go
+++ b/openstack/compute/v2/extensions/defsecrules/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package defsecrules
 
 import (
@@ -72,6 +74,41 @@
 	})
 }
 
+func mockCreateRuleResponseICMPZero(t *testing.T) {
+	th.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "security_group_default_rule": {
+    "ip_protocol": "ICMP",
+    "from_port": 0,
+    "to_port": 0,
+    "cidr": "10.10.12.0/24"
+  }
+}
+	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+  "security_group_default_rule": {
+    "from_port": 0,
+    "id": "{ruleID}",
+    "ip_protocol": "ICMP",
+    "ip_range": {
+      "cidr": "10.10.12.0/24"
+    },
+    "to_port": 0
+  }
+}
+`)
+	})
+}
+
 func mockGetRuleResponse(t *testing.T, ruleID string) {
 	url := rootPath + "/" + ruleID
 	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
diff --git a/openstack/compute/v2/extensions/defsecrules/requests_test.go b/openstack/compute/v2/extensions/defsecrules/requests_test.go
index 0e2a010..df568fe 100644
--- a/openstack/compute/v2/extensions/defsecrules/requests_test.go
+++ b/openstack/compute/v2/extensions/defsecrules/requests_test.go
@@ -69,6 +69,32 @@
 	th.AssertDeepEquals(t, expected, group)
 }
 
+func TestCreateICMPZero(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockCreateRuleResponseICMPZero(t)
+
+	opts := CreateOpts{
+		IPProtocol: "ICMP",
+		FromPort:   0,
+		ToPort:     0,
+		CIDR:       "10.10.12.0/24",
+	}
+
+	group, err := Create(client.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &DefaultRule{
+		ID:         ruleID,
+		FromPort:   0,
+		ToPort:     0,
+		IPProtocol: "ICMP",
+		IPRange:    secgroups.IPRange{CIDR: "10.10.12.0/24"},
+	}
+	th.AssertDeepEquals(t, expected, group)
+}
+
 func TestGet(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
diff --git a/openstack/compute/v2/extensions/quotasets/doc.go b/openstack/compute/v2/extensions/quotasets/doc.go
new file mode 100644
index 0000000..721024e
--- /dev/null
+++ b/openstack/compute/v2/extensions/quotasets/doc.go
@@ -0,0 +1,3 @@
+// Package quotasets provides information and interaction with QuotaSet
+// extension for the OpenStack Compute service.
+package quotasets
diff --git a/openstack/compute/v2/extensions/quotasets/fixtures.go b/openstack/compute/v2/extensions/quotasets/fixtures.go
new file mode 100644
index 0000000..c1bb4ea
--- /dev/null
+++ b/openstack/compute/v2/extensions/quotasets/fixtures.go
@@ -0,0 +1,59 @@
+// +build fixtures
+
+package quotasets
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	"github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// GetOutput is a sample response to a Get call.
+const GetOutput = `
+{
+   "quota_set" : {
+      "instances" : 25,
+      "security_groups" : 10,
+      "security_group_rules" : 20,
+      "cores" : 200,
+      "injected_file_content_bytes" : 10240,
+      "injected_files" : 5,
+      "metadata_items" : 128,
+      "ram" : 200000,
+      "keypairs" : 10,
+      "injected_file_path_bytes" : 255
+   }
+}
+`
+
+const FirstTenantID = "555544443333222211110000ffffeeee"
+
+// FirstQuotaSet is the first result in ListOutput.
+var FirstQuotaSet = QuotaSet{
+	FixedIps:                 0,
+	FloatingIps:              0,
+	InjectedFileContentBytes: 10240,
+	InjectedFilePathBytes:    255,
+	InjectedFiles:            5,
+	KeyPairs:                 10,
+	MetadataItems:            128,
+	Ram:                      200000,
+	SecurityGroupRules:       20,
+	SecurityGroups:           10,
+	Cores:                    200,
+	Instances:                25,
+}
+
+// HandleGetSuccessfully configures the test server to respond to a Get request for sample tenant
+func HandleGetSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, 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)
+	})
+}
diff --git a/openstack/compute/v2/extensions/quotasets/requests.go b/openstack/compute/v2/extensions/quotasets/requests.go
new file mode 100644
index 0000000..52f0839
--- /dev/null
+++ b/openstack/compute/v2/extensions/quotasets/requests.go
@@ -0,0 +1,12 @@
+package quotasets
+
+import (
+	"github.com/rackspace/gophercloud"
+)
+
+// Get returns public data about a previously created QuotaSet.
+func Get(client *gophercloud.ServiceClient, tenantID string) GetResult {
+	var res GetResult
+	_, res.Err = client.Get(getURL(client, tenantID), &res.Body, nil)
+	return res
+}
diff --git a/openstack/compute/v2/extensions/quotasets/requests_test.go b/openstack/compute/v2/extensions/quotasets/requests_test.go
new file mode 100644
index 0000000..5d766fa
--- /dev/null
+++ b/openstack/compute/v2/extensions/quotasets/requests_test.go
@@ -0,0 +1,16 @@
+package quotasets
+
+import (
+	th "github.com/rackspace/gophercloud/testhelper"
+	"github.com/rackspace/gophercloud/testhelper/client"
+	"testing"
+)
+
+func TestGet(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSuccessfully(t)
+	actual, err := Get(client.ServiceClient(), FirstTenantID).Extract()
+	th.AssertNoErr(t, err)
+	th.CheckDeepEquals(t, &FirstQuotaSet, actual)
+}
diff --git a/openstack/compute/v2/extensions/quotasets/results.go b/openstack/compute/v2/extensions/quotasets/results.go
new file mode 100644
index 0000000..cbf4d6b
--- /dev/null
+++ b/openstack/compute/v2/extensions/quotasets/results.go
@@ -0,0 +1,86 @@
+package quotasets
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// QuotaSet is a set of operational limits that allow for control of compute usage.
+type QuotaSet struct {
+	//ID is tenant associated with this quota_set
+	ID string `mapstructure:"id"`
+	//FixedIps is number of fixed ips alloted this quota_set
+	FixedIps int `mapstructure:"fixed_ips"`
+	// FloatingIps is number of floating ips alloted this quota_set
+	FloatingIps int `mapstructure:"floating_ips"`
+	// InjectedFileContentBytes is content bytes allowed for each injected file
+	InjectedFileContentBytes int `mapstructure:"injected_file_content_bytes"`
+	// InjectedFilePathBytes is allowed bytes for each injected file path
+	InjectedFilePathBytes int `mapstructure:"injected_file_path_bytes"`
+	// InjectedFiles is injected files allowed for each project
+	InjectedFiles int `mapstructure:"injected_files"`
+	// KeyPairs is number of ssh keypairs
+	KeyPairs int `mapstructure:"keypairs"`
+	// MetadataItems is number of metadata items allowed for each instance
+	MetadataItems int `mapstructure:"metadata_items"`
+	// Ram is megabytes allowed for each instance
+	Ram int `mapstructure:"ram"`
+	// SecurityGroupRules is rules allowed for each security group
+	SecurityGroupRules int `mapstructure:"security_group_rules"`
+	// SecurityGroups security groups allowed for each project
+	SecurityGroups int `mapstructure:"security_groups"`
+	// Cores is number of instance cores allowed for each project
+	Cores int `mapstructure:"cores"`
+	// Instances is number of instances allowed for each project
+	Instances int `mapstructure:"instances"`
+}
+
+// QuotaSetPage stores a single, only page of QuotaSet results from a List call.
+type QuotaSetPage struct {
+	pagination.SinglePageBase
+}
+
+// IsEmpty determines whether or not a QuotaSetsetPage is empty.
+func (page QuotaSetPage) IsEmpty() (bool, error) {
+	ks, err := ExtractQuotaSets(page)
+	return len(ks) == 0, err
+}
+
+// ExtractQuotaSets interprets a page of results as a slice of QuotaSets.
+func ExtractQuotaSets(page pagination.Page) ([]QuotaSet, error) {
+	var resp struct {
+		QuotaSets []QuotaSet `mapstructure:"quotas"`
+	}
+
+	err := mapstructure.Decode(page.(QuotaSetPage).Body, &resp)
+	results := make([]QuotaSet, len(resp.QuotaSets))
+	for i, q := range resp.QuotaSets {
+		results[i] = q
+	}
+	return results, err
+}
+
+type quotaResult struct {
+	gophercloud.Result
+}
+
+// Extract is a method that attempts to interpret any QuotaSet resource response as a QuotaSet struct.
+func (r quotaResult) Extract() (*QuotaSet, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		QuotaSet *QuotaSet `json:"quota_set" mapstructure:"quota_set"`
+	}
+
+	err := mapstructure.Decode(r.Body, &res)
+	return res.QuotaSet, err
+}
+
+// GetResult is the response from a Get operation. Call its Extract method to interpret it
+// as a QuotaSet.
+type GetResult struct {
+	quotaResult
+}
diff --git a/openstack/compute/v2/extensions/quotasets/urls.go b/openstack/compute/v2/extensions/quotasets/urls.go
new file mode 100644
index 0000000..c04d941
--- /dev/null
+++ b/openstack/compute/v2/extensions/quotasets/urls.go
@@ -0,0 +1,13 @@
+package quotasets
+
+import "github.com/rackspace/gophercloud"
+
+const resourcePath = "os-quota-sets"
+
+func resourceURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL(resourcePath)
+}
+
+func getURL(c *gophercloud.ServiceClient, tenantID string) string {
+	return c.ServiceURL(resourcePath, tenantID)
+}
diff --git a/openstack/compute/v2/extensions/quotasets/urls_test.go b/openstack/compute/v2/extensions/quotasets/urls_test.go
new file mode 100644
index 0000000..f19a6ad
--- /dev/null
+++ b/openstack/compute/v2/extensions/quotasets/urls_test.go
@@ -0,0 +1,16 @@
+package quotasets
+
+import (
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	"github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestGetURL(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	c := client.ServiceClient()
+
+	th.CheckEquals(t, c.Endpoint+"os-quota-sets/wat", getURL(c, "wat"))
+}
diff --git a/openstack/compute/v2/extensions/secgroups/fixtures.go b/openstack/compute/v2/extensions/secgroups/fixtures.go
index 0f97ac8..e4ca587 100644
--- a/openstack/compute/v2/extensions/secgroups/fixtures.go
+++ b/openstack/compute/v2/extensions/secgroups/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package secgroups
 
 import (
@@ -216,6 +218,42 @@
 	})
 }
 
+func mockAddRuleResponseICMPZero(t *testing.T) {
+	th.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "security_group_rule": {
+    "from_port": 0,
+    "ip_protocol": "ICMP",
+    "to_port": 0,
+    "parent_group_id": "{groupID}",
+    "cidr": "0.0.0.0/0"
+  }
+}	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+  "security_group_rule": {
+    "from_port": 0,
+    "group": {},
+    "ip_protocol": "ICMP",
+    "to_port": 0,
+    "parent_group_id": "{groupID}",
+    "ip_range": {
+      "cidr": "0.0.0.0/0"
+    },
+    "id": "{ruleID}"
+  }
+}`)
+	})
+}
+
 func mockDeleteRuleResponse(t *testing.T, ruleID string) {
 	url := fmt.Sprintf("/os-security-group-rules/%s", ruleID)
 	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
diff --git a/openstack/compute/v2/extensions/secgroups/requests_test.go b/openstack/compute/v2/extensions/secgroups/requests_test.go
index 9496d4a..bdbedcd 100644
--- a/openstack/compute/v2/extensions/secgroups/requests_test.go
+++ b/openstack/compute/v2/extensions/secgroups/requests_test.go
@@ -217,6 +217,36 @@
 	th.AssertDeepEquals(t, expected, rule)
 }
 
+func TestAddRuleICMPZero(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockAddRuleResponseICMPZero(t)
+
+	opts := CreateRuleOpts{
+		ParentGroupID: groupID,
+		FromPort:      0,
+		ToPort:        0,
+		IPProtocol:    "ICMP",
+		CIDR:          "0.0.0.0/0",
+	}
+
+	rule, err := CreateRule(client.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &Rule{
+		FromPort:      0,
+		ToPort:        0,
+		Group:         Group{},
+		IPProtocol:    "ICMP",
+		ParentGroupID: groupID,
+		IPRange:       IPRange{CIDR: "0.0.0.0/0"},
+		ID:            ruleID,
+	}
+
+	th.AssertDeepEquals(t, expected, rule)
+}
+
 func TestDeleteRule(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
diff --git a/openstack/compute/v2/extensions/startstop/fixtures.go b/openstack/compute/v2/extensions/startstop/fixtures.go
index 7169f7f..c5b4290 100644
--- a/openstack/compute/v2/extensions/startstop/fixtures.go
+++ b/openstack/compute/v2/extensions/startstop/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package startstop
 
 import (
diff --git a/openstack/db/v1/configurations/fixtures.go b/openstack/db/v1/configurations/fixtures.go
index ae65416..9064c6c 100644
--- a/openstack/db/v1/configurations/fixtures.go
+++ b/openstack/db/v1/configurations/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package configurations
 
 import (
diff --git a/openstack/db/v1/databases/fixtures.go b/openstack/db/v1/databases/fixtures.go
index 4b35062..c99f990 100644
--- a/openstack/db/v1/databases/fixtures.go
+++ b/openstack/db/v1/databases/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package databases
 
 import (
diff --git a/openstack/db/v1/datastores/fixtures.go b/openstack/db/v1/datastores/fixtures.go
index 837b1f4..8caa586 100644
--- a/openstack/db/v1/datastores/fixtures.go
+++ b/openstack/db/v1/datastores/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package datastores
 
 import (
diff --git a/openstack/db/v1/flavors/fixtures.go b/openstack/db/v1/flavors/fixtures.go
index 6a013f9..257b214 100644
--- a/openstack/db/v1/flavors/fixtures.go
+++ b/openstack/db/v1/flavors/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package flavors
 
 import (
diff --git a/openstack/db/v1/instances/fixtures.go b/openstack/db/v1/instances/fixtures.go
index d0a3856..6be384c 100644
--- a/openstack/db/v1/instances/fixtures.go
+++ b/openstack/db/v1/instances/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package instances
 
 import (
diff --git a/openstack/db/v1/users/fixtures.go b/openstack/db/v1/users/fixtures.go
index 3b27005..3661154 100644
--- a/openstack/db/v1/users/fixtures.go
+++ b/openstack/db/v1/users/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package users
 
 import (
diff --git a/openstack/identity/v2/extensions/admin/roles/fixtures.go b/openstack/identity/v2/extensions/admin/roles/fixtures.go
index 519dfae..6b11f5c 100644
--- a/openstack/identity/v2/extensions/admin/roles/fixtures.go
+++ b/openstack/identity/v2/extensions/admin/roles/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package roles
 
 import (
diff --git a/openstack/identity/v2/users/fixtures.go b/openstack/identity/v2/users/fixtures.go
index ecd1768..7b0bc4c 100644
--- a/openstack/identity/v2/users/fixtures.go
+++ b/openstack/identity/v2/users/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package users
 
 import (
diff --git a/openstack/networking/v2/extensions/layer3/routers/requests.go b/openstack/networking/v2/extensions/layer3/routers/requests.go
index 48c0a52..71b2f62 100644
--- a/openstack/networking/v2/extensions/layer3/routers/requests.go
+++ b/openstack/networking/v2/extensions/layer3/routers/requests.go
@@ -40,6 +40,10 @@
 	})
 }
 
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
 type CreateOptsBuilder interface {
 	ToRouterCreateMap() (map[string]interface{}, error)
 }
diff --git a/openstack/networking/v2/subnets/errors.go b/openstack/networking/v2/subnets/errors.go
index 0db0a6e..d2f7b46 100644
--- a/openstack/networking/v2/subnets/errors.go
+++ b/openstack/networking/v2/subnets/errors.go
@@ -7,7 +7,8 @@
 }
 
 var (
-	errNetworkIDRequired = err("A network ID is required")
-	errCIDRRequired      = err("A valid CIDR is required")
-	errInvalidIPType     = err("An IP type must either be 4 or 6")
+	errNetworkIDRequired    = err("A network ID is required")
+	errCIDRRequired         = err("A valid CIDR is required")
+	errInvalidIPType        = err("An IP type must either be 4 or 6")
+	errInvalidGatewayConfig = err("Both disabling the gateway and specifying a gateway is not allowed")
 )
diff --git a/openstack/networking/v2/subnets/requests_test.go b/openstack/networking/v2/subnets/requests_test.go
index 5178c90..3738d4b 100644
--- a/openstack/networking/v2/subnets/requests_test.go
+++ b/openstack/networking/v2/subnets/requests_test.go
@@ -59,6 +59,24 @@
             "gateway_ip": "192.0.0.1",
             "cidr": "192.0.0.0/8",
             "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+        },
+        {
+            "name": "my_gatewayless_subnet",
+            "enable_dhcp": true,
+            "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23",
+            "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+            "dns_nameservers": [],
+            "allocation_pools": [
+                {
+                    "start": "192.168.1.2",
+                    "end": "192.168.1.254"
+                }
+            ],
+            "host_routes": [],
+            "ip_version": 4,
+            "gateway_ip": null,
+            "cidr": "192.168.1.0/24",
+            "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c"
         }
     ]
 }
@@ -112,6 +130,24 @@
 				CIDR:       "192.0.0.0/8",
 				ID:         "54d6f61d-db07-451c-9ab3-b9609b6b6f0b",
 			},
+			Subnet{
+				Name:           "my_gatewayless_subnet",
+				EnableDHCP:     true,
+				NetworkID:      "d32019d3-bc6e-4319-9c1d-6722fc136a23",
+				TenantID:       "4fd44f30292945e481c7b8a0c8908869",
+				DNSNameservers: []string{},
+				AllocationPools: []AllocationPool{
+					AllocationPool{
+						Start: "192.168.1.2",
+						End:   "192.168.1.254",
+					},
+				},
+				HostRoutes: []HostRoute{},
+				IPVersion:  4,
+				GatewayIP:  "",
+				CIDR:       "192.168.1.0/24",
+				ID:         "54d6f61d-db07-451c-9ab3-b9609b6b6f0c",
+			},
 		}
 
 		th.CheckDeepEquals(t, expected, actual)
@@ -270,6 +306,145 @@
 	th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126")
 }
 
+func TestCreateNoGateway(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Content-Type", "application/json")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestJSONRequest(t, r, `
+{
+    "subnet": {
+        "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23",
+        "ip_version": 4,
+        "cidr": "192.168.1.0/24",
+				"gateway_ip": null,
+				"allocation_pools": [
+						{
+								"start": "192.168.1.2",
+								"end": "192.168.1.254"
+						}
+				]
+    }
+}
+			`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusCreated)
+
+		fmt.Fprintf(w, `
+{
+    "subnet": {
+        "name": "",
+        "enable_dhcp": true,
+        "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23",
+        "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+        "allocation_pools": [
+            {
+                "start": "192.168.1.2",
+                "end": "192.168.1.254"
+            }
+        ],
+        "host_routes": [],
+        "ip_version": 4,
+        "gateway_ip": null,
+        "cidr": "192.168.1.0/24",
+        "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c"
+    }
+}
+		`)
+	})
+
+	opts := CreateOpts{
+		NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23",
+		IPVersion: 4,
+		CIDR:      "192.168.1.0/24",
+		NoGateway: true,
+		AllocationPools: []AllocationPool{
+			AllocationPool{
+				Start: "192.168.1.2",
+				End:   "192.168.1.254",
+			},
+		},
+		DNSNameservers: []string{},
+	}
+	s, err := Create(fake.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, s.Name, "")
+	th.AssertEquals(t, s.EnableDHCP, true)
+	th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a23")
+	th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869")
+	th.AssertDeepEquals(t, s.AllocationPools, []AllocationPool{
+		AllocationPool{
+			Start: "192.168.1.2",
+			End:   "192.168.1.254",
+		},
+	})
+	th.AssertDeepEquals(t, s.HostRoutes, []HostRoute{})
+	th.AssertEquals(t, s.IPVersion, 4)
+	th.AssertEquals(t, s.GatewayIP, "")
+	th.AssertEquals(t, s.CIDR, "192.168.1.0/24")
+	th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0c")
+}
+
+func TestCreateInvalidGatewayConfig(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Content-Type", "application/json")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestJSONRequest(t, r, `
+{
+    "subnet": {
+        "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23",
+        "ip_version": 4,
+        "cidr": "192.168.1.0/24",
+				"gateway_ip": "192.168.1.1",
+				"allocation_pools": [
+						{
+								"start": "192.168.1.2",
+								"end": "192.168.1.254"
+						}
+				]
+    }
+}
+			`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusCreated)
+	})
+
+	opts := CreateOpts{
+		NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23",
+		IPVersion: 4,
+		CIDR:      "192.168.1.0/24",
+		NoGateway: true,
+		GatewayIP: "192.168.1.1",
+		AllocationPools: []AllocationPool{
+			AllocationPool{
+				Start: "192.168.1.2",
+				End:   "192.168.1.254",
+			},
+		},
+		DNSNameservers: []string{},
+	}
+	_, err := Create(fake.ServiceClient(), opts).Extract()
+	if err == nil {
+		t.Fatalf("Expected an error, got none")
+	}
+
+	if err != errInvalidGatewayConfig {
+		t.Fatalf("Exected errInvalidGateway but got: %s", err)
+	}
+}
+
 func TestRequiredCreateOpts(t *testing.T) {
 	res := Create(fake.ServiceClient(), CreateOpts{})
 	if res.Err == nil {
diff --git a/openstack/orchestration/v1/buildinfo/fixtures.go b/openstack/orchestration/v1/buildinfo/fixtures.go
index 4e93126..87bc3ec 100644
--- a/openstack/orchestration/v1/buildinfo/fixtures.go
+++ b/openstack/orchestration/v1/buildinfo/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package buildinfo
 
 import (
diff --git a/openstack/orchestration/v1/stackevents/fixtures.go b/openstack/orchestration/v1/stackevents/fixtures.go
index 48524e5..e4af1bb 100644
--- a/openstack/orchestration/v1/stackevents/fixtures.go
+++ b/openstack/orchestration/v1/stackevents/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package stackevents
 
 import (
diff --git a/openstack/orchestration/v1/stackresources/fixtures.go b/openstack/orchestration/v1/stackresources/fixtures.go
index a622f7f..beec637 100644
--- a/openstack/orchestration/v1/stackresources/fixtures.go
+++ b/openstack/orchestration/v1/stackresources/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package stackresources
 
 import (
diff --git a/openstack/orchestration/v1/stacks/fixtures.go b/openstack/orchestration/v1/stacks/fixtures.go
index f1d66f4..4126cf6 100644
--- a/openstack/orchestration/v1/stacks/fixtures.go
+++ b/openstack/orchestration/v1/stacks/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package stacks
 
 import (
diff --git a/openstack/orchestration/v1/stacktemplates/fixtures.go b/openstack/orchestration/v1/stacktemplates/fixtures.go
index bfcaa90..4444a57 100644
--- a/openstack/orchestration/v1/stacktemplates/fixtures.go
+++ b/openstack/orchestration/v1/stacktemplates/fixtures.go
@@ -1,3 +1,5 @@
+// +build fixtures
+
 package stacktemplates
 
 import (