mirror of https://github.com/containers/podman.git
binding tests for volumes
add binding tests for volumes: inspect(get), create, remove, prune, and list implement filters ability for volumes Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
parent
3d1af087e6
commit
306b44380f
|
@ -126,3 +126,10 @@ func (v *Volume) GID() int {
|
||||||
func (v *Volume) CreatedTime() time.Time {
|
func (v *Volume) CreatedTime() time.Time {
|
||||||
return v.config.CreatedTime
|
return v.config.CreatedTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config returns the volume's configuration.
|
||||||
|
func (v *Volume) Config() (*VolumeConfig, error) {
|
||||||
|
config := VolumeConfig{}
|
||||||
|
err := JSONDeepCopy(v.config, &config)
|
||||||
|
return &config, err
|
||||||
|
}
|
||||||
|
|
|
@ -3,9 +3,11 @@ package libpod
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/libpod/cmd/podman/shared"
|
"github.com/containers/libpod/cmd/podman/shared"
|
||||||
"github.com/containers/libpod/libpod"
|
"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"
|
||||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||||
"github.com/gorilla/schema"
|
"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()))
|
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode params from body
|
// decode params from body
|
||||||
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
||||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
|
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)
|
parsedOptions, err := shared.ParseVolumeOptions(input.Opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
volumeOptions = append(volumeOptions, parsedOptions...)
|
volumeOptions = append(volumeOptions, parsedOptions...)
|
||||||
}
|
}
|
||||||
vol, err := runtime.NewVolume(r.Context(), volumeOptions...)
|
vol, err := runtime.NewVolume(r.Context(), volumeOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
utils.WriteResponse(w, http.StatusOK, vol.Name())
|
config, err := vol.Config()
|
||||||
|
if err != nil {
|
||||||
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.WriteResponse(w, http.StatusOK, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InspectVolume(w http.ResponseWriter, r *http.Request) {
|
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) {
|
func ListVolumes(w http.ResponseWriter, r *http.Request) {
|
||||||
//var (
|
var (
|
||||||
// runtime = r.Context().Value("runtime").(*libpod.Runtime)
|
decoder = r.Context().Value("decoder").(*schema.Decoder)
|
||||||
// decoder = r.Context().Value("decoder").(*schema.Decoder)
|
err error
|
||||||
//)
|
runtime = r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
//query := struct {
|
volumeConfigs []*libpod.VolumeConfig
|
||||||
// Filter string `json:"filter"`
|
volumeFilters []libpod.VolumeFilter
|
||||||
//}{
|
)
|
||||||
// // override any golang type defaults
|
query := struct {
|
||||||
//}
|
Filters map[string][]string `schema:"filters"`
|
||||||
//
|
}{
|
||||||
//if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
// override any golang type defaults
|
||||||
// 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
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) {
|
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)
|
vol, err := runtime.LookupVolume(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.VolumeNotFound(w, name, err)
|
utils.VolumeNotFound(w, name, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if err := runtime.RemoveVolume(r.Context(), vol, query.Force); err != nil {
|
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)
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
utils.WriteResponse(w, http.StatusNoContent, "")
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -128,11 +128,16 @@ type CreateContainerConfig struct {
|
||||||
NetworkingConfig dockerNetwork.NetworkingConfig
|
NetworkingConfig dockerNetwork.NetworkingConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swagger:model VolumeCreate
|
||||||
type VolumeCreateConfig struct {
|
type VolumeCreateConfig struct {
|
||||||
Name string `json:"name"`
|
// New volume's name. Can be left blank
|
||||||
Driver string `schema:"driver"`
|
Name string `schema:"name"`
|
||||||
Label map[string]string `schema:"label"`
|
// Volume driver to use
|
||||||
Opts map[string]string `schema:"opts"`
|
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 {
|
type IDResponse struct {
|
||||||
|
|
|
@ -11,15 +11,42 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
|
||||||
// swagger:operation POST /libpod/volumes/create volumes createVolume
|
// swagger:operation POST /libpod/volumes/create volumes createVolume
|
||||||
// ---
|
// ---
|
||||||
// summary: Create a volume
|
// summary: Create a volume
|
||||||
|
// parameters:
|
||||||
|
// - in: body
|
||||||
|
// name: create
|
||||||
|
// description: attributes for creating a container
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/VolumeCreate"
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// responses:
|
// responses:
|
||||||
// '200':
|
// '201':
|
||||||
// description: tbd
|
// $ref: "#/responses/VolumeCreateResponse"
|
||||||
// '500':
|
// '500':
|
||||||
// "$ref": "#/responses/InternalError"
|
// "$ref": "#/responses/InternalError"
|
||||||
r.Handle("/libpod/volumes/create", s.APIHandler(libpod.CreateVolume)).Methods(http.MethodPost)
|
r.Handle(VersionedPath("/libpod/volumes/create"), s.APIHandler(libpod.CreateVolume)).Methods(http.MethodPost)
|
||||||
r.Handle("/libpod/volumes/json", s.APIHandler(libpod.ListVolumes)).Methods(http.MethodGet)
|
// 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':
|
||||||
|
// "$ref": "#/responses/VolumeList"
|
||||||
|
// '500':
|
||||||
|
// "$ref": "#/responses/InternalError"
|
||||||
|
r.Handle(VersionedPath("/libpod/volumes/json"), s.APIHandler(libpod.ListVolumes)).Methods(http.MethodGet)
|
||||||
// swagger:operation POST /libpod/volumes/prune volumes pruneVolumes
|
// swagger:operation POST /libpod/volumes/prune volumes pruneVolumes
|
||||||
// ---
|
// ---
|
||||||
// summary: Prune volumes
|
// summary: Prune volumes
|
||||||
|
@ -30,7 +57,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
|
||||||
// description: no error
|
// description: no error
|
||||||
// '500':
|
// '500':
|
||||||
// "$ref": "#/responses/InternalError"
|
// "$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
|
// swagger:operation GET /libpod/volumes/{name}/json volumes inspectVolume
|
||||||
// ---
|
// ---
|
||||||
// summary: Inspect volume
|
// summary: Inspect volume
|
||||||
|
@ -49,7 +76,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
|
||||||
// "$ref": "#/responses/NoSuchVolume"
|
// "$ref": "#/responses/NoSuchVolume"
|
||||||
// '500':
|
// '500':
|
||||||
// "$ref": "#/responses/InternalError"
|
// "$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
|
// swagger:operation DELETE /libpod/volumes/{name} volumes removeVolume
|
||||||
// ---
|
// ---
|
||||||
// summary: Remove volume
|
// summary: Remove volume
|
||||||
|
@ -68,12 +95,12 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
|
||||||
// responses:
|
// responses:
|
||||||
// 204:
|
// 204:
|
||||||
// description: no error
|
// description: no error
|
||||||
// 400:
|
|
||||||
// $ref: "#/responses/BadParamError"
|
|
||||||
// 404:
|
// 404:
|
||||||
// $ref: "#/responses/NoSuchVolume"
|
// $ref: "#/responses/NoSuchVolume"
|
||||||
|
// 409:
|
||||||
|
// description: Volume is in use and cannot be removed
|
||||||
// 500:
|
// 500:
|
||||||
// $ref: "#/responses/InternalError"
|
// $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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/pkg/api/handlers"
|
"github.com/containers/libpod/pkg/api/handlers"
|
||||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||||
)
|
)
|
||||||
|
@ -139,3 +140,19 @@ type ok struct {
|
||||||
ok string
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func CreateWithSpec(ctx context.Context, s specgen.SpecGenerator) (utils.Contain
|
||||||
}
|
}
|
||||||
specgenString, err := jsoniter.MarshalToString(s)
|
specgenString, err := jsoniter.MarshalToString(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ccr, nil
|
return ccr, err
|
||||||
}
|
}
|
||||||
stringReader := strings.NewReader(specgenString)
|
stringReader := strings.NewReader(specgenString)
|
||||||
response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil)
|
response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil)
|
||||||
|
|
|
@ -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())
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -5,27 +5,33 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/pkg/api/handlers"
|
"github.com/containers/libpod/pkg/api/handlers"
|
||||||
"github.com/containers/libpod/pkg/bindings"
|
"github.com/containers/libpod/pkg/bindings"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create creates a volume given its configuration.
|
// Create creates a volume given its configuration.
|
||||||
func Create(ctx context.Context, config handlers.VolumeCreateConfig) (string, error) {
|
func Create(ctx context.Context, config handlers.VolumeCreateConfig) (*libpod.VolumeConfig, error) {
|
||||||
// TODO This is incomplete. The config needs to be sent via the body
|
|
||||||
var (
|
var (
|
||||||
volumeID string
|
v libpod.VolumeConfig
|
||||||
)
|
)
|
||||||
conn, err := bindings.GetClient(ctx)
|
conn, err := bindings.GetClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/create", nil)
|
createString, err := jsoniter.MarshalToString(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return volumeID, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return volumeID, response.Process(&volumeID)
|
stringReader := strings.NewReader(createString)
|
||||||
|
response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &v, response.Process(&v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inspect returns low-level information about a volume.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return &inspect, err
|
return &inspect, err
|
||||||
}
|
}
|
||||||
return &inspect, response.Process(&inspect)
|
return &inspect, response.Process(&inspect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func List() error {
|
// List returns the configurations for existing volumes in the form of a slice. Optionally, filters
|
||||||
// TODO
|
// can be used to refine the list of volumes.
|
||||||
// The API side of things for this one does a lot in main and therefore
|
func List(ctx context.Context, filters map[string][]string) ([]*libpod.VolumeConfig, error) {
|
||||||
// is not implemented yet.
|
var (
|
||||||
return bindings.ErrNotImplemented // nolint:typecheck
|
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.
|
// 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 {
|
if force != nil {
|
||||||
params.Set("force", strconv.FormatBool(*force))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue