Feature/filestorage sharenetworks list (#124)

* sfs: Add list for share networks

* sfs: Add acceptance tests for share network List

* sfs: Add more fields for filtering List requests

* sfs: Add pagination for share network List

* sfs: Change pagination to use MarkerPage

* sfs: Add acceptance tests for share network pagination
diff --git a/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go b/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go
index 1f75e90..b0aefd8 100644
--- a/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go
+++ b/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go
@@ -55,6 +55,6 @@
 	t.Logf("IP version: %d", sharenetwork.IPVersion)
 	t.Logf("Name: %s", sharenetwork.Name)
 	t.Logf("Description: %s", sharenetwork.Description)
-	t.Logf("Created at: %s", sharenetwork.CreatedAt)
-	t.Logf("Updated at: %s", sharenetwork.UpdatedAt)
+	t.Logf("Created at: %v", sharenetwork.CreatedAt)
+	t.Logf("Updated at: %v", sharenetwork.UpdatedAt)
 }
diff --git a/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go b/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go
index e654251..d7d712b 100644
--- a/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go
+++ b/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go
@@ -4,6 +4,8 @@
 	"testing"
 
 	"github.com/gophercloud/gophercloud/acceptance/clients"
+	"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks"
+	"github.com/gophercloud/gophercloud/pagination"
 )
 
 func TestShareNetworkCreateDestroy(t *testing.T) {
@@ -21,3 +23,106 @@
 
 	defer DeleteShareNetwork(t, client, shareNetwork)
 }
+
+func TestShareNetworkListDetail(t *testing.T) {
+	client, err := clients.NewSharedFileSystemV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a shared file system client: %v", err)
+	}
+
+	allPages, err := sharenetworks.ListDetail(client, sharenetworks.ListOpts{}).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to retrieve share networks: %v", err)
+	}
+
+	allShareNetworks, err := sharenetworks.ExtractShareNetworks(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract share networks: %v", err)
+	}
+
+	for _, shareNetwork := range allShareNetworks {
+		PrintShareNetwork(t, &shareNetwork)
+	}
+}
+
+// The test creates 2 shared networks and verifies that only the one(s) with
+// a particular name are being listed
+func TestShareNetworkListFiltering(t *testing.T) {
+	client, err := clients.NewSharedFileSystemV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a shared file system client: %v", err)
+	}
+
+	shareNetwork, err := CreateShareNetwork(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create share network: %v", err)
+	}
+	defer DeleteShareNetwork(t, client, shareNetwork)
+
+	shareNetwork, err = CreateShareNetwork(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create share network: %v", err)
+	}
+	defer DeleteShareNetwork(t, client, shareNetwork)
+
+	options := sharenetworks.ListOpts{
+		Name: shareNetwork.Name,
+	}
+
+	allPages, err := sharenetworks.ListDetail(client, options).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to retrieve share networks: %v", err)
+	}
+
+	allShareNetworks, err := sharenetworks.ExtractShareNetworks(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract share networks: %v", err)
+	}
+
+	for _, listedShareNetwork := range allShareNetworks {
+		if listedShareNetwork.Name != shareNetwork.Name {
+			t.Fatalf("The name of the share network was expected to be %s", shareNetwork.Name)
+		}
+		PrintShareNetwork(t, &listedShareNetwork)
+	}
+}
+
+func TestShareNetworkListPagination(t *testing.T) {
+	client, err := clients.NewSharedFileSystemV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create a shared file system client: %v", err)
+	}
+
+	shareNetwork, err := CreateShareNetwork(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create share network: %v", err)
+	}
+	defer DeleteShareNetwork(t, client, shareNetwork)
+
+	shareNetwork, err = CreateShareNetwork(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create share network: %v", err)
+	}
+	defer DeleteShareNetwork(t, client, shareNetwork)
+
+	count := 0
+
+	err = sharenetworks.ListDetail(client, sharenetworks.ListOpts{Offset: 0, Limit: 1}).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		_, err := sharenetworks.ExtractShareNetworks(page)
+		if err != nil {
+			t.Fatalf("Failed to extract share networks: %v", err)
+			return false, err
+		}
+
+		return true, nil
+	})
+	if err != nil {
+		t.Fatalf("Unable to retrieve share networks: %v", err)
+	}
+
+	if count < 2 {
+		t.Fatal("Expected to get at least 2 pages")
+	}
+
+}
diff --git a/openstack/sharedfilesystems/v2/sharenetworks/requests.go b/openstack/sharedfilesystems/v2/sharenetworks/requests.go
index ad29404..0c7c5d3 100644
--- a/openstack/sharedfilesystems/v2/sharenetworks/requests.go
+++ b/openstack/sharedfilesystems/v2/sharenetworks/requests.go
@@ -1,6 +1,9 @@
 package sharenetworks
 
