diff --git a/apis/server/volume_bridge.go b/apis/server/volume_bridge.go index b85137c22..be579fd52 100644 --- a/apis/server/volume_bridge.go +++ b/apis/server/volume_bridge.go @@ -79,7 +79,7 @@ func (s *Server) getVolume(ctx context.Context, rw http.ResponseWriter, req *htt Name: volume.Name, Driver: volume.Driver(), Mountpoint: volume.Path(), - CreatedAt: volume.CreationTimestamp.Format("2006-1-2 15:04:05"), + CreatedAt: volume.CreateTime(), Labels: volume.Labels, } @@ -100,12 +100,12 @@ func (s *Server) getVolume(ctx context.Context, rw http.ResponseWriter, req *htt func (s *Server) removeVolume(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { name := mux.Vars(req)["name"] - v, err := s.VolumeMgr.Get(ctx, name) + volume, err := s.VolumeMgr.Get(ctx, name) if err != nil { return err } - ref := v.Option("ref") + ref := volume.Option("ref") if ref != "" { return fmt.Errorf("failed to remove volume: %s, using by: %s", name, ref) } @@ -124,8 +124,27 @@ func (s *Server) listVolume(ctx context.Context, rw http.ResponseWriter, req *ht } respVolumes := types.VolumeListResp{Volumes: []*types.VolumeInfo{}, Warnings: nil} - for _, name := range volumes { - respVolumes.Volumes = append(respVolumes.Volumes, &types.VolumeInfo{Name: name}) + for _, volume := range volumes { + respVolume := &types.VolumeInfo{ + Name: volume.Name, + Driver: volume.Driver(), + Mountpoint: volume.Path(), + CreatedAt: volume.CreateTime(), + Labels: volume.Labels, + } + + var status map[string]interface{} + for k, v := range volume.Options() { + if k != "" && v != "" { + if status == nil { + status = make(map[string]interface{}) + } + status[k] = v + } + } + respVolume.Status = status + + respVolumes.Volumes = append(respVolumes.Volumes, respVolume) } return EncodeResponse(rw, http.StatusOK, respVolumes) } diff --git a/cli/volume.go b/cli/volume.go index 990c99c05..7380f0771 100644 --- a/cli/volume.go +++ b/cli/volume.go @@ -269,6 +269,9 @@ var volumeListDescription = "List volumes in pouchd. " + // VolumeListCommand is used to implement 'volume rm' command. type VolumeListCommand struct { baseCommand + + size bool + mountPoint bool } // Init initializes VolumeListCommand command. @@ -289,7 +292,11 @@ func (v *VolumeListCommand) Init(c *Cli) { } // addFlags adds flags for specific command. -func (v *VolumeListCommand) addFlags() {} +func (v *VolumeListCommand) addFlags() { + flagSet := v.cmd.Flags() + flagSet.BoolVar(&v.size, "size", false, "Display volume size") + flagSet.BoolVar(&v.mountPoint, "mountpoint", false, "Display volume mountpoint") +} // runVolumeList is the entry of VolumeListCommand command. func (v *VolumeListCommand) runVolumeList(args []string) error { @@ -304,10 +311,28 @@ func (v *VolumeListCommand) runVolumeList(args []string) error { } display := v.cli.NewTableDisplay() - display.AddRow([]string{"Name:"}) - - for _, v := range volumeList.Volumes { - display.AddRow([]string{v.Name}) + displayHead := []string{"DRIVER", "VOLUME NAME"} + if v.size { + displayHead = append(displayHead, "SIZE") + } + if v.mountPoint { + displayHead = append(displayHead, "MOUNT POINT") + } + display.AddRow(displayHead) + + for _, volume := range volumeList.Volumes { + displayLine := []string{volume.Driver, volume.Name} + if v.size { + if s, ok := volume.Status["size"]; ok { + displayLine = append(displayLine, s.(string)) + } else { + displayLine = append(displayLine, "ulimit") + } + } + if v.mountPoint { + displayLine = append(displayLine, volume.Mountpoint) + } + display.AddRow(displayLine) } display.Flush() @@ -318,8 +343,8 @@ func (v *VolumeListCommand) runVolumeList(args []string) error { // volumeListExample shows examples in volume list command, and is used in auto-generated cli docs. func volumeListExample() string { return `$ pouch volume list -Name: -pouch-volume-1 -pouch-volume-2 -pouch-volume-3` +DRIVER VOLUME NAME +local pouch-volume-1 +local pouch-volume-2 +local pouch-volume-3` } diff --git a/daemon/mgr/volume.go b/daemon/mgr/volume.go index 85b800e09..016e0f9ad 100644 --- a/daemon/mgr/volume.go +++ b/daemon/mgr/volume.go @@ -21,7 +21,7 @@ type VolumeMgr interface { Remove(ctx context.Context, name string) error // List returns all volumes on this host. - List(ctx context.Context, labels map[string]string) ([]string, error) + List(ctx context.Context, labels map[string]string) ([]*types.Volume, error) // Get returns the information of volume that specified name/id. Get(ctx context.Context, name string) (*types.Volume, error) @@ -100,7 +100,7 @@ func (vm *VolumeManager) Remove(ctx context.Context, name string) error { } // List returns all volumes on this host. -func (vm *VolumeManager) List(ctx context.Context, labels map[string]string) ([]string, error) { +func (vm *VolumeManager) List(ctx context.Context, labels map[string]string) ([]*types.Volume, error) { if _, ok := labels["hostname"]; !ok { hostname, err := os.Hostname() if err != nil { @@ -110,7 +110,7 @@ func (vm *VolumeManager) List(ctx context.Context, labels map[string]string) ([] labels["hostname"] = hostname } - return vm.core.ListVolumeName(labels) + return vm.core.ListVolumes(labels) } // Get returns the information of volume that specified name/id. diff --git a/test/cli_volume_test.go b/test/cli_volume_test.go index 035103183..5fc9265b3 100644 --- a/test/cli_volume_test.go +++ b/test/cli_volume_test.go @@ -238,3 +238,75 @@ func (suite *PouchVolumeSuite) TestVolumeBindReplaceMode(c *check.C) { } c.Assert(found, check.Equals, true) } + +// TestVolumeList tests the volume list. +func (suite *PouchVolumeSuite) TestVolumeList(c *check.C) { + pc, _, _, _ := runtime.Caller(0) + tmpname := strings.Split(runtime.FuncForPC(pc).Name(), ".") + var funcname string + for i := range tmpname { + funcname = tmpname[i] + } + + volumeName := "volume_" + funcname + volumeName1 := "volume_" + funcname + "_1" + command.PouchRun("volume", "create", "--name", volumeName1, "-o", "size=1g").Assert(c, icmd.Success) + defer command.PouchRun("volume", "rm", volumeName1) + + volumeName2 := "volume_" + funcname + "_2" + command.PouchRun("volume", "create", "--name", volumeName2, "-o", "size=2g").Assert(c, icmd.Success) + defer command.PouchRun("volume", "rm", volumeName2) + + volumeName3 := "volume_" + funcname + "_3" + command.PouchRun("volume", "create", "--name", volumeName3, "-o", "size=3g").Assert(c, icmd.Success) + defer command.PouchRun("volume", "rm", volumeName3) + + ret := command.PouchRun("volume", "list") + ret.Assert(c, icmd.Success) + + for _, line := range strings.Split(ret.Stdout(), "\n") { + if strings.Contains(line, volumeName) { + if !strings.Contains(line, "local") { + c.Errorf("list result have no driver or name or size or mountpoint, line: %s", line) + break + } + } + } +} + +// TestVolumeList tests the volume list with options: size and mountpoint. +func (suite *PouchVolumeSuite) TestVolumeListOptions(c *check.C) { + pc, _, _, _ := runtime.Caller(0) + tmpname := strings.Split(runtime.FuncForPC(pc).Name(), ".") + var funcname string + for i := range tmpname { + funcname = tmpname[i] + } + + volumeName := "volume_" + funcname + volumeName1 := "volume_" + funcname + "_1" + command.PouchRun("volume", "create", "--name", volumeName1, "-o", "size=1g").Assert(c, icmd.Success) + defer command.PouchRun("volume", "rm", volumeName1) + + volumeName2 := "volume_" + funcname + "_2" + command.PouchRun("volume", "create", "--name", volumeName2, "-o", "size=2g").Assert(c, icmd.Success) + defer command.PouchRun("volume", "rm", volumeName2) + + volumeName3 := "volume_" + funcname + "_3" + command.PouchRun("volume", "create", "--name", volumeName3, "-o", "size=3g").Assert(c, icmd.Success) + defer command.PouchRun("volume", "rm", volumeName3) + + ret := command.PouchRun("volume", "list", "--size", "--mountpoint") + ret.Assert(c, icmd.Success) + + for _, line := range strings.Split(ret.Stdout(), "\n") { + if strings.Contains(line, volumeName) { + if !strings.Contains(line, "local") || + !strings.Contains(line, "g") || + !strings.Contains(line, "/mnt/local") { + c.Errorf("list result have no driver or name or size or mountpoint, line: %s", line) + break + } + } + } +} diff --git a/volume/core.go b/volume/core.go index fb6a8473e..0f8a7dd43 100644 --- a/volume/core.go +++ b/volume/core.go @@ -187,6 +187,42 @@ func (c *Core) CreateVolume(id types.VolumeID) error { return nil } +// ListVolumes return all volumes. +// Param 'labels' use to filter the volumes, only return those you want. +func (c *Core) ListVolumes(labels map[string]string) ([]*types.Volume, error) { + var ls = make([]*types.Volume, 0) + + // first, list local meta store. + list, err := c.store.List() + if err != nil { + return nil, err + } + + // then, list central store. + if c.EnableControl { + url, err := c.listVolumeURL(labels) + if err != nil { + return nil, errors.Wrap(err, "List volume's name") + } + + logrus.Debugf("List volume URL: %s, labels: %s", url, labels) + + if err := client.New().ListKeys(url, &ls); err != nil { + return nil, errors.Wrap(err, "List volume's name") + } + } + + for _, obj := range list { + v, ok := obj.(*types.Volume) + if !ok { + return nil, fmt.Errorf("failed to get volumes in store") + } + ls = append(ls, v) + } + + return ls, nil +} + // ListVolumeName return the name of all volumes only. // Param 'labels' use to filter the volume's names, only return those you want. func (c *Core) ListVolumeName(labels map[string]string) ([]string, error) { @@ -205,7 +241,7 @@ func (c *Core) ListVolumeName(labels map[string]string) ([]string, error) { return nil, errors.Wrap(err, "List volume's name") } - logrus.Debugf("List volume URL: %s, labels: %s", url, labels) + logrus.Debugf("List volume name URL: %s, labels: %s", url, labels) if err := client.New().ListKeys(url, &names); err != nil { return nil, errors.Wrap(err, "List volume's name") diff --git a/volume/core_util.go b/volume/core_util.go index 04674cf95..f39ab3176 100644 --- a/volume/core_util.go +++ b/volume/core_util.go @@ -74,6 +74,24 @@ func (c *Core) volumeURL(id ...types.VolumeID) (string, error) { return client.JoinURL(c.BaseURL, types.APIVersion, client.VolumePath, id[0].Name) } +func (c *Core) listVolumeURL(labels map[string]string) (string, error) { + if c.BaseURL == "" { + return "", volerr.ErrDisableControl + } + url, err := client.JoinURL(c.BaseURL, types.APIVersion, client.VolumePath) + if err != nil { + return "", err + } + + querys := make([]string, 0, len(labels)) + for k, v := range labels { + querys = append(querys, fmt.Sprintf("labels=%s=%s", k, v)) + } + + url = url + "?" + strings.Join(querys, "&") + return url, nil +} + func (c *Core) listVolumeNameURL(labels map[string]string) (string, error) { if c.BaseURL == "" { return "", volerr.ErrDisableControl diff --git a/volume/types/volume.go b/volume/types/volume.go index 36c8adc52..5b6a2b84b 100644 --- a/volume/types/volume.go +++ b/volume/types/volume.go @@ -191,6 +191,15 @@ func (v *Volume) Key() string { return v.Name } +//CreateTime returns the volume's create time. +func (v *Volume) CreateTime() string { + if v.CreationTimestamp == nil { + return "" + } + + return v.CreationTimestamp.Format("2006-1-2 15:04:05") +} + // VolumeID use to define the volume's identity. type VolumeID struct { Name string