Merge pull request #480 from timbyr/HostRoute

[rfr]Allow DNSNameservers and HostRoutes to be removed
diff --git a/openstack/networking/v2/extensions/security/groups/requests.go b/openstack/networking/v2/extensions/security/groups/requests.go
index b55fb5d..2712ac1 100644
--- a/openstack/networking/v2/extensions/security/groups/requests.go
+++ b/openstack/networking/v2/extensions/security/groups/requests.go
@@ -45,6 +45,9 @@
 	// Required. Human-readable name for the VIP. Does not have to be unique.
 	Name string
 
+	// Required for admins. Indicates the owner of the VIP.
+	TenantID string
+
 	// Optional. Describes the security group.
 	Description string
 }
@@ -62,6 +65,7 @@
 
 	type secgroup struct {
 		Name        string `json:"name"`
+		TenantID    string `json:"tenant_id,omitempty"`
 		Description string `json:"description,omitempty"`
 	}
 
@@ -71,6 +75,7 @@
 
 	reqBody := request{SecGroup: secgroup{
 		Name:        opts.Name,
+		TenantID:    opts.TenantID,
 		Description: opts.Description,
 	}}
 
diff --git a/openstack/networking/v2/extensions/security/rules/requests.go b/openstack/networking/v2/extensions/security/rules/requests.go
index 0b2d10b..a80ceb3 100644
--- a/openstack/networking/v2/extensions/security/rules/requests.go
+++ b/openstack/networking/v2/extensions/security/rules/requests.go
@@ -99,6 +99,9 @@
 	// attribute matches the specified IP prefix as the source IP address of the
 	// IP packet.
 	RemoteIPPrefix string
+
+	// Required for admins. Indicates the owner of the VIP.
+	TenantID string
 }
 
 // Create is an operation which provisions a new security group with default
@@ -133,6 +136,7 @@
 		Protocol       string `json:"protocol,omitempty"`
 		RemoteGroupID  string `json:"remote_group_id,omitempty"`
 		RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"`
+		TenantID       string `json:"tenant_id,omitempty"`
 	}
 
 	type request struct {
@@ -148,6 +152,7 @@
 		Protocol:       opts.Protocol,
 		RemoteGroupID:  opts.RemoteGroupID,
 		RemoteIPPrefix: opts.RemoteIPPrefix,
+		TenantID:       opts.TenantID,
 	}}
 
 	_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
diff --git a/openstack/networking/v2/subnets/results.go b/openstack/networking/v2/subnets/results.go
index 1910f17..77b956a 100644
--- a/openstack/networking/v2/subnets/results.go
+++ b/openstack/networking/v2/subnets/results.go
@@ -55,8 +55,8 @@
 // HostRoute represents a route that should be used by devices with IPs from
 // a subnet (not including local subnet route).
 type HostRoute struct {
-	DestinationCIDR string `json:"destination"`
-	NextHop         string `json:"nexthop"`
+	DestinationCIDR string `mapstructure:"destination" json:"destination"`
+	NextHop         string `mapstructure:"nexthop" json:"nexthop"`
 }
 
 // Subnet represents a subnet. See package documentation for a top-level
diff --git a/openstack/networking/v2/subnets/results_test.go b/openstack/networking/v2/subnets/results_test.go
new file mode 100644
index 0000000..d404838
--- /dev/null
+++ b/openstack/networking/v2/subnets/results_test.go
@@ -0,0 +1,54 @@
+package subnets
+
+import (
+	"encoding/json"
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+	"testing"
+)
+
+func TestHostRoute(t *testing.T) {
+	sejson := []byte(`
+    {"subnet": {
+      "name": "test-subnet",
+      "enable_dhcp": false,
+      "network_id": "3e66c41e-cbbd-4019-9aab-740b7e4150a0",
+      "tenant_id": "f86e123198cf42d19c8854c5f80c2f06",
+      "dns_nameservers": [],
+      "gateway_ip": "172.16.0.1",
+      "ipv6_ra_mode": null,
+      "allocation_pools": [
+        {
+          "start": "172.16.0.2",
+          "end": "172.16.255.254"
+        }
+      ],
+      "host_routes": [
+        {
+          "destination": "172.20.1.0/24",
+		  "nexthop": "172.16.0.2"
+        }
+      ],
+      "ip_version": 4,
+      "ipv6_address_mode": null,
+      "cidr": "172.16.0.0/16",
+      "id": "6dcaa873-7115-41af-9ef5-915f73636e43",
+      "subnetpool_id": null
+  }}
+`)
+
+	var dejson interface{}
+	err := json.Unmarshal(sejson, &dejson)
+	if err != nil {
+		t.Fatalf("%s", err)
+	}
+
+	resp := commonResult{gophercloud.Result{Body: dejson}}
+	subnet, err := resp.Extract()
+	if err != nil {
+		t.Fatalf("%s", err)
+	}
+	route := subnet.HostRoutes[0]
+	th.AssertEquals(t, route.NextHop, "172.16.0.2")
+	th.AssertEquals(t, route.DestinationCIDR, "172.20.1.0/24")
+}
diff --git a/openstack/orchestration/v1/stackresources/requests.go b/openstack/orchestration/v1/stackresources/requests.go
index ee9c3c2..fcb8d8a 100644
--- a/openstack/orchestration/v1/stackresources/requests.go
+++ b/openstack/orchestration/v1/stackresources/requests.go
@@ -25,12 +25,6 @@
 // ListOpts allows the filtering and sorting of paginated collections through
 // the API. Marker and Limit are used for pagination.
 type ListOpts struct {
-	// The stack resource ID with which to start the listing.
-	Marker string `q:"marker"`
-
-	// Integer value for the limit of values to return.
-	Limit int `q:"limit"`
-
 	// Include resources from nest stacks up to Depth levels of recursion.
 	Depth int `q:"nested_depth"`
 }
@@ -57,9 +51,7 @@
 	}
 
 	createPageFn := func(r pagination.PageResult) pagination.Page {
-		p := ResourcePage{pagination.MarkerPageBase{PageResult: r}}
-		p.MarkerPageBase.Owner = p
-		return p
+		return ResourcePage{pagination.SinglePageBase(r)}
 	}
 
 	return pagination.NewPager(client, url, createPageFn)
diff --git a/openstack/orchestration/v1/stackresources/results.go b/openstack/orchestration/v1/stackresources/results.go
index 69f21da..ea84db5 100644
--- a/openstack/orchestration/v1/stackresources/results.go
+++ b/openstack/orchestration/v1/stackresources/results.go
@@ -63,7 +63,7 @@
 // As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
 // data provided through the ExtractResources call.
 type ResourcePage struct {
-	pagination.MarkerPageBase
+	pagination.SinglePageBase
 }
 
 // IsEmpty returns true if a page contains no Server results.