From ddea30e40e27ce97166f0ef03650e4c7d67bb653 Mon Sep 17 00:00:00 2001 From: Matt Heon Date: Wed, 10 Apr 2024 13:14:41 -0400 Subject: [PATCH] Add Compat API for Update The Docker endpoint here is kind of a nightmare - accepts a full Resources block, including a large number of scary things like devices. But it only documents (and seems to use) a small subset of those. This implements support for that subset. We can always extend things to implement more later if we have a need. Signed-off-by: Matt Heon --- pkg/api/handlers/compat/containers.go | 121 ++++++++++++++++++++++++++ pkg/api/server/register_containers.go | 28 ++++++ test/apiv2/20-containers.at | 6 ++ test/e2e/update_test.go | 2 +- 4 files changed, 156 insertions(+), 1 deletion(-) diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index e72db326b2..f30600f26b 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -28,6 +28,7 @@ import ( "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" "github.com/docker/go-units" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -661,3 +662,123 @@ func RenameContainer(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNoContent, nil) } + +func UpdateContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + name := utils.GetName(r) + + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + options := new(container.UpdateConfig) + if err := json.NewDecoder(r.Body).Decode(options); err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decoding request body: %w", err)) + return + } + + // Only handle the bits of update that Docker uses as examples. + // For example, the update API claims to be able to update devices for + // existing containers... Which I am very dubious about. + // Ignore bits like that unless someone asks us for them. + + // We're going to be editing this, so we have to deep-copy to not affect + // the container's own resources + resources := new(spec.LinuxResources) + oldResources := ctr.LinuxResources() + if oldResources != nil { + if err := libpod.JSONDeepCopy(oldResources, resources); err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("copying old resource limits: %w", err)) + return + } + } + + // CPU limits + cpu := resources.CPU + if cpu == nil { + cpu = new(spec.LinuxCPU) + } + useCPU := false + if options.CPUShares != 0 { + shares := uint64(options.CPUShares) + cpu.Shares = &shares + useCPU = true + } + if options.CPUPeriod != 0 { + period := uint64(options.CPUPeriod) + cpu.Period = &period + useCPU = true + } + if options.CPUQuota != 0 { + cpu.Quota = &options.CPUQuota + useCPU = true + } + if options.CPURealtimeRuntime != 0 { + cpu.RealtimeRuntime = &options.CPURealtimeRuntime + useCPU = true + } + if options.CPURealtimePeriod != 0 { + period := uint64(options.CPURealtimePeriod) + cpu.RealtimePeriod = &period + useCPU = true + } + if options.CpusetCpus != "" { + cpu.Cpus = options.CpusetCpus + useCPU = true + } + if options.CpusetMems != "" { + cpu.Mems = options.CpusetMems + useCPU = true + } + if useCPU { + resources.CPU = cpu + } + + // Memory limits + mem := resources.Memory + if mem == nil { + mem = new(spec.LinuxMemory) + } + useMem := false + if options.Memory != 0 { + mem.Limit = &options.Memory + useMem = true + } + if options.MemorySwap != 0 { + mem.Swap = &options.MemorySwap + useMem = true + } + if options.MemoryReservation != 0 { + mem.Reservation = &options.MemoryReservation + useMem = true + } + if useMem { + resources.Memory = mem + } + + // PIDs limit + if options.PidsLimit != nil { + if resources.Pids == nil { + resources.Pids = new(spec.LinuxPids) + } + resources.Pids.Limit = *options.PidsLimit + } + + // Blkio Weight + if options.BlkioWeight != 0 { + if resources.BlockIO == nil { + resources.BlockIO = new(spec.LinuxBlockIO) + } + resources.BlockIO.Weight = &options.BlkioWeight + } + + if err := ctr.Update(resources); err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("updating resources: %w", err)) + return + } + + responseStruct := container.ContainerUpdateOKBody{} + utils.WriteResponse(w, http.StatusOK, responseStruct) +} diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 998a70e1dc..dbfc395394 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -675,6 +675,34 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // $ref: "#/responses/internalError" r.HandleFunc(VersionedPath("/containers/{name}/rename"), s.APIHandler(compat.RenameContainer)).Methods(http.MethodPost) r.HandleFunc("/containers/{name}/rename", s.APIHandler(compat.RenameContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{name}/update compat ContainerUpdate + // --- + // tags: + // - containers (compat) + // summary: Update configuration of an existing container + // description: Change configuration settings for an existing container without requiring recreation. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: Full or partial ID or full name of the container to rename + // - in: body + // name: resources + // description: attributes for updating the container + // schema: + // $ref: "#/definitions/UpdateConfig" + // produces: + // - application/json + // responses: + // 200: + // description: no error + // 404: + // $ref: "#/responses/containerNotFound" + // 500: + // $ref: "#/responses/internalError" + r.HandleFunc(VersionedPath("/containers/{name}/update"), s.APIHandler(compat.UpdateContainer)).Methods(http.MethodPost) + r.HandleFunc("/containers/{name}/update", s.APIHandler(compat.UpdateContainer)).Methods(http.MethodPost) /* libpod endpoints diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 3d1f0896bd..e7ffefa1da 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -730,6 +730,12 @@ if root; then eid=$(jq -r '.Id' <<<"$output") t POST exec/$eid/start 200 $cpu_weight_expect + # Now use the compat API + echo '{ "Memory": 536870912 }' >${TMPD}/compatupdate.json + t POST containers/updateCtr/update ${TMPD}/compatupdate.json 200 + t GET libpod/containers/updateCtr/json 200 \ + .HostConfig.Memory=536870912 + podman rm -f updateCtr fi diff --git a/test/e2e/update_test.go b/test/e2e/update_test.go index 89533a022a..3cd64e6c75 100644 --- a/test/e2e/update_test.go +++ b/test/e2e/update_test.go @@ -284,7 +284,7 @@ var _ = Describe("Podman update", func() { Expect(inspect4.OutputToString()).To(Equal("536870912")) exec4 := podmanTest.Podman([]string{"exec", testCtr, "cat", "/sys/fs/cgroup/memory.max"}) - exec3.WaitWithDefaultTimeout() + exec4.WaitWithDefaultTimeout() Expect(exec4).Should(ExitCleanly()) Expect(exec4.OutputToString()).Should(ContainSubstring("536870912")) })