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 (