virtual interfaces operations & unit tests; networks unit tests
diff --git a/rackspace/networking/v2/networks/delegate.go b/rackspace/networking/v2/networks/delegate.go
deleted file mode 100644
index e040d58..0000000
--- a/rackspace/networking/v2/networks/delegate.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package networks
-
-import (
- "github.com/rackspace/gophercloud"
- os "github.com/rackspace/gophercloud/openstack/networking/v2/networks"
-)
-
-// Delete accepts a unique ID and deletes the network associated with it.
-func Delete(c *gophercloud.ServiceClient, id string) os.DeleteResult {
- return os.Delete(c, id)
-}
diff --git a/rackspace/networking/v2/networks/delegate_test.go b/rackspace/networking/v2/networks/delegate_test.go
deleted file mode 100644
index 83c4a6a..0000000
--- a/rackspace/networking/v2/networks/delegate_test.go
+++ /dev/null
@@ -1 +0,0 @@
-package networks
diff --git a/rackspace/networking/v2/networks/requests.go b/rackspace/networking/v2/networks/requests.go
index f7896b8..f55aa6c 100644
--- a/rackspace/networking/v2/networks/requests.go
+++ b/rackspace/networking/v2/networks/requests.go
@@ -1,33 +1,34 @@
package networks
import (
- "errors"
+ "errors"
- "github.com/rackspace/gophercloud"
- "github.com/rackspace/gophercloud/pagination"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
- "github.com/racker/perigee"
+ "github.com/racker/perigee"
)
// List returns a Pager which allows you to iterate over a collection of
// networks. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
func List(c *gophercloud.ServiceClient) pagination.Pager {
- url := listURL(c)
- return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
- return NetworkPage{pagination.SinglePageBase{PageResult: r}}
- })
+ createPage := func(r pagination.PageResult) pagination.Page {
+ return NetworkPage{pagination.SinglePageBase(r)}
+ }
+
+ return pagination.NewPager(c, listURL(c), createPage)
}
// Get retrieves a specific network based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult {
- var res GetResult
- _, res.Err = perigee.Request("GET", getURL(c, id), perigee.Options{
- MoreHeaders: c.Provider.AuthenticatedHeaders(),
- Results: &res.Body,
- OkCodes: []int{200},
- })
- return res
+ var res GetResult
+ _, res.Err = perigee.Request("GET", getURL(c, id), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+ return res
}
// CreateOptsBuilder is the interface options structs have to satisfy in order
@@ -35,32 +36,32 @@
// 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 {
- ToNetworkCreateMap() (map[string]interface{}, error)
+ ToNetworkCreateMap() (map[string]interface{}, error)
}
// CreateOpts is the common options struct used in this package's Create
// operation.
-type CreateOpts struct{
- // REQUIRED. See Network object for more info.
- CIDR string
- // REQUIRED. See Network object for more info.
- Label string
+type CreateOpts struct {
+ // REQUIRED. See Network object for more info.
+ CIDR string
+ // REQUIRED. See Network object for more info.
+ Label string
}
// ToNetworkCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) {
- n := make(map[string]interface{})
+ n := make(map[string]interface{})
- if opts.CIDR == "" {
- return nil, errors.New("Required field CIDR not set.")
- }
- if opts.Label == "" {
- return nil, errors.New("Required field Label not set.")
- }
+ if opts.CIDR == "" {
+ return nil, errors.New("Required field CIDR not set.")
+ }
+ if opts.Label == "" {
+ return nil, errors.New("Required field Label not set.")
+ }
- n["label"] = opts.Label
- n["cidr"] = opts.CIDR
- return map[string]interface{}{"network": n}, nil
+ n["label"] = opts.Label
+ n["cidr"] = opts.CIDR
+ return map[string]interface{}{"network": n}, nil
}
// Create accepts a CreateOpts struct and creates a new network using the values
@@ -71,20 +72,30 @@
// network. An admin user, however, has the option of specifying another tenant
// ID in the CreateOpts struct.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
- var res CreateResult
+ var res CreateResult
- reqBody, err := opts.ToNetworkCreateMap()
- if err != nil {
- res.Err = err
- return res
- }
+ reqBody, err := opts.ToNetworkCreateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
- // Send request to API
- _, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
- MoreHeaders: c.Provider.AuthenticatedHeaders(),
- ReqBody: &reqBody,
- Results: &res.Body,
- OkCodes: []int{201},
- })
- return res
+ // Send request to API
+ _, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res.Body,
+ OkCodes: []int{201, 202},
+ })
+ return res
+}
+
+// Delete accepts a unique ID and deletes the network associated with it.
+func Delete(c *gophercloud.ServiceClient, networkID string) DeleteResult {
+ var res DeleteResult
+ _, res.Err = perigee.Request("DELETE", deleteURL(c, networkID), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ OkCodes: []int{204},
+ })
+ return res
}
diff --git a/rackspace/networking/v2/networks/requests_test.go b/rackspace/networking/v2/networks/requests_test.go
index 83c4a6a..441c450 100644
--- a/rackspace/networking/v2/networks/requests_test.go
+++ b/rackspace/networking/v2/networks/requests_test.go
@@ -1 +1,160 @@
package networks
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/os-networksv2", 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, `
+{
+ "networks": [
+ {
+ "label": "test-network-1",
+ "cidr": "192.168.100.0/24",
+ "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+ },
+ {
+ "label": "test-network-2",
+ "cidr": "192.30.250.00/18",
+ "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324"
+ }
+ ]
+}
+ `)
+ })
+
+ client := fake.ServiceClient()
+ count := 0
+
+ List(client).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractNetworks(page)
+ if err != nil {
+ t.Errorf("Failed to extract networks: %v", err)
+ return false, err
+ }
+
+ expected := []Network{
+ Network{
+ Label: "test-network-1",
+ CIDR: "192.168.100.0/24",
+ ID: "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+ },
+ Network{
+ Label: "test-network-2",
+ CIDR: "192.30.250.00/18",
+ ID: "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+ },
+ }
+
+ 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("/os-networksv2/d32019d3-bc6e-4319-9c1d-6722fc136a22", 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, `
+{
+ "network": {
+ "label": "test-network-1",
+ "cidr": "192.168.100.0/24",
+ "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+ }
+}
+ `)
+ })
+
+ n, err := Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, n.CIDR, "192.168.100.0/24")
+ th.AssertEquals(t, n.Label, "test-network-1")
+ th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/os-networksv2", 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, `
+{
+ "network": {
+ "label": "test-network-1",
+ "cidr": "192.168.100.0/24"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "network": {
+ "label": "test-network-1",
+ "cidr": "192.168.100.0/24",
+ "id": "4e8e5957-649f-477b-9e5b-f1f75b21c03c"
+ }
+}
+ `)
+ })
+
+ options := CreateOpts{Label: "test-network-1", CIDR: "192.168.100.0/24"}
+ n, err := Create(fake.ServiceClient(), options).Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, n.Label, "test-network-1")
+ th.AssertEquals(t, n.ID, "4e8e5957-649f-477b-9e5b-f1f75b21c03c")
+}
+
+func TestDelete(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/os-networksv2/4e8e5957-649f-477b-9e5b-f1f75b21c03c", 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)
+ })
+
+ s := fake.ServiceClient()
+ t.Logf("fake.serviceclient: %+v\n", s)
+ res := Delete(s, "4e8e5957-649f-477b-9e5b-f1f75b21c03c")
+ th.AssertNoErr(t, res.Err)
+}
diff --git a/rackspace/networking/v2/networks/results.go b/rackspace/networking/v2/networks/results.go
index 2334faf..823c56e 100644
--- a/rackspace/networking/v2/networks/results.go
+++ b/rackspace/networking/v2/networks/results.go
@@ -1,38 +1,38 @@
package networks
import (
- "github.com/mitchellh/mapstructure"
- "github.com/rackspace/gophercloud"
- "github.com/rackspace/gophercloud/pagination"
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
)
type commonResult struct {
- gophercloud.Result
+ gophercloud.Result
}
// Extract is a function that accepts a result and extracts a network resource.
func (r commonResult) Extract() (*Network, error) {
- if r.Err != nil {
- return nil, r.Err
- }
+ if r.Err != nil {
+ return nil, r.Err
+ }
- var res struct {
- Network *Network `json:"network"`
- }
+ var res struct {
+ Network *Network `json:"network"`
+ }
- err := mapstructure.Decode(r.Body, &res)
+ err := mapstructure.Decode(r.Body, &res)
- return res.Network, err
+ return res.Network, err
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
- commonResult
+ commonResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
- commonResult
+ commonResult
}
// DeleteResult represents the result of a delete operation.
@@ -40,31 +40,40 @@
// Network represents, well, a network.
type Network struct {
- // UUID for the network
- ID string `mapstructure:"id" json:"id"`
+ // UUID for the network
+ ID string `mapstructure:"id" json:"id"`
- // Human-readable name for the network. Might not be unique.
- Label string `mapstructure:"label" json:"label"`
+ // Human-readable name for the network. Might not be unique.
+ Label string `mapstructure:"label" json:"label"`
- // Classless Inter-Domain Routing
- CIDR string `mapstructure:"cidr" json:"cidr"`
+ // Classless Inter-Domain Routing
+ CIDR string `mapstructure:"cidr" json:"cidr"`
}
// NetworkPage is the page returned by a pager when traversing over a
// collection of networks.
type NetworkPage struct {
- pagination.SinglePageBase
+ pagination.SinglePageBase
+}
+
+// IsEmpty returns true if the NetworkPage contains no Networks.
+func (r NetworkPage) IsEmpty() (bool, error) {
+ networks, err := ExtractNetworks(r)
+ if err != nil {
+ return true, err
+ }
+ return len(networks) == 0, nil
}
// ExtractNetworks accepts a Page struct, specifically a NetworkPage struct,
// and extracts the elements into a slice of Network structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractNetworks(page pagination.Page) ([]Network, error) {
- var resp struct {
- Networks []Network `mapstructure:"networks" json:"networks"`
- }
+ var resp struct {
+ Networks []Network `mapstructure:"networks" json:"networks"`
+ }
- err := mapstructure.Decode(page.(NetworkPage).Body, &resp)
+ err := mapstructure.Decode(page.(NetworkPage).Body, &resp)
- return resp.Networks, err
+ return resp.Networks, err
}
diff --git a/rackspace/networking/v2/networks/urls.go b/rackspace/networking/v2/networks/urls.go
index 65d843b..19a21aa 100644
--- a/rackspace/networking/v2/networks/urls.go
+++ b/rackspace/networking/v2/networks/urls.go
@@ -3,25 +3,25 @@
import "github.com/rackspace/gophercloud"
func resourceURL(c *gophercloud.ServiceClient, id string) string {
- return c.ServiceURL("os-networksv2", id)
+ return c.ServiceURL("os-networksv2", id)
}
func rootURL(c *gophercloud.ServiceClient) string {
- return c.ServiceURL("networks")
+ return c.ServiceURL("os-networksv2")
}
func getURL(c *gophercloud.ServiceClient, id string) string {
- return resourceURL(c, id)
+ return resourceURL(c, id)
}
func listURL(c *gophercloud.ServiceClient) string {
- return rootURL(c)
+ return rootURL(c)
}
func createURL(c *gophercloud.ServiceClient) string {
- return rootURL(c)
+ return rootURL(c)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
- return resourceURL(c, id)
+ return resourceURL(c, id)
}
diff --git a/rackspace/networking/v2/networks/urls_test.go b/rackspace/networking/v2/networks/urls_test.go
index 2e064b0..983992e 100644
--- a/rackspace/networking/v2/networks/urls_test.go
+++ b/rackspace/networking/v2/networks/urls_test.go
@@ -1,38 +1,38 @@
package networks
import (
- "testing"
+ "testing"
- "github.com/rackspace/gophercloud"
- th "github.com/rackspace/gophercloud/testhelper"
+ "github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
)
-const endpoint = "http://localhost:57909"
+const endpoint = "http://localhost:57909/"
func endpointClient() *gophercloud.ServiceClient {
- return &gophercloud.ServiceClient{Endpoint: endpoint}
+ return &gophercloud.ServiceClient{Endpoint: endpoint}
}
func TestGetURL(t *testing.T) {
- actual := getURL(endpointClient(), "foo")
- expected := endpoint + "os-networksv2/foo"
- th.AssertEquals(t, expected, actual)
+ actual := getURL(endpointClient(), "foo")
+ expected := endpoint + "os-networksv2/foo"
+ th.AssertEquals(t, expected, actual)
}
func TestCreateURL(t *testing.T) {
- actual := createURL(endpointClient())
- expected := endpoint + "os-networksv2"
- th.AssertEquals(t, expected, actual)
+ actual := createURL(endpointClient())
+ expected := endpoint + "os-networksv2"
+ th.AssertEquals(t, expected, actual)
}
func TestListURL(t *testing.T) {
- actual := createURL(endpointClient())
- expected := endpoint + "os-networksv2"
- th.AssertEquals(t, expected, actual)
+ actual := createURL(endpointClient())
+ expected := endpoint + "os-networksv2"
+ th.AssertEquals(t, expected, actual)
}
func TestDeleteURL(t *testing.T) {
- actual := deleteURL(endpointClient(), "foo")
- expected := endpoint + "os-networksv2/foo"
- th.AssertEquals(t, expected, actual)
+ actual := deleteURL(endpointClient(), "foo")
+ expected := endpoint + "os-networksv2/foo"
+ th.AssertEquals(t, expected, actual)
}
diff --git a/rackspace/networking/v2/virtualinterfaces/requests.go b/rackspace/networking/v2/virtualinterfaces/requests.go
index 4f91359..adb3184 100644
--- a/rackspace/networking/v2/virtualinterfaces/requests.go
+++ b/rackspace/networking/v2/virtualinterfaces/requests.go
@@ -1 +1,51 @@
package virtualinterfaces
+
+import (
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+
+ "github.com/racker/perigee"
+)
+
+// List returns a Pager which allows you to iterate over a collection of
+// networks. It accepts a ListOpts struct, which allows you to filter and sort
+// the returned collection for greater efficiency.
+func List(c *gophercloud.ServiceClient, instanceID string) pagination.Pager {
+ createPage := func(r pagination.PageResult) pagination.Page {
+ return VirtualInterfacePage{pagination.SinglePageBase(r)}
+ }
+
+ return pagination.NewPager(c, listURL(c, instanceID), createPage)
+}
+
+// Create creates a new virtual interface for a network and attaches the network
+// to the server instance.
+func Create(c *gophercloud.ServiceClient, instanceID, networkID string) CreateResult {
+ var res CreateResult
+
+ reqBody := map[string]map[string]string{
+ "virtual_interface": {
+ "network_id": networkID,
+ },
+ }
+
+ // Send request to API
+ _, res.Err = perigee.Request("POST", createURL(c, instanceID), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res.Body,
+ OkCodes: []int{201, 202},
+ })
+ return res
+}
+
+// Delete deletes the interface with interfaceID attached to the instance with
+// instanceID.
+func Delete(c *gophercloud.ServiceClient, instanceID, interfaceID string) DeleteResult {
+ var res DeleteResult
+ _, res.Err = perigee.Request("DELETE", deleteURL(c, instanceID, interfaceID), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ OkCodes: []int{204},
+ })
+ return res
+}
diff --git a/rackspace/networking/v2/virtualinterfaces/requests_test.go b/rackspace/networking/v2/virtualinterfaces/requests_test.go
index 4f91359..c3487d2 100644
--- a/rackspace/networking/v2/virtualinterfaces/requests_test.go
+++ b/rackspace/networking/v2/virtualinterfaces/requests_test.go
@@ -1 +1,167 @@
package virtualinterfaces
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/servers/12345/os-virtual-interfacesv2", 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, `
+{
+ "virtual_interfaces": [
+ {
+ "id": "de7c6d53-b895-4b4a-963c-517ccb0f0775",
+ "ip_addresses": [
+ {
+ "address": "192.168.0.2",
+ "network_id": "f212726e-6321-4210-9bae-a13f5a33f83f",
+ "network_label": "superprivate_xml"
+ }
+ ],
+ "mac_address": "BC:76:4E:04:85:20"
+ },
+ {
+ "id": "e14e789d-3b98-44a6-9c2d-c23eb1d1465c",
+ "ip_addresses": [
+ {
+ "address": "10.181.1.30",
+ "network_id": "3b324a1b-31b8-4db5-9fe5-4a2067f60297",
+ "network_label": "private"
+ }
+ ],
+ "mac_address": "BC:76:4E:04:81:55"
+ }
+ ]
+}
+ `)
+ })
+
+ client := fake.ServiceClient()
+ count := 0
+
+ List(client, "12345").EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractVirtualInterfaces(page)
+ if err != nil {
+ t.Errorf("Failed to extract networks: %v", err)
+ return false, err
+ }
+
+ expected := []VirtualInterface{
+ VirtualInterface{
+ MACAddress: "BC:76:4E:04:85:20",
+ IPAddresses: []IPAddress{
+ IPAddress{
+ Address: "192.168.0.2",
+ NetworkID: "f212726e-6321-4210-9bae-a13f5a33f83f",
+ NetworkLabel: "superprivate_xml",
+ },
+ },
+ ID: "de7c6d53-b895-4b4a-963c-517ccb0f0775",
+ },
+ VirtualInterface{
+ MACAddress: "BC:76:4E:04:81:55",
+ IPAddresses: []IPAddress{
+ IPAddress{
+ Address: "10.181.1.30",
+ NetworkID: "3b324a1b-31b8-4db5-9fe5-4a2067f60297",
+ NetworkLabel: "private",
+ },
+ },
+ ID: "e14e789d-3b98-44a6-9c2d-c23eb1d1465c",
+ },
+ }
+
+ 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("/servers/12345/os-virtual-interfacesv2", 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, `
+{
+ "virtual_interface": {
+ "network_id": "6789"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `{
+ "virtual_interfaces": [
+ {
+ "id": "de7c6d53-b895-4b4a-963c-517ccb0f0775",
+ "ip_addresses": [
+ {
+ "address": "192.168.0.2",
+ "network_id": "f212726e-6321-4210-9bae-a13f5a33f83f",
+ "network_label": "superprivate_xml"
+ }
+ ],
+ "mac_address": "BC:76:4E:04:85:20"
+ }
+ ]
+ }`)
+ })
+
+ expected := &VirtualInterface{
+ MACAddress: "BC:76:4E:04:85:20",
+ IPAddresses: []IPAddress{
+ IPAddress{
+ Address: "192.168.0.2",
+ NetworkID: "f212726e-6321-4210-9bae-a13f5a33f83f",
+ NetworkLabel: "superprivate_xml",
+ },
+ },
+ ID: "de7c6d53-b895-4b4a-963c-517ccb0f0775",
+ }
+
+ actual, err := Create(fake.ServiceClient(), "12345", "6789").Extract()
+ th.AssertNoErr(t, err)
+
+ th.CheckDeepEquals(t, expected, actual)
+}
+
+func TestDelete(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/servers/12345/os-virtual-interfacesv2/6789", 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(), "12345", "6789")
+ th.AssertNoErr(t, res.Err)
+}
diff --git a/rackspace/networking/v2/virtualinterfaces/results.go b/rackspace/networking/v2/virtualinterfaces/results.go
index 4f91359..6818fcb 100644
--- a/rackspace/networking/v2/virtualinterfaces/results.go
+++ b/rackspace/networking/v2/virtualinterfaces/results.go
@@ -1 +1,79 @@
package virtualinterfaces
+
+import (
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+type commonResult struct {
+ gophercloud.Result
+}
+
+// Extract is a function that accepts a result and extracts a network resource.
+func (r commonResult) Extract() (*VirtualInterface, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ VirtualInterfaces []VirtualInterface `mapstructure:"virtual_interfaces" json:"virtual_interfaces"`
+ }
+
+ err := mapstructure.Decode(r.Body, &res)
+
+ return &res.VirtualInterfaces[0], err
+}
+
+// CreateResult represents the result of a create operation.
+type CreateResult struct {
+ commonResult
+}
+
+// DeleteResult represents the result of a delete operation.
+type DeleteResult commonResult
+
+// IPAddress represents a vitual address attached to a VirtualInterface.
+type IPAddress struct {
+ Address string `mapstructure:"address" json:"address"`
+ NetworkID string `mapstructure:"network_id" json:"network_id"`
+ NetworkLabel string `mapstructure:"network_label" json:"network_label"`
+}
+
+// VirtualInterface represents a virtual interface.
+type VirtualInterface struct {
+ // UUID for the virtual interface
+ ID string `mapstructure:"id" json:"id"`
+
+ MACAddress string `mapstructure:"mac_address" json:"mac_address"`
+
+ IPAddresses []IPAddress `mapstructure:"ip_addresses" json:"ip_addresses"`
+}
+
+// VirtualInterfacePage is the page returned by a pager when traversing over a
+// collection of virtual interfaces.
+type VirtualInterfacePage struct {
+ pagination.SinglePageBase
+}
+
+// IsEmpty returns true if the NetworkPage contains no Networks.
+func (r VirtualInterfacePage) IsEmpty() (bool, error) {
+ networks, err := ExtractVirtualInterfaces(r)
+ if err != nil {
+ return true, err
+ }
+ return len(networks) == 0, nil
+}
+
+// ExtractVirtualInterfaces accepts a Page struct, specifically a VirtualInterfacePage struct,
+// and extracts the elements into a slice of VirtualInterface structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractVirtualInterfaces(page pagination.Page) ([]VirtualInterface, error) {
+ var resp struct {
+ VirtualInterfaces []VirtualInterface `mapstructure:"virtual_interfaces" json:"virtual_interfaces"`
+ }
+
+ err := mapstructure.Decode(page.(VirtualInterfacePage).Body, &resp)
+
+ return resp.VirtualInterfaces, err
+}
diff --git a/rackspace/networking/v2/virtualinterfaces/urls.go b/rackspace/networking/v2/virtualinterfaces/urls.go
index 4f91359..9e5693e 100644
--- a/rackspace/networking/v2/virtualinterfaces/urls.go
+++ b/rackspace/networking/v2/virtualinterfaces/urls.go
@@ -1 +1,15 @@
package virtualinterfaces
+
+import "github.com/rackspace/gophercloud"
+
+func listURL(c *gophercloud.ServiceClient, instanceID string) string {
+ return c.ServiceURL("servers", instanceID, "os-virtual-interfacesv2")
+}
+
+func createURL(c *gophercloud.ServiceClient, instanceID string) string {
+ return c.ServiceURL("servers", instanceID, "os-virtual-interfacesv2")
+}
+
+func deleteURL(c *gophercloud.ServiceClient, instanceID, interfaceID string) string {
+ return c.ServiceURL("servers", instanceID, "os-virtual-interfacesv2", interfaceID)
+}
diff --git a/rackspace/networking/v2/virtualinterfaces/urls_test.go b/rackspace/networking/v2/virtualinterfaces/urls_test.go
index 4f91359..6732e4e 100644
--- a/rackspace/networking/v2/virtualinterfaces/urls_test.go
+++ b/rackspace/networking/v2/virtualinterfaces/urls_test.go
@@ -1 +1,32 @@
package virtualinterfaces
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestCreateURL(t *testing.T) {
+ actual := createURL(endpointClient(), "12345")
+ expected := endpoint + "servers/12345/os-virtual-interfacesv2"
+ th.AssertEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+ actual := createURL(endpointClient(), "12345")
+ expected := endpoint + "servers/12345/os-virtual-interfacesv2"
+ th.AssertEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+ actual := deleteURL(endpointClient(), "12345", "6789")
+ expected := endpoint + "servers/12345/os-virtual-interfacesv2/6789"
+ th.AssertEquals(t, expected, actual)
+}