-import "github.com/gophercloud/gophercloud"
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
 
 // CreateOptsBuilder allows extensions to add additional parameters to the
 // Create request.
@@ -50,3 +53,66 @@
 	_, r.Err = client.Delete(deleteURL(client, id), nil)
 	return
 }
+
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+	ToShareNetworkListQuery() (string, error)
+}
+
+// ListOpts holds options for listing ShareNetworks. It is passed to the
+// sharenetworks.List function.
+type ListOpts struct {
+	// admin-only option. Set it to true to see all tenant share networks.
+	AllTenants bool `q:"all_tenants"`
+	// The UUID of the project where the share network was created
+	ProjectID string `q:"project_id"`
+	// The neutron network ID
+	NeutronNetID string `q:"neutron_net_id"`
+	// The neutron subnet ID
+	NeutronSubnetID string `q:"neutron_subnet_id"`
+	// The nova network ID
+	NovaNetID string `q:"nova_net_id"`
+	// The network type. A valid value is VLAN, VXLAN, GRE or flat
+	NetworkType string `q:"network_type"`
+	// The Share Network name
+	Name string `q:"name"`
+	// The Share Network description
+	Description string `q:"description"`
+	// The Share Network IP version
+	IPVersion gophercloud.IPVersion `q:"ip_version"`
+	// The Share Network segmentation ID
+	SegmentationID int `q:"segmentation_id"`
+	// List all share networks created after the given date
+	CreatedSince string `q:"created_since"`
+	// List all share networks created before the given date
+	CreatedBefore string `q:"created_before"`
+	// Limit specifies the page size.
+	Limit int `q:"limit"`
+	// Limit specifies the page number.
+	Offset int `q:"offset"`
+}
+
+// ToShareNetworkListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToShareNetworkListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	return q.String(), err
+}
+
+// ListDetail returns ShareNetworks optionally limited by the conditions provided in ListOpts.
+func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listDetailURL(client)
+	if opts != nil {
+		query, err := opts.ToShareNetworkListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
+		p := ShareNetworkPage{pagination.MarkerPageBase{PageResult: r}}
+		p.MarkerPageBase.Owner = p
+		return p
+	})
+}
diff --git a/openstack/sharedfilesystems/v2/sharenetworks/results.go b/openstack/sharedfilesystems/v2/sharenetworks/results.go
index 946ddd8..8086508 100644
--- a/openstack/sharedfilesystems/v2/sharenetworks/results.go
+++ b/openstack/sharedfilesystems/v2/sharenetworks/results.go
@@ -1,6 +1,12 @@
 package sharenetworks
 
-import "github.com/gophercloud/gophercloud"
+import (
+	"net/url"
+	"strconv"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
 
 // ShareNetwork contains all the information associated with an OpenStack
 // ShareNetwork.
@@ -28,15 +34,91 @@
 	// The Share Network description
 	Description string `json:"description"`
 	// The date and time stamp when the Share Network was created
-	CreatedAt string `json:"created_at"`
+	CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
 	// The date and time stamp when the Share Network was updated
-	UpdatedAt string `json:"updated_at"`
+	UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
 }
 
 type commonResult struct {
 	gophercloud.Result
 }
 
