diff --git a/apis/server/container_bridge.go b/apis/server/container_bridge.go index 0491713b5..067520dbe 100644 --- a/apis/server/container_bridge.go +++ b/apis/server/container_bridge.go @@ -23,11 +23,11 @@ import ( func (s *Server) removeContainers(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { name := mux.Vars(req)["name"] - option := &mgr.ContainerRemoveOption{ - Force: httputils.BoolValue(req, "force"), - // TODO Volume and Link will be supported in the future. - Volume: httputils.BoolValue(req, "v"), - Link: httputils.BoolValue(req, "link"), + option := &types.ContainerRemoveOptions{ + Force: httputils.BoolValue(req, "force"), + Volumes: httputils.BoolValue(req, "v"), + // TODO: Link will be supported in the future. + Link: httputils.BoolValue(req, "link"), } if err := s.ContainerMgr.Remove(ctx, name, option); err != nil { diff --git a/apis/server/volume_bridge.go b/apis/server/volume_bridge.go index be579fd52..cf86cc1a0 100644 --- a/apis/server/volume_bridge.go +++ b/apis/server/volume_bridge.go @@ -3,12 +3,12 @@ package server import ( "context" "encoding/json" - "fmt" "net/http" "github.com/alibaba/pouch/apis/types" "github.com/alibaba/pouch/pkg/httputils" "github.com/alibaba/pouch/pkg/randomid" + volumetypes "github.com/alibaba/pouch/storage/volume/types" "github.com/go-openapi/strfmt" "github.com/gorilla/mux" @@ -35,7 +35,7 @@ func (s *Server) createVolume(ctx context.Context, rw http.ResponseWriter, req * } if driver == "" { - driver = "local" + driver = volumetypes.DefaultBackend } if err := s.VolumeMgr.Create(ctx, name, driver, options, labels); err != nil { @@ -100,16 +100,6 @@ 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"] - volume, err := s.VolumeMgr.Get(ctx, name) - if err != nil { - return err - } - - ref := volume.Option("ref") - if ref != "" { - return fmt.Errorf("failed to remove volume: %s, using by: %s", name, ref) - } - if err := s.VolumeMgr.Remove(ctx, name); err != nil { return err } diff --git a/apis/swagger.yml b/apis/swagger.yml index e7c5532a8..31bece7e2 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -3376,6 +3376,17 @@ definitions: Width: type: "integer" + ContainerRemoveOptions: + description: "options of remove container" + type: "object" + properties: + Force: + type: "boolean" + Volumes: + type: "boolean" + Link: + type: "boolean" + parameters: id: name: id diff --git a/apis/types/container_remove_options.go b/apis/types/container_remove_options.go new file mode 100644 index 000000000..fbaa0c602 --- /dev/null +++ b/apis/types/container_remove_options.go @@ -0,0 +1,62 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package types + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" +) + +// ContainerRemoveOptions options of remove container +// swagger:model ContainerRemoveOptions + +type ContainerRemoveOptions struct { + + // force + Force bool `json:"Force,omitempty"` + + // link + Link bool `json:"Link,omitempty"` + + // volumes + Volumes bool `json:"Volumes,omitempty"` +} + +/* polymorph ContainerRemoveOptions Force false */ + +/* polymorph ContainerRemoveOptions Link false */ + +/* polymorph ContainerRemoveOptions Volumes false */ + +// Validate validates this container remove options +func (m *ContainerRemoveOptions) Validate(formats strfmt.Registry) error { + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// MarshalBinary interface implementation +func (m *ContainerRemoveOptions) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ContainerRemoveOptions) UnmarshalBinary(b []byte) error { + var res ContainerRemoveOptions + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/cli/rm.go b/cli/rm.go index 846e0e884..7849267e7 100644 --- a/cli/rm.go +++ b/cli/rm.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/alibaba/pouch/apis/types" + "github.com/spf13/cobra" ) @@ -18,7 +20,8 @@ be released. // RmCommand is used to implement 'rm' command. type RmCommand struct { baseCommand - force bool + force bool + removeVolumes bool } // Init initializes RmCommand command. @@ -39,7 +42,10 @@ func (r *RmCommand) Init(c *Cli) { // addFlags adds flags for specific command. func (r *RmCommand) addFlags() { - r.cmd.Flags().BoolVarP(&r.force, "force", "f", false, "if the container is running, force to remove it") + flagSet := r.cmd.Flags() + + flagSet.BoolVarP(&r.force, "force", "f", false, "if the container is running, force to remove it") + flagSet.BoolVarP(&r.removeVolumes, "volumes", "v", false, "remove container's volumes that create by the container") } // runRm is the entry of RmCommand command. @@ -47,8 +53,13 @@ func (r *RmCommand) runRm(args []string) error { ctx := context.Background() apiClient := r.cli.Client() + options := &types.ContainerRemoveOptions{ + Force: r.force, + Volumes: r.removeVolumes, + } + for _, name := range args { - if err := apiClient.ContainerRemove(ctx, name, r.force); err != nil { + if err := apiClient.ContainerRemove(ctx, name, options); err != nil { return fmt.Errorf("failed to remove container: %v", err) } fmt.Printf("%s\n", name) diff --git a/cli/run.go b/cli/run.go index 40c1c31c2..35165fad2 100644 --- a/cli/run.go +++ b/cli/run.go @@ -7,6 +7,8 @@ import ( "os" "strings" + "github.com/alibaba/pouch/apis/types" + "github.com/spf13/cobra" ) @@ -145,7 +147,7 @@ func (rc *RunCommand) runRun(args []string) error { } if rc.rm { - if err := apiClient.ContainerRemove(ctx, containerName, true); err != nil { + if err := apiClient.ContainerRemove(ctx, containerName, &types.ContainerRemoveOptions{Force: true}); err != nil { return fmt.Errorf("failed to remove container %s: %v", containerName, err) } } diff --git a/client/container_remove.go b/client/container_remove.go index d212f7116..de80e6230 100644 --- a/client/container_remove.go +++ b/client/container_remove.go @@ -3,14 +3,19 @@ package client import ( "context" "net/url" + + "github.com/alibaba/pouch/apis/types" ) // ContainerRemove removes a container. -func (client *APIClient) ContainerRemove(ctx context.Context, name string, force bool) error { +func (client *APIClient) ContainerRemove(ctx context.Context, name string, options *types.ContainerRemoveOptions) error { q := url.Values{} - if force { + if options.Force { q.Set("force", "true") } + if options.Volumes { + q.Set("v", "true") + } resp, err := client.delete(ctx, "/containers/"+name, q, nil) if err != nil { diff --git a/client/container_remove_test.go b/client/container_remove_test.go index 635facf07..30d9cfe46 100644 --- a/client/container_remove_test.go +++ b/client/container_remove_test.go @@ -8,13 +8,15 @@ import ( "net/http" "strings" "testing" + + "github.com/alibaba/pouch/apis/types" ) func TestContainerRemoveError(t *testing.T) { client := &APIClient{ HTTPCli: newMockClient(errorMockResponse(http.StatusInternalServerError, "Server error")), } - err := client.ContainerRemove(context.Background(), "nothing", true) + err := client.ContainerRemove(context.Background(), "nothing", &types.ContainerRemoveOptions{Force: true}) if err == nil || !strings.Contains(err.Error(), "Server error") { t.Fatalf("expected a Server Error, got %v", err) } @@ -24,7 +26,7 @@ func TestContainerRemoveNotFoundError(t *testing.T) { client := &APIClient{ HTTPCli: newMockClient(errorMockResponse(http.StatusNotFound, "Not Found")), } - err := client.ContainerRemove(context.Background(), "no contaienr", true) + err := client.ContainerRemove(context.Background(), "no container", &types.ContainerRemoveOptions{Force: true}) if err == nil || !strings.Contains(err.Error(), "Not Found") { t.Fatalf("expected a Not Found Error, got %v", err) } @@ -49,7 +51,7 @@ func TestContainerRemove(t *testing.T) { client := &APIClient{ HTTPCli: httpClient, } - err := client.ContainerRemove(context.Background(), "container_id", true) + err := client.ContainerRemove(context.Background(), "container_id", &types.ContainerRemoveOptions{Force: true}) if err != nil { t.Fatal(err) } diff --git a/client/interface.go b/client/interface.go index 5fd2145a1..5738cfe47 100644 --- a/client/interface.go +++ b/client/interface.go @@ -23,7 +23,7 @@ type ContainerAPIClient interface { ContainerCreate(ctx context.Context, config types.ContainerConfig, hostConfig *types.HostConfig, networkConfig *types.NetworkingConfig, containerName string) (*types.ContainerCreateResp, error) ContainerStart(ctx context.Context, name, detachKeys string) error ContainerStop(ctx context.Context, name, timeout string) error - ContainerRemove(ctx context.Context, name string, force bool) error + ContainerRemove(ctx context.Context, name string, options *types.ContainerRemoveOptions) error ContainerList(ctx context.Context, all bool) ([]*types.Container, error) ContainerAttach(ctx context.Context, name string, stdin bool) (net.Conn, *bufio.Reader, error) ContainerCreateExec(ctx context.Context, name string, config *types.ExecCreateConfig) (*types.ExecCreateResp, error) diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index 65fa4b0fa..7f9e5dc15 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -28,6 +28,7 @@ import ( "github.com/alibaba/pouch/pkg/reference" "github.com/alibaba/pouch/pkg/utils" "github.com/alibaba/pouch/storage/quota" + volumetypes "github.com/alibaba/pouch/storage/volume/types" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/namespaces" @@ -77,7 +78,7 @@ type ContainerMgr interface { GetExecConfig(ctx context.Context, execid string) (*ContainerExecConfig, error) // Remove removes a container, it may be running or stopped and so on. - Remove(ctx context.Context, name string, option *ContainerRemoveOption) error + Remove(ctx context.Context, name string, option *types.ContainerRemoveOptions) error // Rename renames a container. Rename(ctx context.Context, oldName string, newName string) error @@ -209,7 +210,7 @@ func (mgr *ContainerManager) Restore(ctx context.Context) error { } // Remove removes a container, it may be running or stopped and so on. -func (mgr *ContainerManager) Remove(ctx context.Context, name string, option *ContainerRemoveOption) error { +func (mgr *ContainerManager) Remove(ctx context.Context, name string, options *types.ContainerRemoveOptions) error { c, err := mgr.container(name) if err != nil { return err @@ -217,12 +218,12 @@ func (mgr *ContainerManager) Remove(ctx context.Context, name string, option *Co c.Lock() defer c.Unlock() - if !c.IsStopped() && !c.IsExited() && !c.IsCreated() && !option.Force { + if !c.IsStopped() && !c.IsExited() && !c.IsCreated() && !options.Force { return fmt.Errorf("container: %s is not stopped, can't remove it without flag force", c.ID()) } // if the container is running, force to stop it. - if c.IsRunning() && option.Force { + if c.IsRunning() && options.Force { msg, err := mgr.Client.DestroyContainer(ctx, c.ID(), c.StopTimeout()) if err != nil && !errtypes.IsNotfound(err) { return errors.Wrapf(err, "failed to destroy container: %s", c.ID()) @@ -232,7 +233,7 @@ func (mgr *ContainerManager) Remove(ctx context.Context, name string, option *Co } } - if err := mgr.detachVolumes(ctx, c.meta); err != nil { + if err := mgr.detachVolumes(ctx, c.meta, options.Volumes); err != nil { logrus.Errorf("failed to detach volume: %v", err) } @@ -1799,32 +1800,22 @@ func (mgr *ContainerManager) execExitedAndRelease(id string, m *ctrd.Message) er return nil } -func (mgr *ContainerManager) bindVolume(ctx context.Context, name string, meta *ContainerMeta) (string, string, error) { - id := meta.ID - - ref := "" - driver := "local" +func (mgr *ContainerManager) attachVolume(ctx context.Context, name string, meta *ContainerMeta) (string, string, error) { + driver := volumetypes.DefaultBackend v, err := mgr.VolumeMgr.Get(ctx, name) if err != nil || v == nil { opts := map[string]string{ - "backend": "local", + "backend": driver, } if err := mgr.VolumeMgr.Create(ctx, name, meta.HostConfig.VolumeDriver, opts, nil); err != nil { logrus.Errorf("failed to create volume: %s, err: %v", name, err) return "", "", errors.Wrap(err, "failed to create volume") } } else { - ref = v.Option("ref") driver = v.Driver() } - option := map[string]string{} - if ref == "" { - option["ref"] = id - } else { - option["ref"] = ref + "," + id - } - if _, err := mgr.VolumeMgr.Attach(ctx, name, option); err != nil { + if _, err := mgr.VolumeMgr.Attach(ctx, name, map[string]string{volumetypes.OptionRef: meta.ID}); err != nil { logrus.Errorf("failed to attach volume: %s, err: %v", name, err) return "", "", errors.Wrap(err, "failed to attach volume") } @@ -1854,7 +1845,7 @@ func (mgr *ContainerManager) generateMountPoints(ctx context.Context, meta *Cont defer func() { if err != nil { - if err := mgr.detachVolumes(ctx, meta); err != nil { + if err := mgr.detachVolumes(ctx, meta, false); err != nil { logrus.Errorf("failed to detach volume, err: %v", err) } } @@ -1906,10 +1897,12 @@ func (mgr *ContainerManager) getMountPointFromBinds(ctx context.Context, meta *C case 2: mp.Source = parts[0] mp.Destination = parts[1] + mp.Named = true case 3: mp.Source = parts[0] mp.Destination = parts[1] mode = parts[2] + mp.Named = true default: return errors.Errorf("unknown bind: %s", b) } @@ -1939,7 +1932,7 @@ func (mgr *ContainerManager) getMountPointFromBinds(ctx context.Context, meta *C name := mp.Source if _, exist := volumeSet[name]; !exist { mp.Name = name - mp.Source, mp.Driver, err = mgr.bindVolume(ctx, name, meta) + mp.Source, mp.Driver, err = mgr.attachVolume(ctx, name, meta) if err != nil { logrus.Errorf("failed to bind volume: %s, err: %v", name, err) return errors.Wrap(err, "failed to bind volume") @@ -2002,10 +1995,9 @@ func (mgr *ContainerManager) getMountPointFromVolumes(ctx context.Context, meta mp := new(types.MountPoint) mp.Name = name - mp.Named = true mp.Destination = dest - mp.Source, mp.Driver, err = mgr.bindVolume(ctx, mp.Name, meta) + mp.Source, mp.Driver, err = mgr.attachVolume(ctx, mp.Name, meta) if err != nil { logrus.Errorf("failed to bind volume: %s, err: %v", mp.Name, err) return errors.Wrap(err, "failed to bind volume") @@ -2050,10 +2042,9 @@ func (mgr *ContainerManager) getMountPointFromImage(ctx context.Context, meta *C mp := new(types.MountPoint) mp.Name = name - mp.Named = true mp.Destination = dest - mp.Source, mp.Driver, err = mgr.bindVolume(ctx, mp.Name, meta) + mp.Source, mp.Driver, err = mgr.attachVolume(ctx, mp.Name, meta) if err != nil { logrus.Errorf("failed to bind volume: %s, err: %v", mp.Name, err) return errors.Wrap(err, "failed to bind volume") @@ -2106,7 +2097,7 @@ func (mgr *ContainerManager) getMountPointFromContainers(ctx context.Context, me if _, exist := volumeSet[oldMountPoint.Name]; len(oldMountPoint.Name) > 0 && !exist { mp.Name = oldMountPoint.Name - mp.Source, mp.Driver, err = mgr.bindVolume(ctx, oldMountPoint.Name, meta) + mp.Source, mp.Driver, err = mgr.attachVolume(ctx, oldMountPoint.Name, meta) if err != nil { logrus.Errorf("failed to bind volume: %s, err: %v", oldMountPoint.Name, err) return errors.Wrap(err, "failed to bind volume") @@ -2235,42 +2226,23 @@ func (mgr *ContainerManager) setMountPointDiskQuota(ctx context.Context, c *Cont return nil } -func (mgr *ContainerManager) detachVolumes(ctx context.Context, c *ContainerMeta) error { +func (mgr *ContainerManager) detachVolumes(ctx context.Context, c *ContainerMeta, remove bool) error { for _, mount := range c.Mounts { name := mount.Name if name == "" { continue } - v, err := mgr.VolumeMgr.Get(ctx, name) + _, err := mgr.VolumeMgr.Detach(ctx, name, map[string]string{volumetypes.OptionRef: c.ID}) if err != nil { - logrus.Errorf("failed to get volume: %s", name) - return err - } - - option := map[string]string{} - ref := v.Option("ref") - if ref == "" { - continue - } - if !strings.Contains(ref, c.ID) { - continue + logrus.Warnf("failed to detach volume: %s, err: %v", name, err) } - ids := strings.Split(ref, ",") - for i, id := range ids { - if id == c.ID { - ids = append(ids[:i], ids[i+1:]...) - break + if remove && !mount.Named { + if err := mgr.VolumeMgr.Remove(ctx, name); err != nil && !errtypes.IsInUse(err) { + logrus.Warnf("failed to remove volume: %s when remove container", name) } } - if len(ids) > 0 { - option["ref"] = strings.Join(ids, ",") - } else { - option["ref"] = "" - } - - mgr.VolumeMgr.Detach(ctx, name, option) } return nil diff --git a/daemon/mgr/container_types.go b/daemon/mgr/container_types.go index c48e69a55..f714c5087 100644 --- a/daemon/mgr/container_types.go +++ b/daemon/mgr/container_types.go @@ -75,13 +75,6 @@ type AttachConfig struct { CriLogFile *os.File } -// ContainerRemoveOption wraps the container remove interface params. -type ContainerRemoveOption struct { - Force bool - Volume bool - Link bool -} - // ContainerListOption wraps the container list interface params. type ContainerListOption struct { All bool diff --git a/daemon/mgr/cri.go b/daemon/mgr/cri.go index aa467c843..bc80954cb 100644 --- a/daemon/mgr/cri.go +++ b/daemon/mgr/cri.go @@ -309,14 +309,14 @@ func (c *CriManager) RemovePodSandbox(ctx context.Context, r *runtime.RemovePodS // Remove all containers in the sandbox. for _, container := range containers { - err = c.ContainerMgr.Remove(ctx, container.ID, &ContainerRemoveOption{Volume: true, Force: true}) + err = c.ContainerMgr.Remove(ctx, container.ID, &apitypes.ContainerRemoveOptions{Volumes: true, Force: true}) if err != nil { // TODO: log an error message or break? } } // Remove the sandbox container. - err = c.ContainerMgr.Remove(ctx, podSandboxID, &ContainerRemoveOption{Volume: true, Force: true}) + err = c.ContainerMgr.Remove(ctx, podSandboxID, &apitypes.ContainerRemoveOptions{Volumes: true, Force: true}) if err != nil { return nil, fmt.Errorf("failed to remove sandbox %q: %v", podSandboxID, err) } @@ -528,7 +528,7 @@ func (c *CriManager) StopContainer(ctx context.Context, r *runtime.StopContainer func (c *CriManager) RemoveContainer(ctx context.Context, r *runtime.RemoveContainerRequest) (*runtime.RemoveContainerResponse, error) { containerID := r.GetContainerId() - err := c.ContainerMgr.Remove(ctx, containerID, &ContainerRemoveOption{Volume: true, Force: true}) + err := c.ContainerMgr.Remove(ctx, containerID, &apitypes.ContainerRemoveOptions{Volumes: true, Force: true}) if err != nil { return nil, fmt.Errorf("failed to remove container %q: %v", containerID, err) } diff --git a/daemon/mgr/volume.go b/daemon/mgr/volume.go index 38ec306ef..589cb4b59 100644 --- a/daemon/mgr/volume.go +++ b/daemon/mgr/volume.go @@ -46,7 +46,7 @@ type VolumeManager struct { func NewVolumeManager(cfg volume.Config) (*VolumeManager, error) { // init voluem config cfg.RemoveVolume = true - cfg.DefaultBackend = "local" + cfg.DefaultBackend = types.DefaultBackend core, err := volume.NewCore(cfg) if err != nil { @@ -93,7 +93,16 @@ func (vm *VolumeManager) Create(ctx context.Context, name, driver string, option // Remove is used to delete an existing volume. func (vm *VolumeManager) Remove(ctx context.Context, name string) error { - // TODO: check container use. + vol, err := vm.Get(ctx, name) + if err != nil { + return errors.Wrapf(err, "failed to get volume: %s", name) + } + + ref := vol.Option(types.OptionRef) + if ref != "" { + return errors.Wrapf(errtypes.ErrUsingbyContainers, "failed to remove volume: %s", name) + } + id := types.VolumeID{ Name: name, } @@ -102,6 +111,7 @@ func (vm *VolumeManager) Remove(ctx context.Context, name string) error { return errors.Wrap(errtypes.ErrNotfound, err.Error()) } } + return nil } @@ -147,6 +157,26 @@ func (vm *VolumeManager) Attach(ctx context.Context, name string, options map[st id := types.VolumeID{ Name: name, } + + v, err := vm.Get(ctx, name) + if err != nil { + return nil, errors.Errorf("failed to get volume: %s", name) + } + + if options == nil { + options = make(map[string]string) + } + + cid, ok := options[types.OptionRef] + if ok && cid != "" { + ref := v.Option(types.OptionRef) + if ref == "" { + options[types.OptionRef] = cid + } else { + options[types.OptionRef] = strings.Join([]string{ref, cid}, ",") + } + } + return vm.core.AttachVolume(id, options) } @@ -155,5 +185,39 @@ func (vm *VolumeManager) Detach(ctx context.Context, name string, options map[st id := types.VolumeID{ Name: name, } + + v, err := vm.Get(ctx, name) + if err != nil { + return nil, errors.Wrapf(err, "failed to get volume: %s", name) + } + + if options == nil { + options = make(map[string]string) + } + + cid, ok := options[types.OptionRef] + if ok && cid != "" { + ref := v.Option(types.OptionRef) + if !strings.Contains(ref, cid) { + return v, nil + } + + if ref != "" { + ids := strings.Split(ref, ",") + for i, id := range ids { + if id == cid { + ids = append(ids[:i], ids[i+1:]...) + break + } + } + + if len(ids) > 0 { + options[types.OptionRef] = strings.Join(ids, ",") + } else { + options[types.OptionRef] = "" + } + } + } + return vm.core.DetachVolume(id, options) } diff --git a/pkg/errtypes/errors.go b/pkg/errtypes/errors.go index 816de7bf8..acf4943b2 100644 --- a/pkg/errtypes/errors.go +++ b/pkg/errtypes/errors.go @@ -28,6 +28,9 @@ var ( // ErrNotImplemented represents that the function is not implemented. ErrNotImplemented = errorType{codeNotImplemented, "not implemented"} + + // ErrUsingbyContainers represents that object is useing by containers + ErrUsingbyContainers = errorType{codeInUse, "using by containers"} ) const ( @@ -39,6 +42,7 @@ const ( codeTimeout codeLockfailed codeNotImplemented + codeInUse ) type errorType struct { @@ -70,6 +74,11 @@ func IsTimeout(err error) bool { return checkError(err, codeTimeout) } +// IsInUse checks the error is using by others or not. +func IsInUse(err error) bool { + return checkError(err, codeInUse) +} + func checkError(err error, code int) bool { err = causeError(err) diff --git a/storage/volume/core.go b/storage/volume/core.go index 2fd74965e..41f3cdf41 100644 --- a/storage/volume/core.go +++ b/storage/volume/core.go @@ -393,7 +393,9 @@ func (c *Core) DetachVolume(id types.VolumeID, extra map[string]string) (*types. v.Spec.Extra[key] = value } - if a, ok := dv.(driver.AttachDetach); ok { + // if volume has referance, skip to detach volume. + ref := v.Option(types.OptionRef) + if a, ok := dv.(driver.AttachDetach); ok && ref == "" { if !dv.StoreMode(ctx).IsLocal() { if s, err = c.getStorage(v.StorageID()); err != nil { return nil, err diff --git a/storage/volume/types/types.go b/storage/volume/types/types.go index ebb459909..9bce6f61f 100644 --- a/storage/volume/types/types.go +++ b/storage/volume/types/types.go @@ -7,6 +7,12 @@ type Option struct { } var ( - // APIVersion defined control server api version. + // APIVersion defines control server api version. APIVersion = "/api/v1" + + // OptionRef defines the reference of containers. + OptionRef = "ref" + + // DefaultBackend defines the default volume backend. + DefaultBackend = "local" ) diff --git a/test/cli_rm_test.go b/test/cli_rm_test.go new file mode 100644 index 000000000..f4af7430b --- /dev/null +++ b/test/cli_rm_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "strings" + + "github.com/alibaba/pouch/test/command" + "github.com/alibaba/pouch/test/environment" + + "github.com/go-check/check" + "github.com/gotestyourself/gotestyourself/icmd" +) + +// PouchRmSuite is the test suite for pouch rm CLI. +type PouchRmSuite struct{} + +func init() { + check.Suite(&PouchRmSuite{}) +} + +// SetUpSuite does common setup in the beginning of each test suite. +func (suite *PouchRmSuite) SetUpSuite(c *check.C) { + SkipIfFalse(c, environment.IsLinux) + + environment.PruneAllContainers(apiClient) + + PullImage(c, busyboxImage) +} + +// TestContainerRmWithVolume tests remove container . +func (suite *PouchRmSuite) TestContainerRmWithVolume(c *check.C) { + volumeName := "rmVolume-test-volume" + containerName := "rmVolume-test" + + // create volume + command.PouchRun("volume", "create", "-n", volumeName).Assert(c, icmd.Success) + defer func() { + command.PouchRun("volume", "rm", volumeName).Assert(c, icmd.Success) + }() + + ret := command.PouchRun("volume", "ls") + expectVolumeNums := strings.Count(ret.Stdout(), "\n") + + // run container with volume + command.PouchRun("run", "-d", "--name", containerName, + "-v", volumeName+":/mnt", + "-v", "/home", + busyboxImage, "top").Assert(c, icmd.Success) + + command.PouchRun("rm", "-vf", containerName).Assert(c, icmd.Success) + + ret = command.PouchRun("volume", "ls") + ret.Assert(c, icmd.Success) + + found := false + volumeNums := 0 + for _, line := range strings.Split(ret.Stdout(), "\n") { + if strings.Contains(line, volumeName) { + found = true + } + volumeNums++ + } + + c.Assert(volumeNums, check.Equals, expectVolumeNums+1) + c.Assert(found, check.Equals, true) +} diff --git a/test/environment/cleanup.go b/test/environment/cleanup.go index 77effdcb5..e9657cda8 100644 --- a/test/environment/cleanup.go +++ b/test/environment/cleanup.go @@ -4,7 +4,9 @@ import ( "context" "fmt" + "github.com/alibaba/pouch/apis/types" "github.com/alibaba/pouch/client" + "github.com/pkg/errors" ) @@ -35,7 +37,7 @@ func PruneAllContainers(apiClient client.ContainerAPIClient) error { for _, ctr := range containers { // force to remove the containers - if err := apiClient.ContainerRemove(ctx, ctr.ID, true); err != nil { + if err := apiClient.ContainerRemove(ctx, ctr.ID, &types.ContainerRemoveOptions{Force: true}); err != nil { return errors.Wrap(err, fmt.Sprintf("fail to remove container (%s)", ctr.ID)) } }