diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/adapter/containers.go | 13 | ||||
-rw-r--r-- | pkg/adapter/network.go | 1 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/volumes.go | 137 | ||||
-rw-r--r-- | pkg/api/handlers/types.go | 13 | ||||
-rw-r--r-- | pkg/api/server/register_volumes.go | 43 | ||||
-rw-r--r-- | pkg/api/server/server.go | 49 | ||||
-rw-r--r-- | pkg/api/server/swagger.go | 17 | ||||
-rw-r--r-- | pkg/bindings/containers/create.go | 2 | ||||
-rw-r--r-- | pkg/bindings/test/common_test.go | 4 | ||||
-rw-r--r-- | pkg/bindings/test/containers_test.go | 253 | ||||
-rw-r--r-- | pkg/bindings/test/volumes_test.go | 174 | ||||
-rw-r--r-- | pkg/bindings/volumes/volumes.go | 52 | ||||
-rw-r--r-- | pkg/network/netconflist.go | 1 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 51 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.go | 2 | ||||
-rw-r--r-- | pkg/spec/namespaces.go | 2 | ||||
-rw-r--r-- | pkg/specgen/namespaces.go | 2 | ||||
-rw-r--r-- | pkg/util/utils_supported.go | 4 |
18 files changed, 725 insertions, 95 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 78057e3f9..08e19edb8 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -469,6 +469,10 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode logrus.Debugf("unable to remove container %s after failing to start and attach to it", ctr.ID()) } } + if errors.Cause(err) == define.ErrWillDeadlock { + logrus.Debugf("Deadlock error: %v", err) + return define.ExitCode(err), errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID()) + } return define.ExitCode(err), err } @@ -702,6 +706,11 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP return exitCode, nil } + if errors.Cause(err) == define.ErrWillDeadlock { + logrus.Debugf("Deadlock error: %v", err) + return define.ExitCode(err), errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID()) + } + if ctrRunning { return 0, err } @@ -735,6 +744,10 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } + if errors.Cause(err) == define.ErrWillDeadlock { + lastError = errors.Wrapf(err, "please run 'podman system renumber' to resolve deadlocks") + continue + } lastError = errors.Wrapf(err, "unable to start container %q", container) continue } diff --git a/pkg/adapter/network.go b/pkg/adapter/network.go index c5bd91534..b25f54a13 100644 --- a/pkg/adapter/network.go +++ b/pkg/adapter/network.go @@ -209,6 +209,7 @@ func (r *LocalRuntime) NetworkCreateBridge(cli *cliconfig.NetworkCreateValues) ( bridge := network.NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig) plugins = append(plugins, bridge) plugins = append(plugins, network.NewPortMapPlugin()) + plugins = append(plugins, network.NewFirewallPlugin()) // if we find the dnsname plugin, we add configuration for it if network.HasDNSNamePlugin(runtimeConfig.CNIPluginDir) && !cli.DisableDNS { // Note: in the future we might like to allow for dynamic domain names diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index 7e7e46718..9b10ee890 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -3,9 +3,11 @@ package libpod import ( "encoding/json" "net/http" + "strings" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/gorilla/schema" @@ -29,7 +31,6 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } - // decode params from body if err := json.NewDecoder(r.Body).Decode(&input); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) @@ -49,14 +50,21 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { parsedOptions, err := shared.ParseVolumeOptions(input.Opts) if err != nil { utils.InternalServerError(w, err) + return } volumeOptions = append(volumeOptions, parsedOptions...) } vol, err := runtime.NewVolume(r.Context(), volumeOptions...) if err != nil { utils.InternalServerError(w, err) + return + } + config, err := vol.Config() + if err != nil { + utils.InternalServerError(w, err) + return } - utils.WriteResponse(w, http.StatusOK, vol.Name()) + utils.WriteResponse(w, http.StatusOK, config) } func InspectVolume(w http.ResponseWriter, r *http.Request) { @@ -76,25 +84,46 @@ func InspectVolume(w http.ResponseWriter, r *http.Request) { } func ListVolumes(w http.ResponseWriter, r *http.Request) { - //var ( - // runtime = r.Context().Value("runtime").(*libpod.Runtime) - // decoder = r.Context().Value("decoder").(*schema.Decoder) - //) - //query := struct { - // Filter string `json:"filter"` - //}{ - // // override any golang type defaults - //} - // - //if err := decoder.Decode(&query, r.URL.Query()); err != nil { - // utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - // errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) - // return - //} - /* - This is all in main in cmd and needs to be extracted from there first. - */ + var ( + decoder = r.Context().Value("decoder").(*schema.Decoder) + err error + runtime = r.Context().Value("runtime").(*libpod.Runtime) + volumeConfigs []*libpod.VolumeConfig + volumeFilters []libpod.VolumeFilter + ) + query := struct { + Filters map[string][]string `schema:"filters"` + }{ + // override any golang type defaults + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + if len(query.Filters) > 0 { + volumeFilters, err = generateVolumeFilters(query.Filters) + if err != nil { + utils.InternalServerError(w, err) + return + } + } + vols, err := runtime.Volumes(volumeFilters...) + if err != nil { + utils.InternalServerError(w, err) + return + } + for _, v := range vols { + config, err := v.Config() + if err != nil { + utils.InternalServerError(w, err) + return + } + volumeConfigs = append(volumeConfigs, config) + } + utils.WriteResponse(w, http.StatusOK, volumeConfigs) } func PruneVolumes(w http.ResponseWriter, r *http.Request) { @@ -133,9 +162,77 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) { vol, err := runtime.LookupVolume(name) if err != nil { utils.VolumeNotFound(w, name, err) + return } if err := runtime.RemoveVolume(r.Context(), vol, query.Force); err != nil { + if errors.Cause(err) == define.ErrVolumeBeingUsed { + utils.Error(w, "volumes being used", http.StatusConflict, err) + return + } utils.InternalServerError(w, err) + return } utils.WriteResponse(w, http.StatusNoContent, "") } + +func generateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter, error) { + var vf []libpod.VolumeFilter + for filter, v := range filters { + for _, val := range v { + switch filter { + case "name": + nameVal := val + vf = append(vf, func(v *libpod.Volume) bool { + return nameVal == v.Name() + }) + case "driver": + driverVal := val + vf = append(vf, func(v *libpod.Volume) bool { + return v.Driver() == driverVal + }) + case "scope": + scopeVal := val + vf = append(vf, func(v *libpod.Volume) bool { + return v.Scope() == scopeVal + }) + case "label": + filterArray := strings.SplitN(val, "=", 2) + filterKey := filterArray[0] + var filterVal string + if len(filterArray) > 1 { + filterVal = filterArray[1] + } else { + filterVal = "" + } + vf = append(vf, func(v *libpod.Volume) bool { + for labelKey, labelValue := range v.Labels() { + if labelKey == filterKey && ("" == filterVal || labelValue == filterVal) { + return true + } + } + return false + }) + case "opt": + filterArray := strings.SplitN(val, "=", 2) + filterKey := filterArray[0] + var filterVal string + if len(filterArray) > 1 { + filterVal = filterArray[1] + } else { + filterVal = "" + } + vf = append(vf, func(v *libpod.Volume) bool { + for labelKey, labelValue := range v.Options() { + if labelKey == filterKey && ("" == filterVal || labelValue == filterVal) { + return true + } + } + return false + }) + default: + return nil, errors.Errorf("%q is in an invalid volume filter", filter) + } + } + } + return vf, nil +} diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index c72b0f817..2930a9567 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -128,11 +128,16 @@ type CreateContainerConfig struct { NetworkingConfig dockerNetwork.NetworkingConfig } +// swagger:model VolumeCreate type VolumeCreateConfig struct { - Name string `json:"name"` - Driver string `schema:"driver"` - Label map[string]string `schema:"label"` - Opts map[string]string `schema:"opts"` + // New volume's name. Can be left blank + Name string `schema:"name"` + // Volume driver to use + Driver string `schema:"driver"` + // User-defined key/value metadata. + Label map[string]string `schema:"label"` + // Mapping of driver options and values. + Opts map[string]string `schema:"opts"` } type IDResponse struct { diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go index efe56a3ad..d1317904b 100644 --- a/pkg/api/server/register_volumes.go +++ b/pkg/api/server/register_volumes.go @@ -11,15 +11,42 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // swagger:operation POST /libpod/volumes/create volumes createVolume // --- // summary: Create a volume + // parameters: + // - in: body + // name: create + // description: attributes for creating a container + // schema: + // $ref: "#/definitions/VolumeCreate" + // produces: + // - application/json + // responses: + // '201': + // $ref: "#/responses/VolumeCreateResponse" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/volumes/create"), s.APIHandler(libpod.CreateVolume)).Methods(http.MethodPost) + // swagger:operation POST /libpod/volumes/json volumes listVolumes + // --- + // summary: List volumes + // description: Returns a list of networks // produces: // - application/json + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // JSON encoded value of the filters (a map[string][]string) to process on the networks list. Available filters: + // - driver=<volume-driver-name> Matches volumes based on their driver. + // - label=<key> or label=<key>:<value> Matches volumes based on the presence of a label alone or a label and a value. + // - name=<volume-name> Matches all of volume name. + // - opt=<driver-option> Matches a storage driver options // responses: // '200': - // description: tbd + // "$ref": "#/responses/VolumeList" // '500': // "$ref": "#/responses/InternalError" - r.Handle("/libpod/volumes/create", s.APIHandler(libpod.CreateVolume)).Methods(http.MethodPost) - r.Handle("/libpod/volumes/json", s.APIHandler(libpod.ListVolumes)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/volumes/json"), s.APIHandler(libpod.ListVolumes)).Methods(http.MethodGet) // swagger:operation POST /libpod/volumes/prune volumes pruneVolumes // --- // summary: Prune volumes @@ -30,7 +57,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // description: no error // '500': // "$ref": "#/responses/InternalError" - r.Handle("/libpod/volumes/prune", s.APIHandler(libpod.PruneVolumes)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/volumes/prune"), s.APIHandler(libpod.PruneVolumes)).Methods(http.MethodPost) // swagger:operation GET /libpod/volumes/{name}/json volumes inspectVolume // --- // summary: Inspect volume @@ -49,7 +76,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // "$ref": "#/responses/NoSuchVolume" // '500': // "$ref": "#/responses/InternalError" - r.Handle("/libpod/volumes/{name}/json", s.APIHandler(libpod.InspectVolume)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/volumes/{name}/json"), s.APIHandler(libpod.InspectVolume)).Methods(http.MethodGet) // swagger:operation DELETE /libpod/volumes/{name} volumes removeVolume // --- // summary: Remove volume @@ -68,12 +95,12 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // responses: // 204: // description: no error - // 400: - // $ref: "#/responses/BadParamError" // 404: // $ref: "#/responses/NoSuchVolume" + // 409: + // description: Volume is in use and cannot be removed // 500: // $ref: "#/responses/InternalError" - r.Handle("/libpod/volumes/{name}", s.APIHandler(libpod.RemoveVolume)).Methods(http.MethodDelete) + r.Handle(VersionedPath("/libpod/volumes/{name}"), s.APIHandler(libpod.RemoveVolume)).Methods(http.MethodDelete) return nil } diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index e7b2a5525..a5922e5d7 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -140,36 +140,31 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li func (s *APIServer) Serve() error { // stalker to count the connections. Should the timer expire it will shutdown the service. go func() { - for { - select { - case delta := <-s.ConnectionCh: - // Always stop the current timer, things will change... + for delta := range s.ConnectionCh { + switch delta { + case EnterHandler: s.Timer.Stop() - switch delta { - case EnterHandler: - s.ActiveConnections += 1 - s.TotalConnections += 1 - case ExitHandler: - s.ActiveConnections -= 1 - if s.ActiveConnections == 0 { - // Server will be shutdown iff the timer expires before being reset or stopped - s.Timer = time.AfterFunc(s.Duration, func() { - if err := s.Shutdown(); err != nil { - logrus.Errorf("Failed to shutdown APIServer: %v", err) - os.Exit(1) - } - }) - } else { - s.Timer.Reset(s.Duration) - } - case NOOPHandler: - // push the check out another duration... + s.ActiveConnections += 1 + s.TotalConnections += 1 + case ExitHandler: + s.Timer.Stop() + s.ActiveConnections -= 1 + if s.ActiveConnections == 0 { + // Server will be shutdown iff the timer expires before being reset or stopped + s.Timer = time.AfterFunc(s.Duration, func() { + if err := s.Shutdown(); err != nil { + logrus.Errorf("Failed to shutdown APIServer: %v", err) + os.Exit(1) + } + }) + } else { s.Timer.Reset(s.Duration) - default: - logrus.Errorf("ConnectionCh received unsupported input %d", delta) } + case NOOPHandler: + // push the check out another duration... + s.Timer.Reset(s.Duration) default: - time.Sleep(1 * time.Second) + logrus.Errorf("ConnectionCh received unsupported input %d", delta) } } }() @@ -212,7 +207,7 @@ func (s *APIServer) Shutdown() error { go func() { err := s.Server.Shutdown(ctx) - if err != nil && err != context.Canceled { + if err != nil && err != context.Canceled && err != http.ErrServerClosed { logrus.Errorf("Failed to cleanly shutdown APIServer: %s", err.Error()) } }() diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index fc409d816..011196e5a 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -1,6 +1,7 @@ package server import ( + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" ) @@ -139,3 +140,19 @@ type ok struct { ok string } } + +// Volume create response +// swagger:response VolumeCreateResponse +type swagVolumeCreateResponse struct { + // in:body + Body struct { + libpod.VolumeConfig + } +} + +// Volume list +// swagger:response VolumeList +type swagVolumeListResponse struct { + // in:body + Body []libpod.Volume +} diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go index 2943cb522..43a3ef02d 100644 --- a/pkg/bindings/containers/create.go +++ b/pkg/bindings/containers/create.go @@ -19,7 +19,7 @@ func CreateWithSpec(ctx context.Context, s specgen.SpecGenerator) (utils.Contain } specgenString, err := jsoniter.MarshalToString(s) if err != nil { - return ccr, nil + return ccr, err } stringReader := strings.NewReader(specgenString) response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil) diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index 38f5014ca..1fc774074 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -240,3 +240,7 @@ func createCache() { } b.cleanup() } + +func isStopped(state string) bool { + return state == "exited" || state == "stopped" +} diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go new file mode 100644 index 000000000..5a0bdebe6 --- /dev/null +++ b/pkg/bindings/test/containers_test.go @@ -0,0 +1,253 @@ +package test_bindings + +import ( + "context" + "net/http" + "strconv" + "time" + + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman containers ", func() { + var ( + bt *bindingTest + s *gexec.Session + connText context.Context + err error + falseFlag bool = false + trueFlag bool = true + ) + + BeforeEach(func() { + bt = newBindingTest() + bt.RestoreImagesFromCache() + s = bt.startAPIService() + time.Sleep(1 * time.Second) + connText, err = bindings.NewConnection(context.Background(), bt.sock) + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("podman pause a bogus container", func() { + // Pausing bogus container should return 404 + err = containers.Pause(connText, "foobar") + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + }) + + It("podman unpause a bogus container", func() { + // Unpausing bogus container should return 404 + err = containers.Unpause(connText, "foobar") + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + }) + + It("podman pause a running container by name", func() { + // Pausing by name should work + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + err := containers.Pause(connText, name) + Expect(err).To(BeNil()) + + // Ensure container is paused + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + Expect(data.State.Status).To(Equal("paused")) + }) + + It("podman pause a running container by id", func() { + // Pausing by id should work + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + err = containers.Pause(connText, data.ID) + Expect(err).To(BeNil()) + + // Ensure container is paused + data, err = containers.Inspect(connText, data.ID, nil) + Expect(data.State.Status).To(Equal("paused")) + }) + + It("podman unpause a running container by name", func() { + // Unpausing by name should work + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + err := containers.Pause(connText, name) + Expect(err).To(BeNil()) + err = containers.Unpause(connText, name) + Expect(err).To(BeNil()) + + // Ensure container is unpaused + data, err := containers.Inspect(connText, name, nil) + Expect(data.State.Status).To(Equal("running")) + }) + + It("podman unpause a running container by ID", func() { + // Unpausing by ID should work + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + // Pause by name + err := containers.Pause(connText, name) + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + err = containers.Unpause(connText, data.ID) + Expect(err).To(BeNil()) + + // Ensure container is unpaused + data, err = containers.Inspect(connText, name, nil) + Expect(data.State.Status).To(Equal("running")) + }) + + It("podman pause a paused container by name", func() { + // Pausing a paused container by name should fail + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + err := containers.Pause(connText, name) + Expect(err).To(BeNil()) + err = containers.Pause(connText, name) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("podman pause a paused container by id", func() { + // Pausing a paused container by id should fail + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + err = containers.Pause(connText, data.ID) + Expect(err).To(BeNil()) + err = containers.Pause(connText, data.ID) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("podman pause a stopped container by name", func() { + // Pausing a stopped container by name should fail + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + err := containers.Stop(connText, name, nil) + Expect(err).To(BeNil()) + err = containers.Pause(connText, name) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("podman pause a stopped container by id", func() { + // Pausing a stopped container by id should fail + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + data, err := containers.Inspect(connText, name, nil) + err = containers.Stop(connText, data.ID, nil) + Expect(err).To(BeNil()) + err = containers.Pause(connText, data.ID) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("podman remove a paused container by id without force", func() { + // Removing a paused container without force should fail + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + err = containers.Pause(connText, data.ID) + Expect(err).To(BeNil()) + err = containers.Remove(connText, data.ID, &falseFlag, &falseFlag) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("podman remove a paused container by id with force", func() { + // FIXME: Skip on F31 and later + host := utils.GetHostDistributionInfo() + osVer, err := strconv.Atoi(host.Version) + Expect(err).To(BeNil()) + if host.Distribution == "fedora" && osVer >= 31 { + Skip("FIXME: https://github.com/containers/libpod/issues/5325") + } + + // Removing a paused container with force should work + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + err = containers.Pause(connText, data.ID) + Expect(err).To(BeNil()) + err = containers.Remove(connText, data.ID, &trueFlag, &falseFlag) + Expect(err).To(BeNil()) + }) + + It("podman stop a paused container by name", func() { + // Stopping a paused container by name should fail + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + err := containers.Pause(connText, name) + Expect(err).To(BeNil()) + err = containers.Stop(connText, name, nil) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("podman stop a paused container by id", func() { + // Stopping a paused container by id should fail + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + err = containers.Pause(connText, data.ID) + Expect(err).To(BeNil()) + err = containers.Stop(connText, data.ID, nil) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("podman stop a running container by name", func() { + // Stopping a running container by name should work + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + err := containers.Stop(connText, name, nil) + Expect(err).To(BeNil()) + + // Ensure container is stopped + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + Expect(isStopped(data.State.Status)).To(BeTrue()) + }) + + It("podman stop a running container by ID", func() { + // Stopping a running container by ID should work + var name = "top" + bt.RunTopContainer(&name, &falseFlag, nil) + data, err := containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + err = containers.Stop(connText, data.ID, nil) + Expect(err).To(BeNil()) + + // Ensure container is stopped + data, err = containers.Inspect(connText, name, nil) + Expect(err).To(BeNil()) + Expect(isStopped(data.State.Status)).To(BeTrue()) + }) + +}) diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go new file mode 100644 index 000000000..c8940d46e --- /dev/null +++ b/pkg/bindings/test/volumes_test.go @@ -0,0 +1,174 @@ +package test_bindings + +import ( + "context" + "fmt" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/pkg/bindings/volumes" + "net/http" + "time" + + "github.com/containers/libpod/pkg/bindings" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman volumes", func() { + var ( + //tempdir string + //err error + //podmanTest *PodmanTestIntegration + bt *bindingTest + s *gexec.Session + connText context.Context + err error + trueFlag = true + ) + + BeforeEach(func() { + //tempdir, err = CreateTempDirInTempDir() + //if err != nil { + // os.Exit(1) + //} + //podmanTest = PodmanTestCreate(tempdir) + //podmanTest.Setup() + //podmanTest.SeedImages() + bt = newBindingTest() + bt.RestoreImagesFromCache() + s = bt.startAPIService() + time.Sleep(1 * time.Second) + connText, err = bindings.NewConnection(context.Background(), bt.sock) + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + //podmanTest.Cleanup() + //f := CurrentGinkgoTestDescription() + //processTestResult(f) + s.Kill() + bt.cleanup() + }) + + It("create volume", func() { + // create a volume with blank config should work + _, err := volumes.Create(connText, handlers.VolumeCreateConfig{}) + Expect(err).To(BeNil()) + + vcc := handlers.VolumeCreateConfig{ + Name: "foobar", + Label: nil, + Opts: nil, + } + vol, err := volumes.Create(connText, vcc) + Expect(err).To(BeNil()) + Expect(vol.Name).To(Equal("foobar")) + + // create volume with same name should 500 + _, err = volumes.Create(connText, vcc) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("inspect volume", func() { + vol, err := volumes.Create(connText, handlers.VolumeCreateConfig{}) + Expect(err).To(BeNil()) + data, err := volumes.Inspect(connText, vol.Name) + Expect(err).To(BeNil()) + Expect(data.Name).To(Equal(vol.Name)) + }) + + It("remove volume", func() { + // removing a bogus volume should result in 404 + err := volumes.Remove(connText, "foobar", nil) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + + // Removing an unused volume should work + vol, err := volumes.Create(connText, handlers.VolumeCreateConfig{}) + Expect(err).To(BeNil()) + err = volumes.Remove(connText, vol.Name, nil) + Expect(err).To(BeNil()) + + // Removing a volume that is being used without force should be 409 + vol, err = volumes.Create(connText, handlers.VolumeCreateConfig{}) + Expect(err).To(BeNil()) + session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/foobar", vol.Name), "--name", "vtest", alpine.name, "top"}) + session.Wait(45) + err = volumes.Remove(connText, vol.Name, nil) + Expect(err).ToNot(BeNil()) + code, _ = bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusConflict)) + + // Removing with a volume in use with force should work with a stopped container + zero := 0 + err = containers.Stop(connText, "vtest", &zero) + Expect(err).To(BeNil()) + err = volumes.Remove(connText, vol.Name, &trueFlag) + Expect(err).To(BeNil()) + }) + + It("list volumes", func() { + // no volumes should be ok + vols, err := volumes.List(connText, nil) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeZero()) + + // create a bunch of named volumes and make verify with list + volNames := []string{"homer", "bart", "lisa", "maggie", "marge"} + for i := 0; i < 5; i++ { + _, err = volumes.Create(connText, handlers.VolumeCreateConfig{Name: volNames[i]}) + Expect(err).To(BeNil()) + } + vols, err = volumes.List(connText, nil) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 5)) + for _, v := range vols { + Expect(StringInSlice(v.Name, volNames)).To(BeTrue()) + } + + // list with bad filter should be 500 + filters := make(map[string][]string) + filters["foobar"] = []string{"1234"} + _, err = volumes.List(connText, filters) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + + filters = make(map[string][]string) + filters["name"] = []string{"homer"} + vols, err = volumes.List(connText, filters) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 1)) + Expect(vols[0].Name).To(Equal("homer")) + }) + + // TODO we need to add filtering to tests + It("prune unused volume", func() { + // Pruning when no volumes present should be ok + _, err := volumes.Prune(connText) + Expect(err).To(BeNil()) + + // Removing an unused volume should work + _, err = volumes.Create(connText, handlers.VolumeCreateConfig{}) + Expect(err).To(BeNil()) + vols, err := volumes.Prune(connText) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 1)) + + _, err = volumes.Create(connText, handlers.VolumeCreateConfig{Name: "homer"}) + Expect(err).To(BeNil()) + _, err = volumes.Create(connText, handlers.VolumeCreateConfig{}) + Expect(err).To(BeNil()) + session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/homer", "homer"), "--name", "vtest", alpine.name, "top"}) + session.Wait(45) + vols, err = volumes.Prune(connText) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 1)) + _, err = volumes.Inspect(connText, "homer") + Expect(err).To(BeNil()) + }) + +}) diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index 7f6a9cc9b..0bc818605 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -5,27 +5,33 @@ import ( "net/http" "net/url" "strconv" + "strings" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" + jsoniter "github.com/json-iterator/go" ) // Create creates a volume given its configuration. -func Create(ctx context.Context, config handlers.VolumeCreateConfig) (string, error) { - // TODO This is incomplete. The config needs to be sent via the body +func Create(ctx context.Context, config handlers.VolumeCreateConfig) (*libpod.VolumeConfig, error) { var ( - volumeID string + v libpod.VolumeConfig ) conn, err := bindings.GetClient(ctx) if err != nil { - return "", err + return nil, err + } + createString, err := jsoniter.MarshalToString(config) + if err != nil { + return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/create", nil) + stringReader := strings.NewReader(createString) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil) if err != nil { - return volumeID, err + return nil, err } - return volumeID, response.Process(&volumeID) + return &v, response.Process(&v) } // Inspect returns low-level information about a volume. @@ -37,18 +43,36 @@ func Inspect(ctx context.Context, nameOrID string) (*libpod.InspectVolumeData, e if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/%s/json", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nameOrID) if err != nil { return &inspect, err } return &inspect, response.Process(&inspect) } -func List() error { - // TODO - // The API side of things for this one does a lot in main and therefore - // is not implemented yet. - return bindings.ErrNotImplemented // nolint:typecheck +// List returns the configurations for existing volumes in the form of a slice. Optionally, filters +// can be used to refine the list of volumes. +func List(ctx context.Context, filters map[string][]string) ([]*libpod.VolumeConfig, error) { + var ( + vols []*libpod.VolumeConfig + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + if len(filters) > 0 { + strFilters, err := bindings.FiltersToString(filters) + if err != nil { + return nil, err + } + params.Set("filters", strFilters) + } + response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params) + if err != nil { + return vols, err + } + return vols, response.Process(&vols) } // Prune removes unused volumes from the local filesystem. @@ -78,7 +102,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error { if force != nil { params.Set("force", strconv.FormatBool(*force)) } - response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/%s/prune", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nameOrID) if err != nil { return err } diff --git a/pkg/network/netconflist.go b/pkg/network/netconflist.go index a8217097a..34ff00024 100644 --- a/pkg/network/netconflist.go +++ b/pkg/network/netconflist.go @@ -110,7 +110,6 @@ func NewPortMapPlugin() PortMapConfig { func NewFirewallPlugin() FirewallConfig { return FirewallConfig{ PluginType: "firewall", - Backend: "iptables", } } diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 83f4f3254..db898e706 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -58,7 +58,7 @@ static const char *_max_user_namespaces = "/proc/sys/user/max_user_namespaces"; static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivileged_userns_clone"; static int open_files_max_fd; -fd_set open_files_set; +static fd_set *open_files_set; static uid_t rootless_uid_init; static gid_t rootless_gid_init; @@ -240,17 +240,39 @@ static void __attribute__((constructor)) init() if (d) { struct dirent *ent; + size_t size = 0; - FD_ZERO (&open_files_set); for (ent = readdir (d); ent; ent = readdir (d)) { - int fd = atoi (ent->d_name); - if (fd != dirfd (d)) + int fd; + + if (ent->d_name[0] == '.') + continue; + + fd = atoi (ent->d_name); + if (fd == dirfd (d)) + continue; + + if (fd >= size * FD_SETSIZE) { - if (fd > open_files_max_fd) - open_files_max_fd = fd; - FD_SET (fd, &open_files_set); + int i; + size_t new_size; + + new_size = (fd / FD_SETSIZE) + 1; + open_files_set = realloc (open_files_set, new_size * sizeof (fd_set)); + if (open_files_set == NULL) + _exit (EXIT_FAILURE); + + for (i = size; i < new_size; i++) + FD_ZERO (&(open_files_set[i])); + + size = new_size; } + + if (fd > open_files_max_fd) + open_files_max_fd = fd; + + FD_SET (fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE])); } closedir (d); } @@ -553,10 +575,8 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) /* We passed down these fds, close them. */ int f; for (f = 3; f < open_files_max_fd; f++) - { - if (FD_ISSET (f, &open_files_set)) - close (f); - } + if (open_files_set == NULL || FD_ISSET (f % FD_SETSIZE, &(open_files_set[f / FD_SETSIZE]))) + close (f); return pid; } @@ -747,10 +767,11 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re num_fds = strtol (listen_fds, NULL, 10); if (num_fds != LONG_MIN && num_fds != LONG_MAX) { - long i; - for (i = 3; i < num_fds + 3; i++) - if (FD_ISSET (i, &open_files_set)) - close (i); + int f; + + for (f = 3; f < num_fds + 3; f++) + if (open_files_set == NULL || FD_ISSET (f % FD_SETSIZE, &(open_files_set[f / FD_SETSIZE]))) + close (f); } unsetenv ("LISTEN_PID"); unsetenv ("LISTEN_FDS"); diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index f71d55776..5ddfab7ad 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -510,7 +510,7 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st } } } - if !foundProcess { + if !foundProcess && pausePidPath != "" { return BecomeRootInUserNS(pausePidPath) } if lastErr != nil { diff --git a/pkg/spec/namespaces.go b/pkg/spec/namespaces.go index 1f98e6e25..838d95c54 100644 --- a/pkg/spec/namespaces.go +++ b/pkg/spec/namespaces.go @@ -422,7 +422,7 @@ func (c *UtsConfig) ConfigureGenerator(g *generate.Generator, net *NetworkConfig if hostname == "" { switch { case utsCtrID != "": - utsCtr, err := runtime.GetContainer(utsCtrID) + utsCtr, err := runtime.LookupContainer(utsCtrID) if err != nil { return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", utsCtrID) } diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 025cb31e0..17b180cde 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -276,7 +276,7 @@ func (s *SpecGenerator) utsConfigureGenerator(g *generate.Generator, runtime *li if hostname == "" { switch { case s.UtsNS.IsContainer(): - utsCtr, err := runtime.GetContainer(s.UtsNS.Value) + utsCtr, err := runtime.LookupContainer(s.UtsNS.Value) if err != nil { return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value) } diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go index 0b78a8150..c6aed9943 100644 --- a/pkg/util/utils_supported.go +++ b/pkg/util/utils_supported.go @@ -33,7 +33,7 @@ func GetRuntimeDir() (string, error) { logrus.Debugf("unable to make temp dir %s", tmpDir) } st, err := os.Stat(tmpDir) - if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 { + if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && (st.Mode().Perm()&0700 == 0700) { runtimeDir = tmpDir } } @@ -43,7 +43,7 @@ func GetRuntimeDir() (string, error) { logrus.Debugf("unable to make temp dir %s", tmpDir) } st, err := os.Stat(tmpDir) - if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 { + if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && (st.Mode().Perm()&0700 == 0700) { runtimeDir = tmpDir } } |