diff options
-rw-r--r-- | libpod/boltdb_state.go | 98 | ||||
-rw-r--r-- | libpod/in_memory_state.go | 40 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 113 | ||||
-rw-r--r-- | libpod/state.go | 13 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers.go | 15 | ||||
-rw-r--r-- | pkg/api/handlers/compat/secrets.go | 32 | ||||
-rw-r--r-- | pkg/api/server/register_secrets.go | 4 | ||||
-rw-r--r-- | pkg/domain/entities/secrets.go | 23 | ||||
-rw-r--r-- | test/apiv2/20-containers.at | 7 | ||||
-rw-r--r-- | test/apiv2/50-secrets.at | 11 | ||||
-rw-r--r-- | test/e2e/rename_test.go | 21 |
11 files changed, 270 insertions, 107 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 6f2eaeab2..122dd080f 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1681,6 +1681,104 @@ func (s *BoltState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConf return err } +// SafeRewriteContainerConfig rewrites a container's configuration in a more +// limited fashion than RewriteContainerConfig. It is marked as safe to use +// under most circumstances, unlike RewriteContainerConfig. +// DO NOT USE TO: Change container dependencies, change pod membership, change +// locks, change container ID. +func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if newName != "" && newCfg.Name != newName { + return errors.Wrapf(define.ErrInvalidArg, "new name %s for container %s must match name in given container config", newName, ctr.ID()) + } + if newName != "" && oldName == "" { + return errors.Wrapf(define.ErrInvalidArg, "must provide old name for container if a new name is given") + } + + newCfgJSON, err := json.Marshal(newCfg) + if err != nil { + return errors.Wrapf(err, "error marshalling new configuration JSON for container %s", ctr.ID()) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + if newName != "" { + idBkt, err := getIDBucket(tx) + if err != nil { + return err + } + namesBkt, err := getNamesBucket(tx) + if err != nil { + return err + } + allCtrsBkt, err := getAllCtrsBucket(tx) + if err != nil { + return err + } + + needsRename := true + if exists := namesBkt.Get([]byte(newName)); exists != nil { + if string(exists) == ctr.ID() { + // Name already associated with the ID + // of this container. No need for a + // rename. + needsRename = false + } else { + return errors.Wrapf(define.ErrCtrExists, "name %s already in use, cannot rename container %s", newName, ctr.ID()) + } + } + + if needsRename { + // We do have to remove the old name. The other + // buckets are ID-indexed so we just need to + // overwrite the values there. + if err := namesBkt.Delete([]byte(oldName)); err != nil { + return errors.Wrapf(err, "error deleting container %s old name from DB for rename", ctr.ID()) + } + if err := idBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil { + return errors.Wrapf(err, "error renaming container %s in ID bucket in DB", ctr.ID()) + } + if err := namesBkt.Put([]byte(newName), []byte(ctr.ID())); err != nil { + return errors.Wrapf(err, "error adding new name %s for container %s in DB", newName, ctr.ID()) + } + if err := allCtrsBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil { + return errors.Wrapf(err, "error renaming container %s in all containers bucket in DB", ctr.ID()) + } + } + } + + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + + ctrDB := ctrBkt.Bucket([]byte(ctr.ID())) + if ctrDB == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "no container with ID %s found in DB", ctr.ID()) + } + + if err := ctrDB.Put(configKey, newCfgJSON); err != nil { + return errors.Wrapf(err, "error updating container %s config JSON", ctr.ID()) + } + + return nil + }) + return err +} + // RewritePodConfig rewrites a pod's configuration. // WARNING: This function is DANGEROUS. Do not use without reading the full // comment on this function in state.go. diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 26f15d9c8..3875878ed 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -822,6 +822,46 @@ func (s *InMemoryState) RewriteContainerConfig(ctr *Container, newCfg *Container return nil } +// SafeRewriteContainerConfig rewrites a container's configuration. +// It's safer than RewriteContainerConfig, but still has limitations. Please +// read the comment in state.go before using. +func (s *InMemoryState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error { + if !ctr.valid { + return define.ErrCtrRemoved + } + + if _, err := s.nameIndex.Get(newName); err == nil { + return errors.Wrapf(define.ErrCtrExists, "name %s is in use", newName) + } + + // If the container does not exist, return error + stateCtr, ok := s.containers[ctr.ID()] + if !ok { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) + } + + // Change name in registry. + if s.namespace != "" { + nsIndex, ok := s.namespaceIndexes[s.namespace] + if !ok { + return define.ErrInternal + } + nsIndex.nameIndex.Release(oldName) + if err := nsIndex.nameIndex.Reserve(newName, ctr.ID()); err != nil { + return errors.Wrapf(err, "error registering name %s", newName) + } + } + s.nameIndex.Release(oldName) + if err := s.nameIndex.Reserve(newName, ctr.ID()); err != nil { + return errors.Wrapf(err, "error registering name %s", newName) + } + + stateCtr.config = newCfg + + return nil +} + // RewritePodConfig rewrites a pod's configuration. // This function is DANGEROUS, even with in-memory state. // Please read the full comment on it in state.go before using it. diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 8bf862bf2..301c4627d 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -74,8 +74,7 @@ func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config } // RenameContainer renames the given container. -// The given container object will be rendered unusable, and a new, renamed -// Container will be returned. +// Returns a copy of the container that has been renamed if successful. func (r *Runtime) RenameContainer(ctx context.Context, ctr *Container, newName string) (*Container, error) { ctr.lock.Lock() defer ctr.lock.Unlock() @@ -88,26 +87,6 @@ func (r *Runtime) RenameContainer(ctx context.Context, ctr *Container, newName s return nil, define.RegexError } - // Check if the name is available. - // This is *100% NOT ATOMIC* so any failures in-flight will do - // *VERY BAD THINGS* to the state. So we have to try and catch all we - // can before starting. - if _, err := r.state.LookupContainerID(newName); err == nil { - return nil, errors.Wrapf(define.ErrCtrExists, "name %s is already in use by another container", newName) - } - if _, err := r.state.LookupPod(newName); err == nil { - return nil, errors.Wrapf(define.ErrPodExists, "name %s is already in use by another pod", newName) - } - - // TODO: Investigate if it is possible to remove this limitation. - depCtrs, err := r.state.ContainerInUse(ctr) - if err != nil { - return nil, err - } - if len(depCtrs) > 0 { - return nil, errors.Wrapf(define.ErrCtrExists, "cannot rename container %s as it is in use by other containers: %v", ctr.ID(), strings.Join(depCtrs, ",")) - } - // We need to pull an updated config, in case another rename fired and // the config was re-written. newConf, err := r.state.GetContainerConfig(ctr.ID()) @@ -116,95 +95,33 @@ func (r *Runtime) RenameContainer(ctx context.Context, ctr *Container, newName s } ctr.config = newConf - // TODO: This is going to fail if we have active exec sessions, too. - // Investigate fixing that at a later date. - - var pod *Pod - if ctr.config.Pod != "" { - tmpPod, err := r.state.Pod(ctr.config.Pod) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving container %s pod", ctr.ID()) - } - pod = tmpPod - // Lock pod to ensure it's not removed while we're working - pod.lock.Lock() - defer pod.lock.Unlock() - } - - // Lock all volumes to ensure they are not removed while we're working - volsLocked := make(map[string]bool) - for _, namedVol := range ctr.config.NamedVolumes { - if volsLocked[namedVol.Name] { - continue - } - vol, err := r.state.Volume(namedVol.Name) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving volume used by container %s", ctr.ID()) - } - - volsLocked[vol.Name()] = true - vol.lock.Lock() - defer vol.lock.Unlock() - } - logrus.Infof("Going to rename container %s from %q to %q", ctr.ID(), ctr.Name(), newName) - // Step 1: remove the old container. - if pod != nil { - if err := r.state.RemoveContainerFromPod(pod, ctr); err != nil { - return nil, errors.Wrapf(err, "error renaming container %s", ctr.ID()) - } - } else { - if err := r.state.RemoveContainer(ctr); err != nil { - return nil, errors.Wrapf(err, "error renaming container %s", ctr.ID()) - } - } - - // Step 2: Make a new container based on the old one. - // TODO: Should we deep-copy the container config and state, to be safe? - newCtr := new(Container) - newCtr.config = ctr.config - newCtr.state = ctr.state - newCtr.lock = ctr.lock - newCtr.ociRuntime = ctr.ociRuntime - newCtr.runtime = r - newCtr.rootlessSlirpSyncR = ctr.rootlessSlirpSyncR - newCtr.rootlessSlirpSyncW = ctr.rootlessSlirpSyncW - newCtr.rootlessPortSyncR = ctr.rootlessPortSyncR - newCtr.rootlessPortSyncW = ctr.rootlessPortSyncW - - newCtr.valid = true - newCtr.config.Name = newName - - // Step 3: Add that new container to the DB - if pod != nil { - if err := r.state.AddContainerToPod(pod, newCtr); err != nil { - return nil, errors.Wrapf(err, "error renaming container %s", newCtr.ID()) - } - } else { - if err := r.state.AddContainer(newCtr); err != nil { - return nil, errors.Wrapf(err, "error renaming container %s", newCtr.ID()) - } - } + // Step 1: Alter the config. Save the old name, we need it to rewrite + // the config. + oldName := ctr.config.Name + ctr.config.Name = newName - // Step 4: Save the new container, to force the state to be written to - // the DB. This may not be necessary, depending on DB implementation, - // but let's do it to be safe. - if err := newCtr.save(); err != nil { - return nil, err + // Step 2: rewrite the old container's config in the DB. + if err := r.state.SafeRewriteContainerConfig(ctr, oldName, ctr.config.Name, ctr.config); err != nil { + // Assume the rename failed. + // Set config back to the old name so reflect what is actually + // present in the DB. + ctr.config.Name = oldName + return nil, errors.Wrapf(err, "error renaming container %s", ctr.ID()) } - // Step 5: rename the container in c/storage. + // Step 3: rename the container in c/storage. // This can fail if the name is already in use by a non-Podman // container. This puts us in a bad spot - we've already renamed the // container in Podman. We can swap the order, but then we have the // opposite problem. Atomicity is a real problem here, with no easy // solution. - if err := r.store.SetNames(newCtr.ID(), []string{newCtr.Name()}); err != nil { + if err := r.store.SetNames(ctr.ID(), []string{ctr.Name()}); err != nil { return nil, err } - return newCtr, nil + return ctr, nil } func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConfig) (*Container, error) { diff --git a/libpod/state.go b/libpod/state.go index 074d21740..4b711bae9 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -155,6 +155,19 @@ type State interface { // answer is this: use this only very sparingly, and only if you really // know what you're doing. RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error + // This is a more limited version of RewriteContainerConfig, though it + // comes with the added ability to alter a container's name. In exchange + // it loses the ability to manipulate the container's locks. + // It is not intended to be as restrictive as RewriteContainerConfig, in + // that we allow it to be run while other Podman processes are running, + // and without holding the alive lock. + // Container ID and pod membership still *ABSOLUTELY CANNOT* be altered. + // Also, you cannot change a container's dependencies - shared namespace + // containers or generic dependencies - at present. This is + // theoretically possible but not yet implemented. + // If newName is not "" the container will be renamed to the new name. + // The oldName parameter is only required if newName is given. + SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error // PLEASE READ THE DESCRIPTION FOR RewriteContainerConfig BEFORE USING. // This function is identical to RewriteContainerConfig, save for the // fact that it is used with pods instead. diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 1e84888af..d26bb50f4 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -321,6 +321,19 @@ func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error Type: portMapping.Protocol, } } + inspect, err := l.Inspect(false) + if err != nil { + return nil, err + } + + n, err := json.Marshal(inspect.NetworkSettings) + if err != nil { + return nil, err + } + networkSettings := types.SummaryNetworkSettings{} + if err := json.Unmarshal(n, &networkSettings); err != nil { + return nil, err + } return &handlers.Container{Container: types.Container{ ID: l.ID(), @@ -339,7 +352,7 @@ func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error NetworkMode string `json:",omitempty"` }{ "host"}, - NetworkSettings: nil, + NetworkSettings: &networkSettings, Mounts: nil, }, ContainerCreateConfig: types.ContainerCreateConfig{}, diff --git a/pkg/api/handlers/compat/secrets.go b/pkg/api/handlers/compat/secrets.go index c5ee8c324..86e3887a4 100644 --- a/pkg/api/handlers/compat/secrets.go +++ b/pkg/api/handlers/compat/secrets.go @@ -40,7 +40,21 @@ func ListSecrets(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, reports) + if utils.IsLibpodRequest(r) { + utils.WriteResponse(w, http.StatusOK, reports) + return + } + // Docker compat expects a version field that increments when the secret is updated + // We currently can't update a secret, so we default the version to 1 + compatReports := make([]entities.SecretInfoReportCompat, 0, len(reports)) + for _, report := range reports { + compatRep := entities.SecretInfoReportCompat{ + SecretInfoReport: *report, + Version: entities.SecretVersion{Index: 1}, + } + compatReports = append(compatReports, compatRep) + } + utils.WriteResponse(w, http.StatusOK, compatReports) } func InspectSecret(w http.ResponseWriter, r *http.Request) { @@ -59,7 +73,21 @@ func InspectSecret(w http.ResponseWriter, r *http.Request) { utils.SecretNotFound(w, name, errs[0]) return } - utils.WriteResponse(w, http.StatusOK, reports[0]) + if len(reports) < 1 { + utils.InternalServerError(w, err) + return + } + if utils.IsLibpodRequest(r) { + utils.WriteResponse(w, http.StatusOK, reports[0]) + return + } + // Docker compat expects a version field that increments when the secret is updated + // We currently can't update a secret, so we default the version to 1 + compatReport := entities.SecretInfoReportCompat{ + SecretInfoReport: *reports[0], + Version: entities.SecretVersion{Index: 1}, + } + utils.WriteResponse(w, http.StatusOK, compatReport) } func RemoveSecret(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/server/register_secrets.go b/pkg/api/server/register_secrets.go index 1c5f5954b..531623845 100644 --- a/pkg/api/server/register_secrets.go +++ b/pkg/api/server/register_secrets.go @@ -115,7 +115,7 @@ func (s *APIServer) registerSecretHandlers(r *mux.Router) error { // parameters: // responses: // '200': - // "$ref": "#/responses/SecretListResponse" + // "$ref": "#/responses/SecretListCompatResponse" // '500': // "$ref": "#/responses/InternalError" r.Handle(VersionedPath("/secrets"), s.APIHandler(compat.ListSecrets)).Methods(http.MethodGet) @@ -158,7 +158,7 @@ func (s *APIServer) registerSecretHandlers(r *mux.Router) error { // - application/json // responses: // '200': - // "$ref": "#/responses/SecretInspectResponse" + // "$ref": "#/responses/SecretInspectCompatResponse" // '404': // "$ref": "#/responses/NoSuchSecret" // '500': diff --git a/pkg/domain/entities/secrets.go b/pkg/domain/entities/secrets.go index 3481cbe05..8ede981da 100644 --- a/pkg/domain/entities/secrets.go +++ b/pkg/domain/entities/secrets.go @@ -42,6 +42,15 @@ type SecretInfoReport struct { Spec SecretSpec } +type SecretInfoReportCompat struct { + SecretInfoReport + Version SecretVersion +} + +type SecretVersion struct { + Index int +} + type SecretSpec struct { Name string Driver SecretDriverSpec @@ -78,6 +87,13 @@ type SwagSecretListResponse struct { Body []*SecretInfoReport } +// Secret list response +// swagger:response SecretListCompatResponse +type SwagSecretListCompatResponse struct { + // in:body + Body []*SecretInfoReportCompat +} + // Secret inspect response // swagger:response SecretInspectResponse type SwagSecretInspectResponse struct { @@ -85,6 +101,13 @@ type SwagSecretInspectResponse struct { Body SecretInfoReport } +// Secret inspect compat +// swagger:response SecretInspectCompatResponse +type SwagSecretInspectCompatResponse struct { + // in:body + Body SecretInfoReportCompat +} + // No such secret // swagger:response NoSuchSecret type SwagErrNoSuchSecret struct { diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 4b40fde80..18364a47d 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -31,6 +31,13 @@ t GET libpod/containers/json?all=true 200 \ .[0].ExitCode=0 \ .[0].IsInfra=false +# Test compat API for Network Settings +t GET /containers/json?all=true 200 \ + length=1 \ + .[0].Id~[0-9a-f]\\{64\\} \ + .[0].Image=$IMAGE \ + .[0].NetworkSettings.Networks.podman.NetworkID=podman + # Make sure `limit` works. t GET libpod/containers/json?limit=1 200 \ length=1 \ diff --git a/test/apiv2/50-secrets.at b/test/apiv2/50-secrets.at index 69e1f3ae9..c4ffb5883 100644 --- a/test/apiv2/50-secrets.at +++ b/test/apiv2/50-secrets.at @@ -14,15 +14,18 @@ t POST secrets/create '"Name":"mysecret","Data":"c2VjcmV0","Labels":{"fail":"fai t POST secrets/create '"Name":"mysecret","Data":"c2VjcmV0"' 409 # secret inspect -t GET secrets/mysecret 200\ - .Spec.Name=mysecret +t GET secrets/mysecret 200 \ + .Spec.Name=mysecret \ + .Version.Index=1 # secret inspect non-existent secret t GET secrets/bogus 404 # secret list -t GET secrets 200\ - length=1 +t GET secrets 200 \ + length=1 \ + .[0].Spec.Name=mysecret \ + .[0].Version.Index=1 # secret list unsupported filters t GET secrets?filters='{"name":["foo1"]}' 400 diff --git a/test/e2e/rename_test.go b/test/e2e/rename_test.go index f19413221..14696c0f6 100644 --- a/test/e2e/rename_test.go +++ b/test/e2e/rename_test.go @@ -89,4 +89,25 @@ var _ = Describe("podman rename", func() { Expect(ps.ExitCode()).To(Equal(0)) Expect(ps.OutputToString()).To(ContainSubstring(newName)) }) + + It("Rename a running container with exec sessions", func() { + ctrName := "testCtr" + ctr := podmanTest.Podman([]string{"run", "-d", "--name", ctrName, ALPINE, "top"}) + ctr.WaitWithDefaultTimeout() + Expect(ctr.ExitCode()).To(Equal(0)) + + exec := podmanTest.Podman([]string{"exec", "-d", ctrName, "top"}) + exec.WaitWithDefaultTimeout() + Expect(exec.ExitCode()).To(Equal(0)) + + newName := "aNewName" + rename := podmanTest.Podman([]string{"rename", ctrName, newName}) + rename.WaitWithDefaultTimeout() + Expect(rename.ExitCode()).To(Equal(0)) + + ps := podmanTest.Podman([]string{"ps", "-aq", "--filter", fmt.Sprintf("name=%s", newName), "--format", "{{ .Names }}"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + Expect(ps.OutputToString()).To(ContainSubstring(newName)) + }) }) |