+// ShareNetworkPage is a pagination.pager that is returned from a call to the List function.
+type ShareNetworkPage struct {
+	pagination.MarkerPageBase
+}
+
+// NextPageURL generates the URL for the page of results after this one.
+func (r ShareNetworkPage) NextPageURL() (string, error) {
+	currentURL := r.URL
+	mark, err := r.Owner.LastMarker()
+	if err != nil {
+		return "", err
+	}
+
+	q := currentURL.Query()
+	q.Set("offset", mark)
+	currentURL.RawQuery = q.Encode()
+	return currentURL.String(), nil
+}
+
+// LastMarker returns the last offset in a ListResult.
+func (r ShareNetworkPage) LastMarker() (string, error) {
+	maxInt := strconv.Itoa(int(^uint(0) >> 1))
+	shareNetworks, err := ExtractShareNetworks(r)
+	if err != nil {
+		return maxInt, err
+	}
+	if len(shareNetworks) == 0 {
+		return maxInt, nil
+	}
+
+	u, err := url.Parse(r.URL.String())
+	if err != nil {
+		return maxInt, err
+	}
+	queryParams := u.Query()
+	offset := queryParams.Get("offset")
+	limit := queryParams.Get("limit")
+
+	// Limit is not present, only one page required
+	if limit == "" {
+		return maxInt, nil
+	}
+
+	iOffset := 0
+	if offset != "" {
+		iOffset, err = strconv.Atoi(offset)
+		if err != nil {
+			return maxInt, err
+		}
+	}
+	iLimit, err := strconv.Atoi(limit)
+	if err != nil {
+		return maxInt, err
+	}
+	iOffset = iOffset + iLimit
+	offset = strconv.Itoa(iOffset)
+
+	return offset, nil
+}
+
+// IsEmpty satisifies the IsEmpty method of the Page interface
+func (r ShareNetworkPage) IsEmpty() (bool, error) {
+	shareNetworks, err := ExtractShareNetworks(r)
+	return len(shareNetworks) == 0, err
+}
+
+// ExtractShareNetworks extracts and returns ShareNetworks. It is used while
+// iterating over a sharenetworks.List call.
+func ExtractShareNetworks(r pagination.Page) ([]ShareNetwork, error) {
+	var s struct {
+		ShareNetworks []ShareNetwork `json:"share_networks"`
+	}
+	err := (r.(ShareNetworkPage)).ExtractInto(&s)
+	return s.ShareNetworks, err
+}
+
 // Extract will get the ShareNetwork object out of the commonResult object.
 func (r commonResult) Extract() (*ShareNetwork, error) {
 	var s struct {
diff --git a/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures.go b/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures.go
index a014e5e..a99bb9a 100644
--- a/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures.go
+++ b/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures.go
@@ -11,34 +11,34 @@
 
 func createReq(name, description, network, subnetwork string) string {
 	return fmt.Sprintf(`{
-		"share_network": {
-			"name": "%s",
-			"description": "%s",
-			"neutron_net_id": "%s",
-			"neutron_subnet_id": "%s"
-		}
-	}`, name, description, network, subnetwork)
+        "share_network": {
+            "name": "%s",
+            "description": "%s",
+            "neutron_net_id": "%s",
+            "neutron_subnet_id": "%s"
+        }
+    }`, name, description, network, subnetwork)
 }
 
 func createResp(name, description, network, subnetwork string) string {
 	return fmt.Sprintf(`
-	{
-		"share_network": {
-			"name": "%s",
-			"description": "%s",
-			"segmentation_id": null,
-			"created_at": "2015-09-07T14:37:00.583656",
-			"updated_at": null,
-			"id": "77eb3421-4549-4789-ac39-0d5185d68c29",
-			"neutron_net_id": "%s",
-			"neutron_subnet_id": "%s",
-			"ip_version": null,
-			"nova_net_id": null,
-			"cidr": null,
-			"project_id": "e10a683c20da41248cfd5e1ab3d88c62",
-			"network_type": null
-		}
-	}`, name, description, network, subnetwork)
+    {
+        "share_network": {
+            "name": "%s",
+            "description": "%s",
+            "segmentation_id": null,
+            "created_at": "2015-09-07T14:37:00.583656",
+            "updated_at": null,
+            "id": "77eb3421-4549-4789-ac39-0d5185d68c29",
+            "neutron_net_id": "%s",
+            "neutron_subnet_id": "%s",
+            "ip_version": null,
+            "nova_net_id": null,
+            "cidr": null,
+            "project_id": "e10a683c20da41248cfd5e1ab3d88c62",
+            "network_type": null
+        }
+    }`, name, description, network, subnetwork)
 }
 
 func MockCreateResponse(t *testing.T) {
@@ -69,3 +69,157 @@
 		w.WriteHeader(http.StatusAccepted)
 	})
 }
