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")
+}