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/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 {