+
+func MockListResponse(t *testing.T) {
+	th.Mux.HandleFunc("/share-networks/detail", 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)
+
+		r.ParseForm()
+		marker := r.Form.Get("offset")
+
+		switch marker {
+		case "":
+			fmt.Fprintf(w, `{
+            "share_networks": [
+                {
+                    "name": "net_my1",
+                    "segmentation_id": null,
+                    "created_at": "2015-09-04T14:57:13.000000",
+                    "neutron_subnet_id": "53482b62-2c84-4a53-b6ab-30d9d9800d06",
+                    "updated_at": null,
+                    "id": "32763294-e3d4-456a-998d-60047677c2fb",
+                    "neutron_net_id": "998b42ee-2cee-4d36-8b95-67b5ca1f2109",
+                    "ip_version": null,
+                    "nova_net_id": null,
+                    "cidr": null,
+                    "project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
+                    "network_type": null,
+                    "description": "descr"
+                },
+                {
+                    "name": "net_my",
+                    "segmentation_id": null,
+                    "created_at": "2015-09-04T14:54:25.000000",
+                    "neutron_subnet_id": "53482b62-2c84-4a53-b6ab-30d9d9800d06",
+                    "updated_at": null,
+                    "id": "713df749-aac0-4a54-af52-10f6c991e80c",
+                    "neutron_net_id": "998b42ee-2cee-4d36-8b95-67b5ca1f2109",
+                    "ip_version": null,
+                    "nova_net_id": null,
+                    "cidr": null,
+                    "project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
+                    "network_type": null,
+                    "description": "desecr"
+                },
+                {
+                    "name": null,
+                    "segmentation_id": null,
+                    "created_at": "2015-09-04T14:51:41.000000",
+                    "neutron_subnet_id": null,
+                    "updated_at": null,
+                    "id": "fa158a3d-6d9f-4187-9ca5-abbb82646eb2",
+                    "neutron_net_id": null,
+                    "ip_version": null,
+                    "nova_net_id": null,
+                    "cidr": null,
+                    "project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
+                    "network_type": null,
+                    "description": null
+                }
+            ]
+        }`)
+		default:
+			fmt.Fprintf(w, `
+				{
+					"share_networks": []
+				}`)
+		}
+	})
+}
+
+func MockFilteredListResponse(t *testing.T) {
+	th.Mux.HandleFunc("/share-networks/detail", 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)
+
+		r.ParseForm()
+		marker := r.Form.Get("offset")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, `
+				{
+					"share_networks": [
+						{
+							"name": "net_my1",
+							"segmentation_id": null,
+							"created_at": "2015-09-04T14:57:13.000000",
+							"neutron_subnet_id": "53482b62-2c84-4a53-b6ab-30d9d9800d06",
+							"updated_at": null,
+							"id": "32763294-e3d4-456a-998d-60047677c2fb",
+							"neutron_net_id": "998b42ee-2cee-4d36-8b95-67b5ca1f2109",
+							"ip_version": null,
+							"nova_net_id": null,
+							"cidr": null,
+							"project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
+							"network_type": null,
+							"description": "descr"
+						}
+					]
+				}`)
+		case "1":
+			fmt.Fprintf(w, `
+				{
+					"share_networks": [
+						{
+							"name": "net_my1",
+							"segmentation_id": null,
+							"created_at": "2015-09-04T14:57:13.000000",
+							"neutron_subnet_id": "53482b62-2c84-4a53-b6ab-30d9d9800d06",
+							"updated_at": null,
+							"id": "32763294-e3d4-456a-998d-60047677c2fb",
+							"neutron_net_id": "998b42ee-2cee-4d36-8b95-67b5ca1f2109",
+							"ip_version": null,
+							"nova_net_id": null,
+							"cidr": null,
+							"project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
+							"network_type": null,
+							"description": "descr"
+						}
+					]
+				}`)
+		case "2":
+			fmt.Fprintf(w, `
+				{
+					"share_networks": [
+						{
+							"name": "net_my1",
+							"segmentation_id": null,
+							"created_at": "2015-09-04T14:57:13.000000",
+							"neutron_subnet_id": "53482b62-2c84-4a53-b6ab-30d9d9800d06",
+							"updated_at": null,
+							"id": "32763294-e3d4-456a-998d-60047677c2fb",
+							"neutron_net_id": "998b42ee-2cee-4d36-8b95-67b5ca1f2109",
+							"ip_version": null,
+							"nova_net_id": null,
+							"cidr": null,
+							"project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
+							"network_type": null,
+							"description": "descr"
+						}
+					]
+				}`)
+		default:
+			fmt.Fprintf(w, `
+				{
+					"share_networks": []
+				}`)
+		}
+	})
+}
diff --git a/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go b/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go
index 15baf0d..f13f1da 100644
--- a/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go
+++ b/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go
@@ -2,8 +2,11 @@
 
 import (
 	"testing"
+	"time"
 
+	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks"
+	"github.com/gophercloud/gophercloud/pagination"
 	th "github.com/gophercloud/gophercloud/testhelper"
 	"github.com/gophercloud/gophercloud/testhelper/client"
 )
