first attempt
diff --git a/openstack/networking/v2/extensions/portsbinding/fixtures.go b/openstack/networking/v2/extensions/portsbinding/fixtures.go
new file mode 100644
index 0000000..9f7bd08
--- /dev/null
+++ b/openstack/networking/v2/extensions/portsbinding/fixtures.go
@@ -0,0 +1,206 @@
+package portsbinding
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func HandleListSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/v2.0/ports", 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, `
+{
+ "ports": [
+ {
+ "status": "ACTIVE",
+ "binding:host_id": "devstack",
+ "name": "",
+ "admin_state_up": true,
+ "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3",
+ "tenant_id": "",
+ "device_owner": "network:router_gateway",
+ "mac_address": "fa:16:3e:58:42:ed",
+ "fixed_ips": [
+ {
+ "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062",
+ "ip_address": "172.24.4.2"
+ }
+ ],
+ "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b",
+ "security_groups": [],
+ "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
+ "binding:vnic_type": "normal"
+ }
+ ]
+}
+ `)
+ })
+}
+
+func HandleGet(t *testing.T) {
+ th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", 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, `
+{
+ "port": {
+ "status": "ACTIVE",
+ "binding:host_id": "devstack",
+ "name": "",
+ "allowed_address_pairs": [],
+ "admin_state_up": true,
+ "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+ "tenant_id": "7e02058126cc4950b75f9970368ba177",
+ "extra_dhcp_opts": [],
+ "binding:vif_details": {
+ "port_filter": true,
+ "ovs_hybrid_plug": true
+ },
+ "binding:vif_type": "ovs",
+ "device_owner": "network:router_interface",
+ "port_security_enabled": false,
+ "mac_address": "fa:16:3e:23:fd:d7",
+ "binding:profile": {},
+ "binding:vnic_type": "normal",
+ "fixed_ips": [
+ {
+ "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+ "ip_address": "10.0.0.1"
+ }
+ ],
+ "id": "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2",
+ "security_groups": [],
+ "device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e"
+ }
+}
+ `)
+ })
+}
+
+func HandleCreate(t *testing.T) {
+ th.Mux.HandleFunc("/v2.0/ports", 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, `
+{
+ "port": {
+ "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+ "name": "private-port",
+ "admin_state_up": true,
+ "fixed_ips": [
+ {
+ "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+ "ip_address": "10.0.0.2"
+ }
+ ],
+ "security_groups": ["foo"],
+ "binding:host_id": "HOST1",
+ "binding:vnic_type": "normal"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "port": {
+ "status": "DOWN",
+ "name": "private-port",
+ "allowed_address_pairs": [],
+ "admin_state_up": true,
+ "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+ "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+ "device_owner": "",
+ "mac_address": "fa:16:3e:c9:cb:f0",
+ "fixed_ips": [
+ {
+ "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+ "ip_address": "10.0.0.2"
+ }
+ ],
+ "binding:host_id": "HOST1",
+ "binding:vnic_type": "normal",
+ "id": "65c0ee9f-d634-4522-8954-51021b570b0d",
+ "security_groups": [
+ "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
+ ],
+ "device_id": ""
+ }
+}
+ `)
+ })
+}
+
+func HandleUpdate(t *testing.T) {
+ th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ 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, `
+{
+ "port": {
+ "name": "new_port_name",
+ "fixed_ips": [
+ {
+ "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+ "ip_address": "10.0.0.3"
+ }
+ ],
+ "security_groups": [
+ "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
+ ],
+ "binding:host_id": "HOST1",
+ "binding:vnic_type": "normal"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "port": {
+ "status": "DOWN",
+ "name": "new_port_name",
+ "admin_state_up": true,
+ "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+ "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+ "device_owner": "",
+ "mac_address": "fa:16:3e:c9:cb:f0",
+ "fixed_ips": [
+ {
+ "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+ "ip_address": "10.0.0.3"
+ }
+ ],
+ "id": "65c0ee9f-d634-4522-8954-51021b570b0d",
+ "security_groups": [
+ "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
+ ],
+ "device_id": "",
+ "binding:host_id": "HOST1",
+ "binding:vnic_type": "normal"
+ }
+}
+ `)
+ })
+}
diff --git a/openstack/networking/v2/extensions/portsbinding/requests.go b/openstack/networking/v2/extensions/portsbinding/requests.go
index 7105a49..4d4300a 100644
--- a/openstack/networking/v2/extensions/portsbinding/requests.go
+++ b/openstack/networking/v2/extensions/portsbinding/requests.go
@@ -5,6 +5,13 @@
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
)
+// Get retrieves a specific port based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) GetResult {
+ var res GetResult
+ _, res.Err = c.Get(getURL(c, id), &res.Body, nil)
+ return res
+}
+
// CreateOpts represents the attributes used when creating a new
// port with extended attributes.
type CreateOpts struct {
@@ -22,13 +29,6 @@
Profile map[string]string
}
-// Get retrieves a specific port based on its unique ID.
-func Get(c *gophercloud.ServiceClient, id string) GetResult {
- var res GetResult
- _, res.Err = c.Get(getURL(c, id), &res.Body, nil)
- return res
-}
-
// ToPortCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) {
p, err := opts.CreateOptsBuilder.ToPortCreateMap()
@@ -68,10 +68,18 @@
// UpdateOpts represents the attributes used when updating an existing port.
type UpdateOpts struct {
+ // UpdateOptsBuilder is the interface options structs have to satisfy in order
+ // to be used in the main Update operation in this package.
ports.UpdateOptsBuilder
- HostID string
+ // The ID of the host where the port is allocated
+ HostID string
+ // The virtual network interface card (vNIC) type that is bound to the
+ // neutron port
VNICType string
- Profile map[string]string
+ // A dictionary that enables the application running on the specified
+ // host to pass and receive virtual network interface (VIF) port-specific
+ // information to the plug-in
+ Profile map[string]string
}
// ToPortUpdateMap casts an UpdateOpts struct to a map.
diff --git a/openstack/networking/v2/extensions/portsbinding/requests_test.go b/openstack/networking/v2/extensions/portsbinding/requests_test.go
index 87592a6..43a001f 100644
--- a/openstack/networking/v2/extensions/portsbinding/requests_test.go
+++ b/openstack/networking/v2/extensions/portsbinding/requests_test.go
@@ -2,50 +2,68 @@
import (
"fmt"
- "net/http"
"testing"
fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
+ "github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleListSuccessfully(t)
+
+ count := 0
+
+ ports.List(fake.ServiceClient(), ports.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractPorts(page)
+ th.AssertNoErr(t, err)
+
+ expected := []Port{
+ Port{
+ Port: ports.Port{
+ Status: "ACTIVE",
+ Name: "",
+ AdminStateUp: true,
+ NetworkID: "70c1db1f-b701-45bd-96e0-a313ee3430b3",
+ TenantID: "",
+ DeviceOwner: "network:router_gateway",
+ MACAddress: "fa:16:3e:58:42:ed",
+ FixedIPs: []ports.IP{
+ ports.IP{
+ SubnetID: "008ba151-0b8c-4a67-98b5-0d2b87666062",
+ IPAddress: "172.24.4.2",
+ },
+ },
+ ID: "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b",
+ SecurityGroups: []string{},
+ DeviceID: "9ae135f4-b6e0-4dad-9e91-3c223e385824",
+ },
+ VNICType: "normal",
+ },
+ }
+
+ fmt.Printf("%#v", actual)
+
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
+
func TestGet(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
- th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", 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, `
-{
- "port": {
- "status": "ACTIVE",
- "name": "",
- "admin_state_up": true,
- "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
- "tenant_id": "7e02058126cc4950b75f9970368ba177",
- "device_owner": "network:router_interface",
- "mac_address": "fa:16:3e:23:fd:d7",
- "fixed_ips": [
- {
- "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
- "ip_address": "10.0.0.1"
- }
- ],
- "id": "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2",
- "security_groups": [],
- "device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e",
- "binding:host_id": "HOST1",
- "binding:vnic_type": "normal"
- }
-}
- `)
- })
+ HandleGet(t)
n, err := Get(fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").Extract()
th.AssertNoErr(t, err)
@@ -57,7 +75,7 @@
th.AssertEquals(t, n.TenantID, "7e02058126cc4950b75f9970368ba177")
th.AssertEquals(t, n.DeviceOwner, "network:router_interface")
th.AssertEquals(t, n.MACAddress, "fa:16:3e:23:fd:d7")
- th.AssertDeepEquals(t, n.FixedIPs, []IP{
+ th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"},
})
th.AssertEquals(t, n.ID, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2")
@@ -71,61 +89,7 @@
th.SetupHTTP()
defer th.TeardownHTTP()
- th.Mux.HandleFunc("/v2.0/ports", 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, `
-{
- "port": {
- "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
- "name": "private-port",
- "admin_state_up": true,
- "fixed_ips": [
- {
- "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
- "ip_address": "10.0.0.2"
- }
- ],
- "security_groups": ["foo"],
- "binding:host_id": "HOST1",
- "binding:vnic_type": "normal"
- }
-}
- `)
-
- w.Header().Add("Content-Type", "application/json")
- w.WriteHeader(http.StatusCreated)
-
- fmt.Fprintf(w, `
-{
- "port": {
- "status": "DOWN",
- "name": "private-port",
- "allowed_address_pairs": [],
- "admin_state_up": true,
- "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
- "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
- "device_owner": "",
- "mac_address": "fa:16:3e:c9:cb:f0",
- "fixed_ips": [
- {
- "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
- "ip_address": "10.0.0.2"
- }
- ],
- "binding:host_id": "HOST1",
- "binding:vnic_type": "normal",
- "id": "65c0ee9f-d634-4522-8954-51021b570b0d",
- "security_groups": [
- "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
- ],
- "device_id": ""
- }
-}
- `)
- })
+ HandleCreate(t)
asu := true
options := CreateOpts{
@@ -133,7 +97,7 @@
Name: "private-port",
AdminStateUp: &asu,
NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7",
- FixedIPs: []IP{
+ FixedIPs: []ports.IP{
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
},
SecurityGroups: []string{"foo"},
@@ -151,7 +115,7 @@
th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
th.AssertEquals(t, n.DeviceOwner, "")
th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0")
- th.AssertDeepEquals(t, n.FixedIPs, []IP{
+ th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
})
th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
@@ -171,65 +135,12 @@
th.SetupHTTP()
defer th.TeardownHTTP()
- th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
- th.TestMethod(t, r, "PUT")
- 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, `
-{
- "port": {
- "name": "new_port_name",
- "fixed_ips": [
- {
- "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
- "ip_address": "10.0.0.3"
- }
- ],
- "security_groups": [
- "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
- ],
- "binding:host_id": "HOST1",
- "binding:vnic_type": "normal"
- }
-}
- `)
-
- w.Header().Add("Content-Type", "application/json")
- w.WriteHeader(http.StatusOK)
-
- fmt.Fprintf(w, `
-{
- "port": {
- "status": "DOWN",
- "name": "new_port_name",
- "admin_state_up": true,
- "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
- "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
- "device_owner": "",
- "mac_address": "fa:16:3e:c9:cb:f0",
- "fixed_ips": [
- {
- "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
- "ip_address": "10.0.0.3"
- }
- ],
- "id": "65c0ee9f-d634-4522-8954-51021b570b0d",
- "security_groups": [
- "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
- ],
- "device_id": "",
- "binding:host_id": "HOST1",
- "binding:vnic_type": "normal"
- }
-}
- `)
- })
+ HandleUpdate(t)
options := UpdateOpts{
UpdateOptsBuilder: ports.UpdateOpts{
Name: "new_port_name",
- FixedIPs: []IP{
+ FixedIPs: []ports.IP{
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
},
SecurityGroups: []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"},
@@ -242,7 +153,7 @@
th.AssertNoErr(t, err)
th.AssertEquals(t, s.Name, "new_port_name")
- th.AssertDeepEquals(t, s.FixedIPs, []IP{
+ th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
})
th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
diff --git a/openstack/networking/v2/extensions/portsbinding/results.go b/openstack/networking/v2/extensions/portsbinding/results.go
index 57379eb..7a0d1c3 100644
--- a/openstack/networking/v2/extensions/portsbinding/results.go
+++ b/openstack/networking/v2/extensions/portsbinding/results.go
@@ -3,6 +3,9 @@
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
+
+ "github.com/rackspace/gophercloud/openstack/networking/v2/ports"
+ "github.com/rackspace/gophercloud/pagination"
)
type commonResult struct {
@@ -48,32 +51,14 @@
// Port represents a Neutron port. See package documentation for a top-level
// description of what this is.
type Port struct {
- // UUID for the port.
- ID string `mapstructure:"id" json:"id"`
- // Network that this port is associated with.
- NetworkID string `mapstructure:"network_id" json:"network_id"`
- // Human-readable name for the port. Might not be unique.
- Name string `mapstructure:"name" json:"name"`
- // Administrative state of port. If false (down), port does not forward packets.
- AdminStateUp bool `mapstructure:"admin_state_up" json:"admin_state_up"`
- // Indicates whether network is currently operational. Possible values include
- // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional values.
- Status string `mapstructure:"status" json:"status"`
- // Mac address to use on this port.
- MACAddress string `mapstructure:"mac_address" json:"mac_address"`
- // Specifies IP addresses for the port thus associating the port itself with
- // the subnets where the IP addresses are picked from
- FixedIPs []IP `mapstructure:"fixed_ips" json:"fixed_ips"`
- // Owner of network. Only admin users can specify a tenant_id other than its own.
- TenantID string `mapstructure:"tenant_id" json:"tenant_id"`
- // Identifies the entity (e.g.: dhcp agent) using this port.
- DeviceOwner string `mapstructure:"device_owner" json:"device_owner"`
- // Specifies the IDs of any security groups associated with a port.
- SecurityGroups []string `mapstructure:"security_groups" json:"security_groups"`
- // Identifies the device (e.g., virtual server) using this port.
- DeviceID string `mapstructure:"device_id" json:"device_id"`
+ ports.Port
// The ID of the host where the port is allocated
HostID string `mapstructure:"binding:host_id" json:"binding:host_id"`
+ // A dictionary that enables the application to pass information about
+ // functions that the Networking API provides.
+ VIFDetails map[string]string `mapstructure:"binding:vif_details" json:"binding:vif_details"`
+ // The VIF type for the port.
+ VIFType string `mapstructure:"binding:vif_type" json:"binding:vif_type"`
// The virtual network interface card (vNIC) type that is bound to the
// neutron port
VNICType string `mapstructure:"binding:vnic_type" json:"binding:vnic_type"`
@@ -82,3 +67,15 @@
// information to the plug-in
Profile map[string]string `mapstructure:"binding:profile" json:"binding:profile"`
}
+
+// ExtractPorts accepts a Page struct, specifically a PortPage struct,
+// and extracts the elements into a slice of Port structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractPorts(page pagination.Page) ([]Port, error) {
+ var resp struct {
+ Ports []Port `mapstructure:"ports" json:"ports"`
+ }
+
+ err := mapstructure.Decode(page.(ports.PortPage).Body, &resp)
+ return resp.Ports, err
+}