Skip to content
This repository has been archived by the owner on Mar 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #752 from vpandey-RH/filter-volumes-by-metadata
Browse files Browse the repository at this point in the history
filter volume list/info on basis of metadata keys and values
  • Loading branch information
prashanthpai authored May 17, 2018
2 parents f6f8e2f + 71cf1a8 commit bb32f32
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 12 deletions.
37 changes: 37 additions & 0 deletions e2e/volume_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ func testVolumeCreate(t *testing.T) {
},
},
},
Metadata: map[string]string{
"owner": "gd2test",
},
Force: true,
}
_, err := client.VolumeCreate(createReq)
Expand Down Expand Up @@ -148,6 +151,40 @@ func testVolumeStop(t *testing.T) {

func testVolumeList(t *testing.T) {
r := require.New(t)
var matchingQueries []map[string]string
var nonMatchingQueries []map[string]string

matchingQueries = append(matchingQueries, map[string]string{
"key": "owner",
"value": "gd2test",
})
matchingQueries = append(matchingQueries, map[string]string{
"key": "owner",
})
matchingQueries = append(matchingQueries, map[string]string{
"value": "gd2test",
})
for _, filter := range matchingQueries {
volumes, err := client.Volumes("", filter)
r.Nil(err)
r.Len(volumes, 1)
}

nonMatchingQueries = append(nonMatchingQueries, map[string]string{
"key": "owner",
"value": "gd2-test",
})
nonMatchingQueries = append(nonMatchingQueries, map[string]string{
"key": "owners",
})
nonMatchingQueries = append(nonMatchingQueries, map[string]string{
"value": "gd2tests",
})
for _, filter := range nonMatchingQueries {
volumes, err := client.Volumes("", filter)
r.Nil(err)
r.Len(volumes, 0)
}

volumes, err := client.Volumes("")
r.Nil(err)
Expand Down
27 changes: 24 additions & 3 deletions glustercli/cmd/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ var (
flagExpandCmdReplicaCount int
flagExpandCmdForce bool

// Filter Volume Info/List command flags
flagCmdFilterKey string
flagCmdFilterValue string

// Edit Command Flags
flagCmdMetadataKey string
flagCmdMetadataValue string
Expand All @@ -62,10 +66,14 @@ func init() {
volumeCmd.AddCommand(volumeGetCmd)
volumeCmd.AddCommand(volumeResetCmd)

volumeInfoCmd.Flags().StringVar(&flagCmdFilterKey, "key", "", "Filter by metadata key")
volumeInfoCmd.Flags().StringVar(&flagCmdFilterValue, "value", "", "Filter by metadata value")
volumeCmd.AddCommand(volumeInfoCmd)

volumeCmd.AddCommand(volumeStatusCmd)

volumeListCmd.Flags().StringVar(&flagCmdFilterKey, "key", "", "Filter by metadata Key")
volumeListCmd.Flags().StringVar(&flagCmdFilterValue, "value", "", "Filter by metadata value")
volumeCmd.AddCommand(volumeListCmd)

// Volume Expand
Expand Down Expand Up @@ -284,8 +292,21 @@ func volumeInfoHandler2(cmd *cobra.Command, isInfo bool) error {
volname = cmd.Flags().Args()[0]
}
if volname == "" {
vols, err = client.Volumes("")
if flagCmdFilterKey == "" && flagCmdFilterValue == "" {
vols, err = client.Volumes("")
} else if flagCmdFilterKey != "" && flagCmdFilterValue == "" {
vols, err = client.Volumes("", map[string]string{"key": flagCmdFilterKey})
} else if flagCmdFilterKey == "" && flagCmdFilterValue != "" {
vols, err = client.Volumes("", map[string]string{"value": flagCmdFilterValue})
} else if flagCmdFilterKey != "" && flagCmdFilterValue != "" {
vols, err = client.Volumes("", map[string]string{"key": flagCmdFilterKey,
"value": flagCmdFilterValue,
})
}
} else {
if flagCmdFilterKey != "" || flagCmdFilterValue != "" {
return errors.New("Invalid command. Cannot give filter arguments when providing volname")
}
vols, err = client.Volumes(volname)
}

Expand All @@ -309,7 +330,7 @@ func volumeInfoHandler2(cmd *cobra.Command, isInfo bool) error {
}

var volumeInfoCmd = &cobra.Command{
Use: "info",
Use: "info [<volname> |--key <key>|--value <value>|--key <key> --value <value>]",
Short: helpVolumeInfoCmd,
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
Expand All @@ -326,7 +347,7 @@ var volumeInfoCmd = &cobra.Command{
}

var volumeListCmd = &cobra.Command{
Use: "list",
Use: "list [--key <key>|--value <value>|--key <key> --value <value>]",
Short: helpVolumeListCmd,
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
Expand Down
13 changes: 10 additions & 3 deletions glusterd2/commands/volumes/volume-list.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@ import (
func volumeListHandler(w http.ResponseWriter, r *http.Request) {

ctx := r.Context()

volumes, err := volume.GetVolumes()
keys, keyFound := r.URL.Query()["key"]
values, valueFound := r.URL.Query()["value"]
filterParams := make(map[string]string)
if keyFound {
filterParams["key"] = keys[0]
}
if valueFound {
filterParams["value"] = values[0]
}
volumes, err := volume.GetVolumes(filterParams)
if err != nil {
restutils.SendHTTPError(ctx, w, http.StatusNotFound, err)
}

resp := createVolumeListResp(volumes)
restutils.SendHTTPResponse(ctx, w, http.StatusOK, resp)
}
Expand Down
60 changes: 56 additions & 4 deletions glusterd2/volume/store-utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ const (
volumePrefix string = "volumes/"
)

// metadataFilter is a filter type
type metadataFilter uint32

// GetVolumes Filter Types
const (
noKeyAndValue metadataFilter = iota
onlyKey
onlyValue
keyAndValue
)

var (
// AddOrUpdateVolumeFunc marshals to volume object and passes to store to add/update
AddOrUpdateVolumeFunc = AddOrUpdateVolume
Expand Down Expand Up @@ -91,17 +102,38 @@ func GetVolumesList() (map[string]uuid.UUID, error) {
return volumes, nil
}

// getFilterType return the filter type for volume list/info
func getFilterType(filterParams map[string]string) metadataFilter {
_, key := filterParams["key"]
_, value := filterParams["value"]
if key && !value {
return onlyKey
} else if value && !key {
return onlyValue
} else if value && key {
return keyAndValue
}
return noKeyAndValue
}

//GetVolumes retrives the json objects from the store and converts them into
//respective volinfo objects
func GetVolumes() ([]*Volinfo, error) {
func GetVolumes(filterParams ...map[string]string) ([]*Volinfo, error) {
resp, e := store.Store.Get(context.TODO(), volumePrefix, clientv3.WithPrefix())
if e != nil {
return nil, e
}

volumes := make([]*Volinfo, len(resp.Kvs))
var filterType metadataFilter
if len(filterParams) == 0 {
filterType = noKeyAndValue
} else {
filterType = getFilterType(filterParams[0])
}

var volumes []*Volinfo

for i, kv := range resp.Kvs {
for _, kv := range resp.Kvs {
var vol Volinfo

if err := json.Unmarshal(kv.Value, &vol); err != nil {
Expand All @@ -111,8 +143,28 @@ func GetVolumes() ([]*Volinfo, error) {
}).Error("Failed to unmarshal volume")
continue
}
switch filterType {

case onlyKey:
if _, keyFound := vol.Metadata[filterParams[0]["key"]]; keyFound {
volumes = append(volumes, &vol)
}
case onlyValue:
for _, value := range vol.Metadata {
if value == filterParams[0]["value"] {
volumes = append(volumes, &vol)
}
}
case keyAndValue:
if value, keyFound := vol.Metadata[filterParams[0]["key"]]; keyFound {
if value == filterParams[0]["value"] {
volumes = append(volumes, &vol)
}
}
default:
volumes = append(volumes, &vol)

volumes[i] = &vol
}
}

return volumes, nil
Expand Down
20 changes: 20 additions & 0 deletions pkg/api/volume_resp.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ type VolumeStatusResp struct {
type VolumeCreateResp VolumeInfo

// VolumeGetResp is the response sent for a volume get request.
/* VolumeGetResp can also be filtered based on query parameters
sent along with volume list/info api.
Query Parameters can be either metadata key/value/both key and value.
Example of API request-
1- /volumes -
a- GET 'http://localhost:24007/v1/volumes?key={keyname}&value={value}
b- GET 'http://localhost:24007/v1/volumes?key={keyname}'
c- GET 'http://localhost:24007/v1/volumes?value={value}'
Note - Cannot use query parameters or cli flags if volname is also supplied.
*/
type VolumeGetResp VolumeInfo

// VolumeExpandResp is the response sent for a volume expand request.
Expand All @@ -91,6 +101,16 @@ type VolumeStopResp VolumeInfo
type VolumeOptionResp VolumeInfo

// VolumeListResp is the response sent for a volume list request.
/* VolumeListResp can also be filtered based on query parameters
sent along with volume list/info api.
Query Parameters can be either metadata key/value/both key and value.
Example of API request-
1- /volumes -
a- GET 'http://localhost:24007/v1/volumes?key={keyname}&value={value}
b- GET 'http://localhost:24007/v1/volumes?key={keyname}'
c- GET 'http://localhost:24007/v1/volumes?value={value}'
Note - Cannot use query parameters or cli flags if volname is also supplied.
*/
type VolumeListResp []VolumeGetResp

// OptionGroupListResp is the response sent for a group list request.
Expand Down
49 changes: 47 additions & 2 deletions pkg/restclient/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,67 @@ package restclient
import (
"fmt"
"net/http"
"net/url"

"github.com/gluster/glusterd2/pkg/api"
)

// metadataFilter is a filter type
type metadataFilter uint32

// GetVolumes Filter Types
const (
noKeyAndValue metadataFilter = iota
onlyKey
onlyValue
keyAndValue
)

// VolumeCreate creates Gluster Volume
func (c *Client) VolumeCreate(req api.VolCreateReq) (api.VolumeCreateResp, error) {
var vol api.VolumeCreateResp
err := c.post("/v1/volumes", req, http.StatusCreated, &vol)
return vol, err
}

// getFilterType return the filter type for volume list/info
func getFilterType(filterParams map[string]string) metadataFilter {
_, key := filterParams["key"]
_, value := filterParams["value"]
if key && !value {
return onlyKey
} else if value && !key {
return onlyValue
} else if value && key {
return keyAndValue
}
return noKeyAndValue
}

// getQueryString returns the query string for filtering volumes
func getQueryString(filterParam map[string]string) string {
filterType := getFilterType(filterParam)
var queryString string
switch filterType {
case onlyKey:
queryString = fmt.Sprintf("?key=%s", url.QueryEscape(filterParam["key"]))
case onlyValue:
queryString = fmt.Sprintf("?value=%s", url.QueryEscape(filterParam["value"]))
case keyAndValue:
queryString = fmt.Sprintf("?key=%s&value=%s", url.QueryEscape(filterParam["key"]), url.QueryEscape(filterParam["value"]))
}
return queryString
}

// Volumes returns list of all volumes
func (c *Client) Volumes(volname string) (api.VolumeListResp, error) {
func (c *Client) Volumes(volname string, filterParams ...map[string]string) (api.VolumeListResp, error) {
if volname == "" {
var vols api.VolumeListResp
url := fmt.Sprintf("/v1/volumes")
var queryString string
if len(filterParams) > 0 {
queryString = getQueryString(filterParams[0])
}
url := fmt.Sprintf("/v1/volumes%s", queryString)
err := c.get(url, nil, http.StatusOK, &vols)
return vols, err
}
Expand Down

0 comments on commit bb32f32

Please sign in to comment.