Finalizing routers
diff --git a/openstack/networking/v2/extensions/layer3/routers/requests.go b/openstack/networking/v2/extensions/layer3/routers/requests.go
index 38ab4f4..670dc8f 100755
--- a/openstack/networking/v2/extensions/layer3/routers/requests.go
+++ b/openstack/networking/v2/extensions/layer3/routers/requests.go
@@ -3,6 +3,7 @@
 import (
 	"strconv"
 
+	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack/utils"
 	"github.com/rackspace/gophercloud/pagination"
@@ -68,3 +69,102 @@
 		return RouterPage{pagination.LinkedPageBase(r)}
 	})
 }
+
+type CreateOpts struct {
+	Name         string
+	AdminStateUp *bool
+	TenantID     string
+	GatewayInfo  *GatewayInfo
+}
+
+func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
+	type router struct {
+		Name         *string      `json:"name,omitempty"`
+		AdminStateUp *bool        `json:"admin_state_up,omitempty"`
+		TenantID     *string      `json:"tenant_id,omitempty"`
+		GatewayInfo  *GatewayInfo `json:"external_gateway_info,omitempty"`
+	}
+
+	type request struct {
+		Router router `json:"router"`
+	}
+
+	reqBody := request{Router: router{
+		Name:         gophercloud.MaybeString(opts.Name),
+		AdminStateUp: opts.AdminStateUp,
+		TenantID:     gophercloud.MaybeString(opts.TenantID),
+	}}
+
+	if opts.GatewayInfo != nil {
+		reqBody.Router.GatewayInfo = opts.GatewayInfo
+	}
+
+	var res CreateResult
+	_, err := perigee.Request("POST", rootURL(c), perigee.Options{
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		Results:     &res.Resp,
+		OkCodes:     []int{201},
+	})
+	res.Err = err
+	return res
+}
+
+func Get(c *gophercloud.ServiceClient, id string) GetResult {
+	var res GetResult
+	_, err := perigee.Request("GET", resourceURL(c, id), perigee.Options{
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		Results:     &res.Resp,
+		OkCodes:     []int{200},
+	})
+	res.Err = err
+	return res
+}
+
+type UpdateOpts struct {
+	Name         string
+	AdminStateUp *bool
+	GatewayInfo  *GatewayInfo
+}
+
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
+	type router struct {
+		Name         *string      `json:"name,omitempty"`
+		AdminStateUp *bool        `json:"admin_state_up,omitempty"`
+		GatewayInfo  *GatewayInfo `json:"external_gateway_info,omitempty"`
+	}
+
+	type request struct {
+		Router router `json:"router"`
+	}
+
+	reqBody := request{Router: router{
+		Name:         gophercloud.MaybeString(opts.Name),
+		AdminStateUp: opts.AdminStateUp,
+	}}
+
+	if opts.GatewayInfo != nil {
+		reqBody.Router.GatewayInfo = opts.GatewayInfo
+	}
+
+	// Send request to API
+	var res UpdateResult
+	_, err := perigee.Request("PUT", resourceURL(c, id), perigee.Options{
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		Results:     &res.Resp,
+		OkCodes:     []int{200},
+	})
+	res.Err = err
+	return res
+}
+
+func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
+	var res DeleteResult
+	_, err := perigee.Request("DELETE", resourceURL(c, id), perigee.Options{
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{204},
+	})
+	res.Err = err
+	return res
+}
diff --git a/openstack/networking/v2/extensions/layer3/routers/requests_test.go b/openstack/networking/v2/extensions/layer3/routers/requests_test.go
index 0078fc5..e005ed8 100755
--- a/openstack/networking/v2/extensions/layer3/routers/requests_test.go
+++ b/openstack/networking/v2/extensions/layer3/routers/requests_test.go
@@ -75,20 +75,20 @@
 
 		expected := []Router{
 			Router{
-				Status:         "ACTIVE",
-				ExtGatewayInfo: GatewayInfo{NetworkID: ""},
-				AdminStateUp:   true,
-				Name:           "second_routers",
-				ID:             "7177abc4-5ae9-4bb7-b0d4-89e94a4abf3b",
-				TenantID:       "6b96ff0cb17a4b859e1e575d221683d3",
+				Status:       "ACTIVE",
+				GatewayInfo:  GatewayInfo{NetworkID: ""},
+				AdminStateUp: true,
+				Name:         "second_routers",
+				ID:           "7177abc4-5ae9-4bb7-b0d4-89e94a4abf3b",
+				TenantID:     "6b96ff0cb17a4b859e1e575d221683d3",
 			},
 			Router{
-				Status:         "ACTIVE",
-				ExtGatewayInfo: GatewayInfo{NetworkID: "3c5bcddd-6af9-4e6b-9c3e-c153e521cab8"},
-				AdminStateUp:   true,
-				Name:           "router1",
-				ID:             "a9254bdb-2613-4a13-ac4c-adc581fba50d",
-				TenantID:       "33a40233088643acb66ff6eb0ebea679",
+				Status:       "ACTIVE",
+				GatewayInfo:  GatewayInfo{NetworkID: "3c5bcddd-6af9-4e6b-9c3e-c153e521cab8"},
+				AdminStateUp: true,
+				Name:         "router1",
+				ID:           "a9254bdb-2613-4a13-ac4c-adc581fba50d",
+				TenantID:     "33a40233088643acb66ff6eb0ebea679",
 			},
 		}
 
@@ -103,17 +103,158 @@
 }
 
 func TestCreate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
+	th.Mux.HandleFunc("/v2.0/routers", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", tokenID)
+		th.TestHeader(t, r, "Content-Type", "application/json")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestJSONRequest(t, r, `
+{
+   "router":{
+      "name": "foo_router",
+      "admin_state_up": false,
+      "external_gateway_info":{
+         "network_id":"8ca37218-28ff-41cb-9b10-039601ea7e6b"
+      }
+   }
+}
+			`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusCreated)
+
+		fmt.Fprintf(w, `
+{
+    "router": {
+        "status": "ACTIVE",
+        "external_gateway_info": {
+            "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b"
+        },
+        "name": "foo_router",
+        "admin_state_up": false,
+        "tenant_id": "6b96ff0cb17a4b859e1e575d221683d3",
+        "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e"
+    }
+}
+		`)
+	})
+
+	asu := false
+	gwi := GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"}
+
+	options := CreateOpts{
+		Name:         "foo_router",
+		AdminStateUp: &asu,
+		GatewayInfo:  &gwi,
+	}
+	r, err := Create(serviceClient(), options).Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, "foo_router", r.Name)
+	th.AssertEquals(t, false, r.AdminStateUp)
+	th.AssertDeepEquals(t, GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"}, r.GatewayInfo)
 }
 
 func TestGet(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
+	th.Mux.HandleFunc("/v2.0/routers/a07eea83-7710-4860-931b-5fe220fae533", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", tokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+    "router": {
+        "status": "ACTIVE",
+        "external_gateway_info": {
+            "network_id": "85d76829-6415-48ff-9c63-5c5ca8c61ac6"
+        },
+        "name": "router1",
+        "admin_state_up": true,
+        "tenant_id": "d6554fe62e2f41efbb6e026fad5c1542",
+        "id": "a07eea83-7710-4860-931b-5fe220fae533"
+    }
+}
+			`)
+	})
+
+	n, err := Get(serviceClient(), "a07eea83-7710-4860-931b-5fe220fae533").Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, n.Status, "ACTIVE")
+	th.AssertDeepEquals(t, n.GatewayInfo, GatewayInfo{NetworkID: "85d76829-6415-48ff-9c63-5c5ca8c61ac6"})
+	th.AssertEquals(t, n.Name, "router1")
+	th.AssertEquals(t, n.AdminStateUp, true)
+	th.AssertEquals(t, n.TenantID, "d6554fe62e2f41efbb6e026fad5c1542")
+	th.AssertEquals(t, n.ID, "a07eea83-7710-4860-931b-5fe220fae533")
 }
 
 func TestUpdate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
+	th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", tokenID)
+		th.TestHeader(t, r, "Content-Type", "application/json")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestJSONRequest(t, r, `
+{
+    "router": {
+			"name": "new_name",
+        "external_gateway_info": {
+            "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b"
+        }
+    }
+}
+			`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+    "router": {
+        "status": "ACTIVE",
+        "external_gateway_info": {
+            "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b"
+        },
+        "name": "new_name",
+        "admin_state_up": true,
+        "tenant_id": "6b96ff0cb17a4b859e1e575d221683d3",
+        "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e"
+    }
+}
+		`)
+	})
+
+	gwi := GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"}
+	options := UpdateOpts{Name: "new_name", GatewayInfo: &gwi}
+
+	n, err := Update(serviceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, n.Name, "new_name")
+	th.AssertDeepEquals(t, n.GatewayInfo, GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"})
 }
 
 func TestDelete(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
+	th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", tokenID)
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	res := Delete(serviceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c")
+	th.AssertNoErr(t, res.Err)
 }
diff --git a/openstack/networking/v2/extensions/layer3/routers/results.go b/openstack/networking/v2/extensions/layer3/routers/results.go
index cdacb5b..8b2e5ea 100755
--- a/openstack/networking/v2/extensions/layer3/routers/results.go
+++ b/openstack/networking/v2/extensions/layer3/routers/results.go
@@ -1,7 +1,10 @@
 package routers
 
 import (
+	"fmt"
+
 	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
 )
 
@@ -10,12 +13,12 @@
 }
 
 type Router struct {
-	Status         string      `json:"status" mapstructure:"status"`
-	ExtGatewayInfo GatewayInfo `json:"external_gateway_info" mapstructure:"external_gateway_info"`
-	AdminStateUp   bool        `json:"admin_state_up" mapstructure:"admin_state_up"`
-	Name           string      `json:"name" mapstructure:"name"`
-	ID             string      `json:"id" mapstructure:"id"`
-	TenantID       string      `json:"tenant_id" mapstructure:"tenant_id"`
+	Status       string      `json:"status" mapstructure:"status"`
+	GatewayInfo  GatewayInfo `json:"external_gateway_info" mapstructure:"external_gateway_info"`
+	AdminStateUp bool        `json:"admin_state_up" mapstructure:"admin_state_up"`
+	Name         string      `json:"name" mapstructure:"name"`
+	ID           string      `json:"id" mapstructure:"id"`
+	TenantID     string      `json:"tenant_id" mapstructure:"tenant_id"`
 }
 
 type RouterPage struct {
@@ -70,3 +73,42 @@
 
 	return resp.Routers, nil
 }
+
+type commonResult struct {
+	gophercloud.CommonResult
+}
+
+// Extract is a function that accepts a result and extracts a network resource.
+func (r commonResult) Extract() (*Router, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Router *Router `json:"router"`
+	}
+
+	err := mapstructure.Decode(r.Resp, &res)
+	if err != nil {
+		return nil, fmt.Errorf("Error decoding Neutron router: %v", err)
+	}
+
+	return res.Router, nil
+}
+
+// CreateResult represents the result of a create operation.
+type CreateResult struct {
+	commonResult
+}
+
+// GetResult represents the result of a get operation.
+type GetResult struct {
+	commonResult
+}
+
+// UpdateResult represents the result of an update operation.
+type UpdateResult struct {
+	commonResult
+}
+
+type DeleteResult commonResult