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 <mheon@redhat.com>
This commit is contained in:
Matt Heon 2024-04-10 13:14:41 -04:00
parent be3f075402
commit ddea30e40e
4 changed files with 156 additions and 1 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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"))
})