@@ -41,3 +44,97 @@
 	res := sharenetworks.Delete(client.ServiceClient(), "fa158a3d-6d9f-4187-9ca5-abbb82646eb2")
 	th.AssertNoErr(t, res.Err)
 }
+
+// Verifies that share networks can be listed correctly
+func TestListDetail(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	MockListResponse(t)
+
+	allPages, err := sharenetworks.ListDetail(client.ServiceClient(), &sharenetworks.ListOpts{}).AllPages()
+
+	th.AssertNoErr(t, err)
+	actual, err := sharenetworks.ExtractShareNetworks(allPages)
+	th.AssertNoErr(t, err)
+
+	var nilTime time.Time
+	expected := []sharenetworks.ShareNetwork{
+		{
+			ID:              "32763294-e3d4-456a-998d-60047677c2fb",
+			Name:            "net_my1",
+			CreatedAt:       gophercloud.JSONRFC3339MilliNoZ(time.Date(2015, 9, 4, 14, 57, 13, 0, time.UTC)),
+			Description:     "descr",
+			NetworkType:     "",
+			CIDR:            "",
+			NovaNetID:       "",
+			NeutronNetID:    "998b42ee-2cee-4d36-8b95-67b5ca1f2109",
+			NeutronSubnetID: "53482b62-2c84-4a53-b6ab-30d9d9800d06",
+			IPVersion:       0,
+			SegmentationID:  0,
+			UpdatedAt:       gophercloud.JSONRFC3339MilliNoZ(nilTime),
+			ProjectID:       "16e1ab15c35a457e9c2b2aa189f544e1",
+		},
+		{
+			ID:              "713df749-aac0-4a54-af52-10f6c991e80c",
+			Name:            "net_my",
+			CreatedAt:       gophercloud.JSONRFC3339MilliNoZ(time.Date(2015, 9, 4, 14, 54, 25, 0, time.UTC)),
+			Description:     "desecr",
+			NetworkType:     "",
+			CIDR:            "",
+			NovaNetID:       "",
+			NeutronNetID:    "998b42ee-2cee-4d36-8b95-67b5ca1f2109",
+			NeutronSubnetID: "53482b62-2c84-4a53-b6ab-30d9d9800d06",
+			IPVersion:       0,
+			SegmentationID:  0,
+			UpdatedAt:       gophercloud.JSONRFC3339MilliNoZ(nilTime),
+			ProjectID:       "16e1ab15c35a457e9c2b2aa189f544e1",
+		},
+		{
+			ID:              "fa158a3d-6d9f-4187-9ca5-abbb82646eb2",
+			Name:            "",
+			CreatedAt:       gophercloud.JSONRFC3339MilliNoZ(time.Date(2015, 9, 4, 14, 51, 41, 0, time.UTC)),
+			Description:     "",
+			NetworkType:     "",
+			CIDR:            "",
+			NovaNetID:       "",
+			NeutronNetID:    "",
+			NeutronSubnetID: "",
+			IPVersion:       0,
+			SegmentationID:  0,
+			UpdatedAt:       gophercloud.JSONRFC3339MilliNoZ(nilTime),
+			ProjectID:       "16e1ab15c35a457e9c2b2aa189f544e1",
+		},
+	}
+
+	th.CheckDeepEquals(t, expected, actual)
+}
+
+// Verifies that share networks list can be called with query parameters
+func TestPaginatedListDetail(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	MockFilteredListResponse(t)
+
+	options := &sharenetworks.ListOpts{
+		Offset: 0,
+		Limit:  1,
+	}
+
+	count := 0
+
+	err := sharenetworks.ListDetail(client.ServiceClient(), options).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		_, err := sharenetworks.ExtractShareNetworks(page)
+		if err != nil {
+			t.Errorf("Failed to extract share networks: %v", err)
+			return false, err
+		}
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, count, 3)
+}
diff --git a/openstack/sharedfilesystems/v2/sharenetworks/urls.go b/openstack/sharedfilesystems/v2/sharenetworks/urls.go
index 7cd6c73..464c17a 100644
--- a/openstack/sharedfilesystems/v2/sharenetworks/urls.go
+++ b/openstack/sharedfilesystems/v2/sharenetworks/urls.go
@@ -9,3 +9,7 @@
 func deleteURL(c *gophercloud.ServiceClient, id string) string {
 	return c.ServiceURL("share-networks", id)
 }
+
+func listDetailURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL("share-networks", "detail")
+}