diff options
-rw-r--r-- | contrib/rootless-cni-infra/Containerfile | 5 | ||||
-rw-r--r-- | contrib/rootless-cni-infra/README.md | 1 | ||||
-rwxr-xr-x | contrib/rootless-cni-infra/rootless-cni-infra | 9 | ||||
-rw-r--r-- | libpod/rootless_cni_linux.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_attach.go | 6 | ||||
-rw-r--r-- | pkg/api/handlers/compat/exec.go | 6 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 47 | ||||
-rw-r--r-- | pkg/api/server/idle/tracker.go | 96 | ||||
-rw-r--r-- | pkg/api/server/idletracker/idletracker.go | 74 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 2 | ||||
-rw-r--r-- | pkg/api/server/server.go | 22 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/images.go | 8 | ||||
-rw-r--r-- | test/e2e/untag_test.go | 1 |
13 files changed, 158 insertions, 121 deletions
diff --git a/contrib/rootless-cni-infra/Containerfile b/contrib/rootless-cni-infra/Containerfile index 6bf70d644..dd80fda28 100644 --- a/contrib/rootless-cni-infra/Containerfile +++ b/contrib/rootless-cni-infra/Containerfile @@ -2,8 +2,7 @@ ARG GOLANG_VERSION=1.15 ARG ALPINE_VERSION=3.12 ARG CNI_VERSION=v0.8.0 ARG CNI_PLUGINS_VERSION=v0.8.7 -# Aug 20, 2020 -ARG DNSNAME_VESION=78b4da7bbfc51c27366da630e1df1c4f2e8b1b5b +ARG DNSNAME_VESION=v1.0.0 FROM golang:${GOLANG_VERSION}-alpine${ALPINE_VERSION} AS golang-base RUN apk add --no-cache git @@ -34,4 +33,4 @@ COPY rootless-cni-infra /usr/local/bin ENV CNI_PATH=/opt/cni/bin CMD ["sleep", "infinity"] -ENV ROOTLESS_CNI_INFRA_VERSION=2 +ENV ROOTLESS_CNI_INFRA_VERSION=3 diff --git a/contrib/rootless-cni-infra/README.md b/contrib/rootless-cni-infra/README.md index 5aa13374b..c43b4cf49 100644 --- a/contrib/rootless-cni-infra/README.md +++ b/contrib/rootless-cni-infra/README.md @@ -22,3 +22,4 @@ The container images live on `quay.io/libpod/rootless-cni-infra`. The tags have * `/run/rootless-cni-infra/${CONTAINER_ID}/pid`: PID of the `sleep infinity` process that corresponds to the allocated netns * `/run/rootless-cni-infra/${CONTAINER_ID}/attached/${NETWORK_NAME}`: CNI result +* `/run/rootless-cni-infra/${CONTAINER_ID}/attached-args/${NETWORK_NAME}`: CNI args diff --git a/contrib/rootless-cni-infra/rootless-cni-infra b/contrib/rootless-cni-infra/rootless-cni-infra index 5cb43621d..463254c7f 100755 --- a/contrib/rootless-cni-infra/rootless-cni-infra +++ b/contrib/rootless-cni-infra/rootless-cni-infra @@ -33,7 +33,7 @@ cmd_entrypoint_alloc() { K8S_POD_NAME="$3" dir="${BASE}/${ID}" - mkdir -p "${dir}/attached" + mkdir -p "${dir}/attached" "${dir}/attached-args" pid="" if [ -f "${dir}/pid" ]; then @@ -50,6 +50,7 @@ cmd_entrypoint_alloc() { CNI_IFNAME="eth${nwcount}" export CNI_ARGS CNI_IFNAME cnitool add "${NET}" "/proc/${pid}/ns/net" >"${dir}/attached/${NET}" + echo "${CNI_ARGS}" >"${dir}/attached-args/${NET}" # return the result ns="/proc/${pid}/ns/net" @@ -71,8 +72,12 @@ cmd_entrypoint_dealloc() { exit 0 fi pid=$(cat "${dir}/pid") + if [ -f "${dir}/attached-args/${NET}" ]; then + CNI_ARGS=$(cat "${dir}/attached-args/${NET}") + export CNI_ARGS + fi cnitool del "${NET}" "/proc/${pid}/ns/net" - rm -f "${dir}/attached/${NET}" + rm -f "${dir}/attached/${NET}" "${dir}/attached-args/${NET}" nwcount=$(find "${dir}/attached" -type f | wc -l) if [ "${nwcount}" = 0 ]; then diff --git a/libpod/rootless_cni_linux.go b/libpod/rootless_cni_linux.go index 2877191e5..21e43ebd0 100644 --- a/libpod/rootless_cni_linux.go +++ b/libpod/rootless_cni_linux.go @@ -25,7 +25,7 @@ import ( // Built from ../contrib/rootless-cni-infra. var rootlessCNIInfraImage = map[string]string{ - "amd64": "quay.io/libpod/rootless-cni-infra@sha256:e92c3a6367f8e554121b96d39af1f19f0f9ac5a32922b290112e13bc661d3a29", // 2-amd64 + "amd64": "quay.io/libpod/rootless-cni-infra@sha256:304742d5d221211df4ec672807a5842ff11e3729c50bc424ea0cea858f69d7b7", // 3-amd64 } const ( diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index e20d48d86..4a1196c89 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -6,7 +6,7 @@ import ( "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/api/handlers/utils" - "github.com/containers/podman/v2/pkg/api/server/idletracker" + "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -92,7 +92,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { return } - idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker) + idleTracker := r.Context().Value("idletracker").(*idle.Tracker) hijackChan := make(chan bool, 1) // Perform HTTP attach. @@ -109,7 +109,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { // We do need to tell the idle tracker that the // connection has been closed, though. We can guarantee // that is true after HTTPAttach exits. - idleTracker.TrackHijackedClosed() + idleTracker.Close() } else { // A hijack was not successfully completed. We need to // report the error normally. diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index 1db950f85..df51293c2 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -10,7 +10,7 @@ import ( "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/handlers/utils" - "github.com/containers/podman/v2/pkg/api/server/idletracker" + "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/containers/podman/v2/pkg/specgen/generate" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -174,7 +174,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { return } - idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker) + idleTracker := r.Context().Value("idletracker").(*idle.Tracker) hijackChan := make(chan bool, 1) if err := sessionCtr.ExecHTTPStartAndAttach(sessionID, r, w, nil, nil, nil, hijackChan); err != nil { @@ -186,7 +186,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { // We do need to tell the idle tracker that the // connection has been closed, though. We can guarantee // that is true after HTTPAttach exits. - idleTracker.TrackHijackedClosed() + idleTracker.Close() } else { // A hijack was not successfully completed. We need to // report the error normally. diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index bc1bdc287..2f9c10746 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -560,24 +560,41 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { func UntagImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) - name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) - return - } - tag := "latest" - if len(r.Form.Get("tag")) > 0 { - tag = r.Form.Get("tag") - } - if len(r.Form.Get("repo")) < 1 { + tags := []string{} // Note: if empty, all tags will be removed from the image. + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + + // Do the parameter dance. + switch { + // If tag is set, repo must be as well. + case len(repo) == 0 && len(tag) > 0: utils.Error(w, "repo tag is required", http.StatusBadRequest, errors.New("repo parameter is required to tag an image")) return + + case len(repo) == 0: + break + + // If repo is specified, we need to add that to the tags. + default: + if len(tag) == 0 { + // Normalize tag to "latest" if empty. + tag = "latest" + } + tags = append(tags, fmt.Sprintf("%s:%s", repo, tag)) } - repo := r.Form.Get("repo") - tagName := fmt.Sprintf("%s:%s", repo, tag) - if err := newImage.UntagImage(tagName); err != nil { - utils.Error(w, "failed to untag", http.StatusInternalServerError, err) + + // Now use the ABI implementation to prevent us from having duplicate + // code. + opts := entities.ImageUntagOptions{} + imageEngine := abi.ImageEngine{Libpod: runtime} + + name := utils.GetName(r) + if err := imageEngine.Untag(r.Context(), name, tags, opts); err != nil { + if errors.Cause(err) == define.ErrNoSuchImage { + utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) + } else { + utils.Error(w, "failed to untag", http.StatusInternalServerError, err) + } return } utils.WriteResponse(w, http.StatusCreated, "") diff --git a/pkg/api/server/idle/tracker.go b/pkg/api/server/idle/tracker.go new file mode 100644 index 000000000..1b378c492 --- /dev/null +++ b/pkg/api/server/idle/tracker.go @@ -0,0 +1,96 @@ +package idle + +import ( + "net" + "net/http" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +// Tracker holds the state for the server's idle tracking +type Tracker struct { + // Duration is the API idle window + Duration time.Duration + hijacked int // count of active connections managed by handlers + managed map[net.Conn]struct{} // set of active connections managed by http package + mux sync.Mutex // protect managed map + timer *time.Timer + total int // total number of connections made to this server instance +} + +// NewTracker creates and initializes a new Tracker object +// For best behavior, duration should be 2x http idle connection timeout +func NewTracker(idle time.Duration) *Tracker { + return &Tracker{ + managed: make(map[net.Conn]struct{}), + Duration: idle, + timer: time.NewTimer(idle), + } +} + +// ConnState is called on HTTP connection state changes. +// - Once StateHijacked, StateClose is _NOT_ called on that connection +// - There are two "idle" timeouts, the http idle connection (not to be confused with the TCP/IP idle socket timeout) +// and the API idle window. The caller should set the http idle timeout to 2x the time provided to NewTacker() which +// is the API idle window. +func (t *Tracker) ConnState(conn net.Conn, state http.ConnState) { + t.mux.Lock() + defer t.mux.Unlock() + + logrus.Debugf("IdleTracker %p:%v %dm+%dh/%dt connection(s)", conn, state, len(t.managed), t.hijacked, t.TotalConnections()) + switch state { + case http.StateNew, http.StateActive: + // stop the API timer when the server transitions any connection to an "active" state + t.managed[conn] = struct{}{} + t.timer.Stop() + t.total++ + case http.StateHijacked: + // hijacked connections should call Close() when finished. + // Note: If a handler hijack's a connection and then doesn't Close() it, + // the API timer will not fire and the server will _NOT_ timeout. + delete(t.managed, conn) + t.hijacked++ + case http.StateIdle: + // When any connection goes into the http idle state, we know: + // - we have an active connection + // - the API timer should not be counting down (See case StateNew/StateActive) + break + case http.StateClosed: + oldActive := t.ActiveConnections() + + // Either the server or a hijacking handler has closed the http connection to a client + if _, found := t.managed[conn]; found { + delete(t.managed, conn) + } else { + t.hijacked-- // guarded by t.mux above + } + + // Transitioned from any "active" connection to no connections + if oldActive > 0 && t.ActiveConnections() == 0 { + t.timer.Stop() // See library source for Reset() issues and why they are not fixed + t.timer.Reset(t.Duration) // Restart the API window timer + } + } +} + +// Close is used to update Tracker that a StateHijacked connection has been closed by handler (StateClosed) +func (t *Tracker) Close() { + t.ConnState(nil, http.StateClosed) +} + +// ActiveConnections returns the number of current managed or StateHijacked connections +func (t *Tracker) ActiveConnections() int { + return len(t.managed) + t.hijacked +} + +// TotalConnections returns total number of connections made to this instance of the service +func (t *Tracker) TotalConnections() int { + return t.total +} + +// Done is called when idle timer has expired +func (t *Tracker) Done() <-chan time.Time { + return t.timer.C +} diff --git a/pkg/api/server/idletracker/idletracker.go b/pkg/api/server/idletracker/idletracker.go deleted file mode 100644 index 1ee905a99..000000000 --- a/pkg/api/server/idletracker/idletracker.go +++ /dev/null @@ -1,74 +0,0 @@ -package idletracker - -import ( - "net" - "net/http" - "sync" - "time" - - "github.com/sirupsen/logrus" -) - -type IdleTracker struct { - http map[net.Conn]struct{} - hijacked int - total int - mux sync.Mutex - timer *time.Timer - Duration time.Duration -} - -func NewIdleTracker(idle time.Duration) *IdleTracker { - return &IdleTracker{ - http: make(map[net.Conn]struct{}), - Duration: idle, - timer: time.NewTimer(idle), - } -} - -func (t *IdleTracker) ConnState(conn net.Conn, state http.ConnState) { - t.mux.Lock() - defer t.mux.Unlock() - - oldActive := t.ActiveConnections() - logrus.Debugf("IdleTracker %p:%v %d/%d connection(s)", conn, state, oldActive, t.TotalConnections()) - switch state { - case http.StateNew, http.StateActive: - t.http[conn] = struct{}{} - // stop the timer if we transitioned from idle - if oldActive == 0 { - t.timer.Stop() - } - t.total++ - case http.StateHijacked: - // hijacked connections are handled elsewhere - delete(t.http, conn) - t.hijacked++ - case http.StateIdle, http.StateClosed: - delete(t.http, conn) - // Restart the timer if we've become idle - if oldActive > 0 && len(t.http) == 0 { - t.timer.Stop() - t.timer.Reset(t.Duration) - } - } -} - -func (t *IdleTracker) TrackHijackedClosed() { - t.mux.Lock() - defer t.mux.Unlock() - - t.hijacked-- -} - -func (t *IdleTracker) ActiveConnections() int { - return len(t.http) + t.hijacked -} - -func (t *IdleTracker) TotalConnections() int { - return t.total -} - -func (t *IdleTracker) Done() <-chan time.Time { - return t.timer.C -} diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index b1007fe09..32e401a54 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -1175,7 +1175,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // tags: // - images // summary: Untag an image - // description: Untag an image + // description: Untag an image. If not repo and tag are specified, all tags are removed from the image. // parameters: // - in: path // name: name:.* diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index e7c031234..09a9f6370 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -16,7 +16,7 @@ import ( "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/pkg/api/handlers" - "github.com/containers/podman/v2/pkg/api/server/idletracker" + "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/coreos/go-systemd/v22/activation" "github.com/coreos/go-systemd/v22/daemon" "github.com/gorilla/mux" @@ -26,14 +26,14 @@ import ( ) type APIServer struct { - http.Server // The HTTP work happens here - *schema.Decoder // Decoder for Query parameters to structs - context.Context // Context to carry objects to handlers - *libpod.Runtime // Where the real work happens - net.Listener // mux for routing HTTP API calls to libpod routines - context.CancelFunc // Stop APIServer - idleTracker *idletracker.IdleTracker // Track connections to support idle shutdown - pprof *http.Server // Sidecar http server for providing performance data + http.Server // The HTTP work happens here + *schema.Decoder // Decoder for Query parameters to structs + context.Context // Context to carry objects to handlers + *libpod.Runtime // Where the real work happens + net.Listener // mux for routing HTTP API calls to libpod routines + context.CancelFunc // Stop APIServer + idleTracker *idle.Tracker // Track connections to support idle shutdown + pprof *http.Server // Sidecar http server for providing performance data } // Number of seconds to wait for next request, if exceeded shutdown server @@ -70,13 +70,13 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li } router := mux.NewRouter().UseEncodedPath() - idle := idletracker.NewIdleTracker(duration) + idle := idle.NewTracker(duration) server := APIServer{ Server: http.Server{ Handler: router, ReadHeaderTimeout: 20 * time.Second, - IdleTimeout: duration, + IdleTimeout: duration * 2, ConnState: idle.ConnState, ErrorLog: log.New(logrus.StandardLogger().Out, "", 0), }, diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 981884109..61ac2141c 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -9,7 +9,6 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" - "github.com/containers/podman/v2/pkg/bindings" images "github.com/containers/podman/v2/pkg/bindings/images" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/utils" @@ -139,13 +138,8 @@ func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, } func (ir *ImageEngine) Untag(ctx context.Context, nameOrID string, tags []string, options entities.ImageUntagOptions) error { - // Remove all tags if none are provided if len(tags) == 0 { - newImage, err := images.GetImage(ir.ClientCxt, nameOrID, bindings.PFalse) - if err != nil { - return err - } - tags = newImage.NamesHistory + return images.Untag(ir.ClientCxt, nameOrID, "", "") } for _, newTag := range tags { diff --git a/test/e2e/untag_test.go b/test/e2e/untag_test.go index 7766ce634..91a933090 100644 --- a/test/e2e/untag_test.go +++ b/test/e2e/untag_test.go @@ -33,7 +33,6 @@ var _ = Describe("Podman untag", func() { }) It("podman untag all", func() { - SkipIfRemote("FIXME This should work on podman-remote") setup := podmanTest.PodmanNoCache([]string{"pull", ALPINE}) setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) |