virtual interfaces operations & unit tests; networks unit tests
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)
+}