Merge pull request #395 from jrperritt/rs-security-groups
[wip] Rackspace Neutron Security Groups; Closes #391
diff --git a/acceptance/rackspace/networking/v2/security_test.go b/acceptance/rackspace/networking/v2/security_test.go
new file mode 100644
index 0000000..ec02991
--- /dev/null
+++ b/acceptance/rackspace/networking/v2/security_test.go
@@ -0,0 +1,165 @@
+// +build acceptance networking security
+
+package v2
+
+import (
+ "testing"
+
+ osGroups "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups"
+ osRules "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules"
+ osNetworks "github.com/rackspace/gophercloud/openstack/networking/v2/networks"
+ osPorts "github.com/rackspace/gophercloud/openstack/networking/v2/ports"
+ "github.com/rackspace/gophercloud/pagination"
+ rsNetworks "github.com/rackspace/gophercloud/rackspace/networking/v2/networks"
+ rsPorts "github.com/rackspace/gophercloud/rackspace/networking/v2/ports"
+ rsGroups "github.com/rackspace/gophercloud/rackspace/networking/v2/security/groups"
+ rsRules "github.com/rackspace/gophercloud/rackspace/networking/v2/security/rules"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestSecurityGroups(t *testing.T) {
+ Setup(t)
+ defer Teardown()
+
+ // create security group
+ groupID := createSecGroup(t)
+
+ // delete security group
+ defer deleteSecGroup(t, groupID)
+
+ // list security group
+ listSecGroups(t)
+
+ // get security group
+ getSecGroup(t, groupID)
+}
+
+func TestSecurityGroupRules(t *testing.T) {
+ Setup(t)
+ defer Teardown()
+
+ // create security group
+ groupID := createSecGroup(t)
+
+ defer deleteSecGroup(t, groupID)
+
+ // create security group rule
+ ruleID := createSecRule(t, groupID)
+
+ // delete security group rule
+ defer deleteSecRule(t, ruleID)
+
+ // list security group rule
+ listSecRules(t)
+
+ // get security group rule
+ getSecRule(t, ruleID)
+}
+
+func createSecGroup(t *testing.T) string {
+ sg, err := rsGroups.Create(Client, osGroups.CreateOpts{
+ Name: "new-webservers",
+ Description: "security group for webservers",
+ }).Extract()
+
+ th.AssertNoErr(t, err)
+
+ t.Logf("Created security group %s", sg.ID)
+
+ return sg.ID
+}
+
+func listSecGroups(t *testing.T) {
+ err := rsGroups.List(Client, osGroups.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ list, err := osGroups.ExtractGroups(page)
+ if err != nil {
+ t.Errorf("Failed to extract secgroups: %v", err)
+ return false, err
+ }
+
+ for _, sg := range list {
+ t.Logf("Listing security group: ID [%s] Name [%s]", sg.ID, sg.Name)
+ }
+
+ return true, nil
+ })
+
+ th.AssertNoErr(t, err)
+}
+
+func getSecGroup(t *testing.T, id string) {
+ sg, err := rsGroups.Get(Client, id).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Getting security group: ID [%s] Name [%s] Description [%s]", sg.ID, sg.Name, sg.Description)
+}
+
+func createSecGroupPort(t *testing.T, groupID string) (string, string) {
+ n, err := rsNetworks.Create(Client, osNetworks.CreateOpts{Name: "tmp_network"}).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Created network %s", n.ID)
+
+ opts := osPorts.CreateOpts{
+ NetworkID: n.ID,
+ Name: "my_port",
+ SecurityGroups: []string{groupID},
+ }
+ p, err := rsPorts.Create(Client, opts).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Created port %s with security group %s", p.ID, groupID)
+
+ return n.ID, p.ID
+}
+
+func deleteSecGroup(t *testing.T, groupID string) {
+ res := rsGroups.Delete(Client, groupID)
+ th.AssertNoErr(t, res.Err)
+ t.Logf("Deleted security group %s", groupID)
+}
+
+func createSecRule(t *testing.T, groupID string) string {
+ r, err := rsRules.Create(Client, osRules.CreateOpts{
+ Direction: "ingress",
+ PortRangeMin: 80,
+ EtherType: "IPv4",
+ PortRangeMax: 80,
+ Protocol: "tcp",
+ SecGroupID: groupID,
+ }).Extract()
+
+ th.AssertNoErr(t, err)
+
+ t.Logf("Created security group rule %s", r.ID)
+
+ return r.ID
+}
+
+func listSecRules(t *testing.T) {
+ err := rsRules.List(Client, osRules.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ list, err := osRules.ExtractRules(page)
+ if err != nil {
+ t.Errorf("Failed to extract sec rules: %v", err)
+ return false, err
+ }
+
+ for _, r := range list {
+ t.Logf("Listing security rule: ID [%s]", r.ID)
+ }
+
+ return true, nil
+ })
+
+ th.AssertNoErr(t, err)
+}
+
+func getSecRule(t *testing.T, id string) {
+ r, err := rsRules.Get(Client, id).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Getting security rule: ID [%s] Direction [%s] EtherType [%s] Protocol [%s]",
+ r.ID, r.Direction, r.EtherType, r.Protocol)
+}
+
+func deleteSecRule(t *testing.T, id string) {
+ res := rsRules.Delete(Client, id)
+ th.AssertNoErr(t, res.Err)
+ t.Logf("Deleted security rule %s", id)
+}
diff --git a/openstack/networking/v2/extensions/security/doc.go b/openstack/networking/v2/extensions/security/doc.go
index 8ef455f..31f744c 100644
--- a/openstack/networking/v2/extensions/security/doc.go
+++ b/openstack/networking/v2/extensions/security/doc.go
@@ -15,13 +15,13 @@
//
// For ingress traffic (to an instance)
// - Only traffic matched with security group rules are allowed.
-// - When there is no rule defined, all traffic are dropped.
+// - When there is no rule defined, all traffic is dropped.
//
// For egress traffic (from an instance)
// - Only traffic matched with security group rules are allowed.
// - When there is no rule defined, all egress traffic are dropped.
// - When a new security group is created, rules to allow all egress traffic
-// are automatically added.
+// is automatically added.
//
// "default security group" is defined for each tenant.
// - For the default security group a rule which allows intercommunication
diff --git a/rackspace/networking/v2/ports/delegate.go b/rackspace/networking/v2/ports/delegate.go
index 091b99e..95728d1 100644
--- a/rackspace/networking/v2/ports/delegate.go
+++ b/rackspace/networking/v2/ports/delegate.go
@@ -24,6 +24,9 @@
// Create accepts a CreateOpts struct and creates a new network using the values
// provided. You must remember to provide a NetworkID value.
+//
+// NOTE: Currently the SecurityGroup option is not implemented to work with
+// Rackspace.
func Create(c *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.CreateResult {
return os.Create(c, opts)
}
diff --git a/rackspace/networking/v2/security/doc.go b/rackspace/networking/v2/security/doc.go
new file mode 100644
index 0000000..31f744c
--- /dev/null
+++ b/rackspace/networking/v2/security/doc.go
@@ -0,0 +1,32 @@
+// Package security contains functionality to work with security group and
+// security group rules Neutron resources.
+//
+// Security groups and security group rules allows administrators and tenants
+// the ability to specify the type of traffic and direction (ingress/egress)
+// that is allowed to pass through a port. A security group is a container for
+// security group rules.
+//
+// When a port is created in Networking it is associated with a security group.
+// If a security group is not specified the port is associated with a 'default'
+// security group. By default, this group drops all ingress traffic and allows
+// all egress. Rules can be added to this group in order to change the behaviour.
+//
+// The basic characteristics of Neutron Security Groups are:
+//
+// For ingress traffic (to an instance)
+// - Only traffic matched with security group rules are allowed.
+// - When there is no rule defined, all traffic is dropped.
+//
+// For egress traffic (from an instance)
+// - Only traffic matched with security group rules are allowed.
+// - When there is no rule defined, all egress traffic are dropped.
+// - When a new security group is created, rules to allow all egress traffic
+// is automatically added.
+//
+// "default security group" is defined for each tenant.
+// - For the default security group a rule which allows intercommunication
+// among hosts associated with the default security group is defined by default.
+// - As a result, all egress traffic and intercommunication in the default
+// group are allowed and all ingress from outside of the default group is
+// dropped by default (in the default security group).
+package security
diff --git a/rackspace/networking/v2/security/groups/delegate.go b/rackspace/networking/v2/security/groups/delegate.go
new file mode 100644
index 0000000..1e9a23a
--- /dev/null
+++ b/rackspace/networking/v2/security/groups/delegate.go
@@ -0,0 +1,30 @@
+package groups
+
+import (
+ "github.com/rackspace/gophercloud"
+ os "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// List returns a Pager which allows you to iterate over a collection of
+// security groups. It accepts a ListOpts struct, which allows you to filter
+// and sort the returned collection for greater efficiency.
+func List(c *gophercloud.ServiceClient, opts os.ListOpts) pagination.Pager {
+ return os.List(c, opts)
+}
+
+// Create is an operation which provisions a new security group with default
+// security group rules for the IPv4 and IPv6 ether types.
+func Create(c *gophercloud.ServiceClient, opts os.CreateOpts) os.CreateResult {
+ return os.Create(c, opts)
+}
+
+// Get retrieves a particular security group based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) os.GetResult {
+ return os.Get(c, id)
+}
+
+// Delete will permanently delete a particular security group based on its unique ID.
+func Delete(c *gophercloud.ServiceClient, id string) os.DeleteResult {
+ return os.Delete(c, id)
+}
diff --git a/rackspace/networking/v2/security/groups/delegate_test.go b/rackspace/networking/v2/security/groups/delegate_test.go
new file mode 100644
index 0000000..45cd3ba
--- /dev/null
+++ b/rackspace/networking/v2/security/groups/delegate_test.go
@@ -0,0 +1,206 @@
+package groups
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
+ osGroups "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups"
+ osRules "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules"
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/security-groups", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+ {
+ "security_groups": [
+ {
+ "description": "default",
+ "id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "name": "default",
+ "security_group_rules": [],
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ ]
+ }
+ `)
+ })
+
+ count := 0
+
+ List(fake.ServiceClient(), osGroups.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := osGroups.ExtractGroups(page)
+ if err != nil {
+ t.Errorf("Failed to extract secgroups: %v", err)
+ return false, err
+ }
+
+ expected := []osGroups.SecGroup{
+ osGroups.SecGroup{
+ Description: "default",
+ ID: "85cc3048-abc3-43cc-89b3-377341426ac5",
+ Name: "default",
+ Rules: []osRules.SecGroupRule{},
+ TenantID: "e4f50856753b4dc6afee5fa6b9b6c550",
+ },
+ }
+
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/security-groups", 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, `
+ {
+ "security_group": {
+ "name": "new-webservers",
+ "description": "security group for webservers"
+ }
+ }
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+ {
+ "security_group": {
+ "description": "security group for webservers",
+ "id": "2076db17-a522-4506-91de-c6dd8e837028",
+ "name": "new-webservers",
+ "security_group_rules": [
+ {
+ "direction": "egress",
+ "ethertype": "IPv4",
+ "id": "38ce2d8e-e8f1-48bd-83c2-d33cb9f50c3d",
+ "port_range_max": null,
+ "port_range_min": null,
+ "protocol": null,
+ "remote_group_id": null,
+ "remote_ip_prefix": null,
+ "security_group_id": "2076db17-a522-4506-91de-c6dd8e837028",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ },
+ {
+ "direction": "egress",
+ "ethertype": "IPv6",
+ "id": "565b9502-12de-4ffd-91e9-68885cff6ae1",
+ "port_range_max": null,
+ "port_range_min": null,
+ "protocol": null,
+ "remote_group_id": null,
+ "remote_ip_prefix": null,
+ "security_group_id": "2076db17-a522-4506-91de-c6dd8e837028",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ ],
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ }
+ `)
+ })
+
+ opts := osGroups.CreateOpts{Name: "new-webservers", Description: "security group for webservers"}
+ _, err := Create(fake.ServiceClient(), opts).Extract()
+ th.AssertNoErr(t, err)
+}
+
+func TestGet(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/security-groups/85cc3048-abc3-43cc-89b3-377341426ac5", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+ {
+ "security_group": {
+ "description": "default",
+ "id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "name": "default",
+ "security_group_rules": [
+ {
+ "direction": "egress",
+ "ethertype": "IPv6",
+ "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff",
+ "port_range_max": null,
+ "port_range_min": null,
+ "protocol": null,
+ "remote_group_id": null,
+ "remote_ip_prefix": null,
+ "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ },
+ {
+ "direction": "egress",
+ "ethertype": "IPv4",
+ "id": "93aa42e5-80db-4581-9391-3a608bd0e448",
+ "port_range_max": null,
+ "port_range_min": null,
+ "protocol": null,
+ "remote_group_id": null,
+ "remote_ip_prefix": null,
+ "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ ],
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ }
+ `)
+ })
+
+ sg, err := Get(fake.ServiceClient(), "85cc3048-abc3-43cc-89b3-377341426ac5").Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, "default", sg.Description)
+ th.AssertEquals(t, "85cc3048-abc3-43cc-89b3-377341426ac5", sg.ID)
+ th.AssertEquals(t, "default", sg.Name)
+ th.AssertEquals(t, 2, len(sg.Rules))
+ th.AssertEquals(t, "e4f50856753b4dc6afee5fa6b9b6c550", sg.TenantID)
+}
+
+func TestDelete(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/security-groups/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "DELETE")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ res := Delete(fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304")
+ th.AssertNoErr(t, res.Err)
+}
diff --git a/rackspace/networking/v2/security/rules/delegate.go b/rackspace/networking/v2/security/rules/delegate.go
new file mode 100644
index 0000000..23b4b31
--- /dev/null
+++ b/rackspace/networking/v2/security/rules/delegate.go
@@ -0,0 +1,30 @@
+package rules
+
+import (
+ "github.com/rackspace/gophercloud"
+ os "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// List returns a Pager which allows you to iterate over a collection of
+// security group rules. It accepts a ListOpts struct, which allows you to filter
+// and sort the returned collection for greater efficiency.
+func List(c *gophercloud.ServiceClient, opts os.ListOpts) pagination.Pager {
+ return os.List(c, opts)
+}
+
+// Create is an operation which provisions a new security group with default
+// security group rules for the IPv4 and IPv6 ether types.
+func Create(c *gophercloud.ServiceClient, opts os.CreateOpts) os.CreateResult {
+ return os.Create(c, opts)
+}
+
+// Get retrieves a particular security group based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) os.GetResult {
+ return os.Get(c, id)
+}
+
+// Delete will permanently delete a particular security group based on its unique ID.
+func Delete(c *gophercloud.ServiceClient, id string) os.DeleteResult {
+ return os.Delete(c, id)
+}
diff --git a/rackspace/networking/v2/security/rules/delegate_test.go b/rackspace/networking/v2/security/rules/delegate_test.go
new file mode 100644
index 0000000..3563fbe
--- /dev/null
+++ b/rackspace/networking/v2/security/rules/delegate_test.go
@@ -0,0 +1,236 @@
+package rules
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
+ osRules "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules"
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/security-group-rules", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+ {
+ "security_group_rules": [
+ {
+ "direction": "egress",
+ "ethertype": "IPv6",
+ "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff",
+ "port_range_max": null,
+ "port_range_min": null,
+ "protocol": null,
+ "remote_group_id": null,
+ "remote_ip_prefix": null,
+ "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ },
+ {
+ "direction": "egress",
+ "ethertype": "IPv4",
+ "id": "93aa42e5-80db-4581-9391-3a608bd0e448",
+ "port_range_max": null,
+ "port_range_min": null,
+ "protocol": null,
+ "remote_group_id": null,
+ "remote_ip_prefix": null,
+ "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ ]
+ }
+ `)
+ })
+
+ count := 0
+
+ List(fake.ServiceClient(), osRules.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := osRules.ExtractRules(page)
+ if err != nil {
+ t.Errorf("Failed to extract secrules: %v", err)
+ return false, err
+ }
+
+ expected := []osRules.SecGroupRule{
+ osRules.SecGroupRule{
+ Direction: "egress",
+ EtherType: "IPv6",
+ ID: "3c0e45ff-adaf-4124-b083-bf390e5482ff",
+ PortRangeMax: 0,
+ PortRangeMin: 0,
+ Protocol: "",
+ RemoteGroupID: "",
+ RemoteIPPrefix: "",
+ SecGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5",
+ TenantID: "e4f50856753b4dc6afee5fa6b9b6c550",
+ },
+ osRules.SecGroupRule{
+ Direction: "egress",
+ EtherType: "IPv4",
+ ID: "93aa42e5-80db-4581-9391-3a608bd0e448",
+ PortRangeMax: 0,
+ PortRangeMin: 0,
+ Protocol: "",
+ RemoteGroupID: "",
+ RemoteIPPrefix: "",
+ SecGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5",
+ TenantID: "e4f50856753b4dc6afee5fa6b9b6c550",
+ },
+ }
+
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/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.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+ {
+ "security_group_rule": {
+ "direction": "ingress",
+ "port_range_min": 80,
+ "ethertype": "IPv4",
+ "port_range_max": 80,
+ "protocol": "tcp",
+ "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a"
+ }
+ }
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+ {
+ "security_group_rule": {
+ "direction": "ingress",
+ "ethertype": "IPv4",
+ "id": "2bc0accf-312e-429a-956e-e4407625eb62",
+ "port_range_max": 80,
+ "port_range_min": 80,
+ "protocol": "tcp",
+ "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "remote_ip_prefix": null,
+ "security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ }
+ `)
+ })
+
+ opts := osRules.CreateOpts{
+ Direction: "ingress",
+ PortRangeMin: 80,
+ EtherType: "IPv4",
+ PortRangeMax: 80,
+ Protocol: "tcp",
+ RemoteGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5",
+ SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a",
+ }
+ _, err := Create(fake.ServiceClient(), opts).Extract()
+ th.AssertNoErr(t, err)
+}
+
+func TestRequiredCreateOpts(t *testing.T) {
+ res := Create(fake.ServiceClient(), osRules.CreateOpts{Direction: "something"})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), osRules.CreateOpts{Direction: osRules.DirIngress, EtherType: "something"})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), osRules.CreateOpts{Direction: osRules.DirIngress, EtherType: osRules.Ether4})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+ res = Create(fake.ServiceClient(), osRules.CreateOpts{Direction: osRules.DirIngress, EtherType: osRules.Ether4, SecGroupID: "something", Protocol: "foo"})
+ if res.Err == nil {
+ t.Fatalf("Expected error, got none")
+ }
+}
+
+func TestGet(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/security-group-rules/3c0e45ff-adaf-4124-b083-bf390e5482ff", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+ {
+ "security_group_rule": {
+ "direction": "egress",
+ "ethertype": "IPv6",
+ "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff",
+ "port_range_max": null,
+ "port_range_min": null,
+ "protocol": null,
+ "remote_group_id": null,
+ "remote_ip_prefix": null,
+ "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+ }
+ }
+ `)
+ })
+
+ sr, err := Get(fake.ServiceClient(), "3c0e45ff-adaf-4124-b083-bf390e5482ff").Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, "egress", sr.Direction)
+ th.AssertEquals(t, "IPv6", sr.EtherType)
+ th.AssertEquals(t, "3c0e45ff-adaf-4124-b083-bf390e5482ff", sr.ID)
+ th.AssertEquals(t, 0, sr.PortRangeMax)
+ th.AssertEquals(t, 0, sr.PortRangeMin)
+ th.AssertEquals(t, "", sr.Protocol)
+ th.AssertEquals(t, "", sr.RemoteGroupID)
+ th.AssertEquals(t, "", sr.RemoteIPPrefix)
+ th.AssertEquals(t, "85cc3048-abc3-43cc-89b3-377341426ac5", sr.SecGroupID)
+ th.AssertEquals(t, "e4f50856753b4dc6afee5fa6b9b6c550", sr.TenantID)
+}
+
+func TestDelete(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/security-group-rules/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "DELETE")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ res := Delete(fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304")
+ th.AssertNoErr(t, res.Err)
+}