diff --git a/apis/plugins/ContainerPlugin.go b/apis/plugins/ContainerPlugin.go index 039c7e1aab..252c9f6206 100644 --- a/apis/plugins/ContainerPlugin.go +++ b/apis/plugins/ContainerPlugin.go @@ -16,4 +16,8 @@ type ContainerPlugin interface { // and if this endpoint should enable resolver and a map which will be used as generic params to create endpoints of // this container PreCreateEndpoint(string, []string) (priority int, disableResolver bool, genericParam map[string]interface{}) + + // PreUpdate defines plugin point where receives an container update request, in this plugin point user + // could change the container update body passed-in by http request body + PreUpdate(io.ReadCloser) (io.ReadCloser, error) } diff --git a/apis/server/container_bridge.go b/apis/server/container_bridge.go index ce6ef9e430..6a9fc61ff8 100644 --- a/apis/server/container_bridge.go +++ b/apis/server/container_bridge.go @@ -266,8 +266,19 @@ func (s *Server) attachContainer(ctx context.Context, rw http.ResponseWriter, re func (s *Server) updateContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { config := &types.UpdateConfig{} + + // set pre update hook plugin + reader := req.Body + if s.ContainerPlugin != nil { + var err error + logrus.Infof("invoke container pre-update hook in plugin") + if reader, err = s.ContainerPlugin.PreUpdate(req.Body); err != nil { + return errors.Wrapf(err, "failed to execute pre-create plugin point") + } + } + // decode request body - if err := json.NewDecoder(req.Body).Decode(config); err != nil { + if err := json.NewDecoder(reader).Decode(config); err != nil { return httputils.NewHTTPError(err, http.StatusBadRequest) } // validate request body diff --git a/apis/swagger.yml b/apis/swagger.yml index 8788b3983a..54d2dbbc6c 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -2201,8 +2201,11 @@ definitions: items: type: "string" DiskQuota: - type: "string" + type: "object" description: "update disk quota for container" + x-nullable: true + additionalProperties: + type: "string" ContainerUpgradeConfig: description: | diff --git a/apis/types/update_config.go b/apis/types/update_config.go index 63216f0a5e..87389254ad 100644 --- a/apis/types/update_config.go +++ b/apis/types/update_config.go @@ -19,7 +19,7 @@ type UpdateConfig struct { Resources // update disk quota for container - DiskQuota string `json:"DiskQuota,omitempty"` + DiskQuota map[string]string `json:"DiskQuota,omitempty"` // A list of environment variables to set inside the container in the form `["VAR=value", ...]`. A variable without `=` is removed from the environment, rather than to have an empty value. // @@ -42,7 +42,7 @@ func (m *UpdateConfig) UnmarshalJSON(raw []byte) error { m.Resources = aO0 var data struct { - DiskQuota string `json:"DiskQuota,omitempty"` + DiskQuota map[string]string `json:"DiskQuota,omitempty"` Env []string `json:"Env,omitempty"` @@ -76,7 +76,7 @@ func (m UpdateConfig) MarshalJSON() ([]byte, error) { _parts = append(_parts, aO0) var data struct { - DiskQuota string `json:"DiskQuota,omitempty"` + DiskQuota map[string]string `json:"DiskQuota,omitempty"` Env []string `json:"Env,omitempty"` diff --git a/cli/update.go b/cli/update.go index a19103a8c5..e063fc8bbb 100644 --- a/cli/update.go +++ b/cli/update.go @@ -18,7 +18,6 @@ type UpdateCommand struct { baseCommand container image string - quota string } // Init initialize update command. @@ -52,7 +51,7 @@ func (uc *UpdateCommand) addFlags() { flagSet.StringSliceVarP(&uc.env, "env", "e", nil, "Set environment variables for container") flagSet.StringSliceVarP(&uc.labels, "label", "l", nil, "Set label for container") flagSet.StringVar(&uc.restartPolicy, "restart", "", "Restart policy to apply when container exits") - flagSet.StringVar(&uc.quota, "quota", "", "Update disk quota for container") + flagSet.StringSliceVar(&uc.diskQuota, "disk-quota", nil, "Update disk quota for container") } // updateRun is the entry of update command. @@ -86,12 +85,17 @@ func (uc *UpdateCommand) updateRun(args []string) error { return err } + diskQuota, err := opts.ParseDiskQuota(uc.diskQuota) + if err != nil { + return err + } + updateConfig := &types.UpdateConfig{ Env: uc.env, Label: uc.labels, RestartPolicy: restartPolicy, Resources: resource, - DiskQuota: uc.quota, + DiskQuota: diskQuota, } apiClient := uc.cli.Client() diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index f1c8be4711..026f721fbf 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -878,6 +878,11 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty return fmt.Errorf("failed to update container %s: can not update kernel memory to a running container, please stop it first", c.ID) } + // update container disk quota + if err := mgr.updateContainerDiskQuota(ctx, c, config.DiskQuota); err != nil { + return errors.Wrapf(err, "failed to update diskquota of container %s", c.ID) + } + c.Lock() // init Container Labels @@ -906,19 +911,18 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty // TODO(ziren): we should use meta.Config.DiskQuota to record container diskquota // compatibility with alidocker, when set DiskQuota for container // add a DiskQuota label - if config.DiskQuota != "" { + if config.DiskQuota != nil { if _, ok := c.Config.Labels["DiskQuota"]; ok { - c.Config.Labels["DiskQuota"] = config.DiskQuota + labels := []string{} + for dir, quota := range c.Config.DiskQuota { + labels = append(labels, fmt.Sprintf("%s=%s", dir, quota)) + } + c.Config.Labels["DiskQuota"] = strings.Join(labels, ";") } } c.Unlock() - // update container disk quota - if err := mgr.updateContainerDiskQuota(ctx, c, config.DiskQuota); err != nil { - return errors.Wrapf(err, "failed to update diskquota of container %s", c.ID) - } - // update Resources of a container. if err := mgr.updateContainerResources(c, config.Resources); err != nil { restore = true @@ -1052,18 +1056,15 @@ func (mgr *ContainerManager) Remove(ctx context.Context, name string, options *t return nil } -func (mgr *ContainerManager) updateContainerDiskQuota(ctx context.Context, c *Container, diskQuota string) error { - if diskQuota == "" { +func (mgr *ContainerManager) updateContainerDiskQuota(ctx context.Context, c *Container, diskQuota map[string]string) error { + if diskQuota == nil { return nil } - quotaMap, err := opts.ParseDiskQuota([]string{diskQuota}) - if err != nil { - return errors.Wrapf(err, "failed to parse disk quota") - } - c.Lock() - c.Config.DiskQuota = quotaMap + for dir, quota := range diskQuota { + c.Config.DiskQuota[dir] = quota + } c.Unlock() // set mount point disk quota @@ -1094,7 +1095,7 @@ func (mgr *ContainerManager) updateContainerDiskQuota(ctx context.Context, c *Co c.Unlock() // get rootfs quota - defaultQuota := quota.GetDefaultQuota(quotaMap) + defaultQuota := quota.GetDefaultQuota(c.Config.DiskQuota) if qid > 0 && defaultQuota == "" { return fmt.Errorf("set quota id but have no set default quota size") } diff --git a/test/cli_update_test.go b/test/cli_update_test.go index a9ca24b136..3f0b7726a9 100644 --- a/test/cli_update_test.go +++ b/test/cli_update_test.go @@ -392,3 +392,46 @@ func (suite *PouchUpdateSuite) TestUpdateContainerDeleteEnv(c *check.C) { c.Errorf("foo=bar env should be deleted from container's env") } } + +// TestUpdateContainerDiskQuota is to verify the correctness of delete env by update interface +func (suite *PouchUpdateSuite) TestUpdateContainerDiskQuota(c *check.C) { + if !environment.IsDiskQuota() { + c.Skip("Host does not support disk quota") + } + + // create container with disk quota + name := "update-container-diskquota" + command.PouchRun("run", "-d", "--disk-quota", "/=2000m", "--name", name, busyboxImage, "top").Assert(c, icmd.Success) + defer DelContainerForceMultyTime(c, name) + + ret := command.PouchRun("exec", name, "df") + //ret.Assert(c, icmd.Success) + out := ret.Combined() + + found := false + for _, line := range strings.Split(out, "\n") { + if strings.Contains(line, "/") && + strings.Contains(line, "2048000") { + found = true + break + } + } + c.Assert(found, check.Equals, true) + + // update diskquota + command.PouchRun("update", "--disk-quota", "/=1000m", name).Assert(c, icmd.Success) + + ret = command.PouchRun("exec", name, "df") + //ret.Assert(c, icmd.Success) + out = ret.Combined() + + found = false + for _, line := range strings.Split(out, "\n") { + if strings.Contains(line, "/") && + strings.Contains(line, "1024000") { + found = true + break + } + } + c.Assert(found, check.Equals, true) +}