diff --git a/api/api.go b/api/api.go index 7c5ad90c..a4f0785b 100644 --- a/api/api.go +++ b/api/api.go @@ -110,7 +110,7 @@ func (a *API) GetStorageParameters(uc *Volume) (storage.MountDriver, *config.Vol return nil, nil, driverOpts, err } - driver, err := backend.NewMountDriver(volConfig.Backends.Mount, (*a.Global).MountPath) + driver, err := backend.NewMountDriver(volConfig.Backends.Mount, (*a.Global).MountPath, volConfig.DriverOptions) if err != nil { return nil, nil, driverOpts, errors.GetDriver.Combine(err) } diff --git a/api/impl/docker/docker_test.go b/api/impl/docker/docker_test.go index b00756dd..1155283a 100644 --- a/api/impl/docker/docker_test.go +++ b/api/impl/docker/docker_test.go @@ -70,7 +70,7 @@ func (s *dockerSuite) TestBasic(c *C) { err := s.client.PublishPolicy("policy1", &config.Policy{ Name: "policy1", Backend: "ceph", - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: config.CreateOptions{ Size: "10MB", }, diff --git a/config/policy.go b/config/policy.go index b00f06f9..f608f59d 100644 --- a/config/policy.go +++ b/config/policy.go @@ -18,14 +18,14 @@ var defaultDrivers = map[string]*BackendDrivers{ // Policy is the configuration of the policy. It includes default // information for items such as pool and volume configuration. type Policy struct { - Name string `json:"name"` - Unlocked bool `json:"unlocked,omitempty" merge:"unlocked"` - CreateOptions CreateOptions `json:"create"` - RuntimeOptions RuntimeOptions `json:"runtime"` - DriverOptions map[string]string `json:"driver"` - FileSystems map[string]string `json:"filesystems"` - Backends *BackendDrivers `json:"backends,omitempty"` - Backend string `json:"backend,omitempty"` + Name string `json:"name"` + Unlocked bool `json:"unlocked,omitempty" merge:"unlocked"` + CreateOptions CreateOptions `json:"create"` + RuntimeOptions RuntimeOptions `json:"runtime"` + DriverOptions map[string]interface{} `json:"driver"` + FileSystems map[string]string `json:"filesystems"` + Backends *BackendDrivers `json:"backends,omitempty"` + Backend string `json:"backend,omitempty"` } // BackendDrivers is a struct containing all the drivers used under this policy diff --git a/config/policy_test.go b/config/policy_test.go index f20f488c..dec3a886 100644 --- a/config/policy_test.go +++ b/config/policy_test.go @@ -10,7 +10,7 @@ var testPolicies = map[string]*Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ Size: "10MB", FileSystem: defaultFilesystem, @@ -31,7 +31,7 @@ var testPolicies = map[string]*Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ Size: "20MB", FileSystem: defaultFilesystem, @@ -45,7 +45,7 @@ var testPolicies = map[string]*Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ Size: "0", FileSystem: defaultFilesystem, @@ -59,7 +59,7 @@ var testPolicies = map[string]*Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ Size: "20MB", FileSystem: defaultFilesystem, @@ -73,7 +73,7 @@ var testPolicies = map[string]*Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ Size: "not a number", FileSystem: defaultFilesystem, @@ -87,7 +87,7 @@ var testPolicies = map[string]*Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ Size: "10MB", FileSystem: defaultFilesystem, @@ -106,7 +106,7 @@ var testPolicies = map[string]*Policy{ Mount: "ceph", }, Name: "blanksize", - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ FileSystem: defaultFilesystem, }, @@ -119,7 +119,7 @@ var testPolicies = map[string]*Policy{ Mount: "ceph", }, Name: "blanksize", - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ FileSystem: defaultFilesystem, }, @@ -128,7 +128,7 @@ var testPolicies = map[string]*Policy{ }, "nobackend": { Name: "nobackend", - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{ Size: "10MB", FileSystem: defaultFilesystem, diff --git a/config/validation_test.go b/config/validation_test.go index 3aa2bbaa..857aa90e 100644 --- a/config/validation_test.go +++ b/config/validation_test.go @@ -7,7 +7,7 @@ var ( VolumeConfigs = map[string]map[string]*Volume{ "valid": { "basic": { - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{Size: "10MB"}, RuntimeOptions: RuntimeOptions{UseSnapshots: false}, VolumeName: "basicvolume", @@ -15,7 +15,7 @@ var ( Backends: defaultBackends, }, "basicwithruntime": { - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{Size: "10MB"}, RuntimeOptions: RuntimeOptions{UseSnapshots: true, Snapshot: SnapshotConfig{Frequency: "10m", Keep: 10}}, VolumeName: "basicvolume", diff --git a/config/volume.go b/config/volume.go index 1193a20d..dcbc8e66 100644 --- a/config/volume.go +++ b/config/volume.go @@ -22,14 +22,14 @@ import ( // Volume is the configuration of the policy. It includes pool and // snapshot information. type Volume struct { - PolicyName string `json:"policy"` - VolumeName string `json:"name"` - Unlocked bool `json:"unlocked,omitempty" merge:"unlocked"` - DriverOptions map[string]string `json:"driver"` - MountSource string `json:"mount" merge:"mount"` - CreateOptions CreateOptions `json:"create"` - RuntimeOptions RuntimeOptions `json:"runtime"` - Backends *BackendDrivers `json:"backends,omitempty"` + PolicyName string `json:"policy"` + VolumeName string `json:"name"` + Unlocked bool `json:"unlocked,omitempty" merge:"unlocked"` + DriverOptions map[string]interface{} `json:"driver"` + MountSource string `json:"mount" merge:"mount"` + CreateOptions CreateOptions `json:"create"` + RuntimeOptions RuntimeOptions `json:"runtime"` + Backends *BackendDrivers `json:"backends,omitempty"` } // CreateOptions are the set of options used by apiserver during the volume @@ -102,7 +102,7 @@ func (c *Client) CreateVolume(rc *VolumeRequest) (*Volume, error) { } if resp.DriverOptions == nil { - resp.DriverOptions = map[string]string{} + resp.DriverOptions = map[string]interface{}{} } if err := resp.Validate(); err != nil { @@ -366,7 +366,7 @@ func (cfg *Volume) validateBackends() error { } if cfg.Backends.CRUD != "" { - crud, err := backend.NewCRUDDriver(cfg.Backends.CRUD) + crud, err := backend.NewCRUDDriver(cfg.Backends.CRUD, cfg.DriverOptions) if err != nil { return err } @@ -376,7 +376,7 @@ func (cfg *Volume) validateBackends() error { } } - mnt, err := backend.NewMountDriver(cfg.Backends.Mount, backend.MountPath) + mnt, err := backend.NewMountDriver(cfg.Backends.Mount, backend.MountPath, cfg.DriverOptions) if err != nil { return err } diff --git a/config/volume_test.go b/config/volume_test.go index 29fc8bc0..e5ca6f28 100644 --- a/config/volume_test.go +++ b/config/volume_test.go @@ -45,7 +45,7 @@ func (s *configSuite) TestVolumeValidate(c *C) { c.Assert(vc.Validate(), NotNil) vc = &Volume{ - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{Size: "10MB"}, RuntimeOptions: RuntimeOptions{UseSnapshots: false}, VolumeName: "", @@ -55,7 +55,7 @@ func (s *configSuite) TestVolumeValidate(c *C) { c.Assert(vc.Validate(), NotNil) vc = &Volume{ - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{Size: "10MB"}, RuntimeOptions: RuntimeOptions{UseSnapshots: false}, VolumeName: "foo", @@ -70,7 +70,7 @@ func (s *configSuite) TestVolumeValidate(c *C) { Snapshot: "ceph", CRUD: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: CreateOptions{Size: "10MB"}, RuntimeOptions: RuntimeOptions{UseSnapshots: false}, VolumeName: "foo", diff --git a/db/structs.go b/db/structs.go index 070d6627..35178236 100644 --- a/db/structs.go +++ b/db/structs.go @@ -7,14 +7,14 @@ import ( // Policy is the configuration of the policy. It includes default // information for items such as pool and volume configuration. type Policy struct { - Name string `json:"name"` - Unlocked bool `json:"unlocked,omitempty" merge:"unlocked"` - CreateOptions CreateOptions `json:"create"` - RuntimeOptions *RuntimeOptions `json:"runtime"` - DriverOptions map[string]string `json:"driver"` - FileSystems map[string]string `json:"filesystems"` - Backends *BackendDrivers `json:"backends,omitempty"` - Backend string `json:"backend,omitempty"` + Name string `json:"name"` + Unlocked bool `json:"unlocked,omitempty" merge:"unlocked"` + CreateOptions CreateOptions `json:"create"` + RuntimeOptions *RuntimeOptions `json:"runtime"` + DriverOptions map[string]interface{} `json:"driver"` + FileSystems map[string]string `json:"filesystems"` + Backends *BackendDrivers `json:"backends,omitempty"` + Backend string `json:"backend,omitempty"` } // BackendDrivers is a struct containing all the drivers used under this policy @@ -73,14 +73,14 @@ type UseLocker interface { // Volume is the configuration of the policy. It includes pool and // snapshot information. type Volume struct { - PolicyName string `json:"policy"` - VolumeName string `json:"name"` - Unlocked bool `json:"unlocked,omitempty" merge:"unlocked"` - DriverOptions map[string]string `json:"driver"` - MountSource string `json:"mount" merge:"mount"` - CreateOptions CreateOptions `json:"create"` - RuntimeOptions *RuntimeOptions `json:"runtime"` - Backends *BackendDrivers `json:"backends,omitempty"` + PolicyName string `json:"policy"` + VolumeName string `json:"name"` + Unlocked bool `json:"unlocked,omitempty" merge:"unlocked"` + DriverOptions map[string]interface{} `json:"driver"` + MountSource string `json:"mount" merge:"mount"` + CreateOptions CreateOptions `json:"create"` + RuntimeOptions *RuntimeOptions `json:"runtime"` + Backends *BackendDrivers `json:"backends,omitempty"` } // CreateOptions are the set of options used by apiserver during the volume diff --git a/db/test/policy_test.go b/db/test/policy_test.go index 999257d5..ddaa75fe 100644 --- a/db/test/policy_test.go +++ b/db/test/policy_test.go @@ -13,7 +13,7 @@ var testPolicies = map[string]*db.Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{ Size: "10MB", FileSystem: db.DefaultFilesystem, @@ -34,7 +34,7 @@ var testPolicies = map[string]*db.Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{ Size: "20MB", FileSystem: db.DefaultFilesystem, @@ -49,7 +49,7 @@ var testPolicies = map[string]*db.Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{ Size: "0", FileSystem: db.DefaultFilesystem, @@ -63,7 +63,7 @@ var testPolicies = map[string]*db.Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{ Size: "not a number", FileSystem: db.DefaultFilesystem, @@ -77,7 +77,7 @@ var testPolicies = map[string]*db.Policy{ Mount: "ceph", Snapshot: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{ Size: "10MB", FileSystem: db.DefaultFilesystem, @@ -96,7 +96,7 @@ var testPolicies = map[string]*db.Policy{ Mount: "ceph", }, Name: "blanksize", - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{ FileSystem: db.DefaultFilesystem, }, @@ -109,7 +109,7 @@ var testPolicies = map[string]*db.Policy{ Mount: "ceph", }, Name: "blanksize", - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{ FileSystem: db.DefaultFilesystem, }, @@ -118,7 +118,7 @@ var testPolicies = map[string]*db.Policy{ }, "nobackend": { Name: "nobackend", - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{ Size: "10MB", FileSystem: db.DefaultFilesystem, diff --git a/db/test/volume_test.go b/db/test/volume_test.go index 55117971..17490f70 100644 --- a/db/test/volume_test.go +++ b/db/test/volume_test.go @@ -145,7 +145,7 @@ func (s *testSuite) TestVolumeValidate(c *C) { c.Assert(vc.Validate(), NotNil) vc = &db.Volume{ - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{Size: "10MB"}, RuntimeOptions: &db.RuntimeOptions{UseSnapshots: false}, VolumeName: "", @@ -155,7 +155,7 @@ func (s *testSuite) TestVolumeValidate(c *C) { c.Assert(vc.Validate(), NotNil) vc = &db.Volume{ - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{Size: "10MB"}, RuntimeOptions: &db.RuntimeOptions{UseSnapshots: false}, VolumeName: "foo", @@ -170,7 +170,7 @@ func (s *testSuite) TestVolumeValidate(c *C) { Snapshot: "ceph", CRUD: "ceph", }, - DriverOptions: map[string]string{"pool": "rbd"}, + DriverOptions: map[string]interface{}{"pool": "rbd"}, CreateOptions: db.CreateOptions{Size: "10MB"}, RuntimeOptions: &db.RuntimeOptions{UseSnapshots: false}, VolumeName: "foo", diff --git a/db/volume.go b/db/volume.go index 00c939b7..6c0d7c16 100644 --- a/db/volume.go +++ b/db/volume.go @@ -39,7 +39,7 @@ func CreateVolume(vr *VolumeRequest) (*Volume, error) { } if vr.Policy.DriverOptions == nil { - vr.Policy.DriverOptions = map[string]string{} + vr.Policy.DriverOptions = map[string]interface{}{} } if err := vr.Policy.Validate(); err != nil { @@ -165,7 +165,7 @@ func (v *Volume) validateBackends() error { } if v.Backends.CRUD != "" { - crud, err := backend.NewCRUDDriver(v.Backends.CRUD) + crud, err := backend.NewCRUDDriver(v.Backends.CRUD, v.DriverOptions) if err != nil { return err } @@ -175,7 +175,7 @@ func (v *Volume) validateBackends() error { } } - mnt, err := backend.NewMountDriver(v.Backends.Mount, backend.MountPath) + mnt, err := backend.NewMountDriver(v.Backends.Mount, backend.MountPath, v.DriverOptions) if err != nil { return err } diff --git a/storage/backend/backends.go b/storage/backend/backends.go index 9f46e7d3..5fa6ef85 100644 --- a/storage/backend/backends.go +++ b/storage/backend/backends.go @@ -16,13 +16,13 @@ var ( ) // MountDrivers is the map of string to storage.MountDriver. -var MountDrivers = map[string]func(string) (storage.MountDriver, error){ +var MountDrivers = map[string]func(string, map[string]interface{}) (storage.MountDriver, error){ ceph.BackendName: ceph.NewMountDriver, nfs.BackendName: nfs.NewMountDriver, } // CRUDDrivers is the map of string to storage.CRUDDriver. -var CRUDDrivers = map[string]func() (storage.CRUDDriver, error){ +var CRUDDrivers = map[string]func(map[string]interface{}) (storage.CRUDDriver, error){ ceph.BackendName: ceph.NewCRUDDriver, } @@ -33,7 +33,7 @@ var SnapshotDrivers = map[string]func() (storage.SnapshotDriver, error){ // NewMountDriver instantiates and return a mount driver instance of the // specified type -func NewMountDriver(backend, mountpath string) (storage.MountDriver, error) { +func NewMountDriver(backend, mountpath string, dOptions map[string]interface{}) (storage.MountDriver, error) { f, ok := MountDrivers[backend] if !ok { return nil, errored.Errorf("invalid mount driver backend: %q", backend) @@ -43,17 +43,17 @@ func NewMountDriver(backend, mountpath string) (storage.MountDriver, error) { return nil, errored.Errorf("mount path not specified, cannot continue") } - return f(mountpath) + return f(mountpath, dOptions) } // NewCRUDDriver instantiates a CRUD Driver. -func NewCRUDDriver(backend string) (storage.CRUDDriver, error) { +func NewCRUDDriver(backend string, dOptions map[string]interface{}) (storage.CRUDDriver, error) { f, ok := CRUDDrivers[backend] if !ok { return nil, errored.Errorf("invalid CRUD driver backend: %q", backend) } - return f() + return f(dOptions) } // NewSnapshotDriver creates a SnapshotDriver based on the backend name. diff --git a/storage/backend/ceph/ceph.go b/storage/backend/ceph/ceph.go index 2357c5d3..d1168f03 100644 --- a/storage/backend/ceph/ceph.go +++ b/storage/backend/ceph/ceph.go @@ -21,6 +21,7 @@ import ( "github.com/contiv/volplugin/errors" "github.com/contiv/volplugin/storage" "github.com/contiv/volplugin/storage/mountscan" + gojson "github.com/xeipuuv/gojsonschema" ) const ( @@ -40,6 +41,46 @@ var spaceSplitRegex = regexp.MustCompile(`\s+`) // type Driver struct { mountpath string + dOpts *dOptions +} + +// dOptions implements the ceph driver options +type dOptions struct { + PoolName string `json:"pool"` +} + +func (do *dOptions) validate() error { + schema := gojson.NewStringLoader(DriverOptionsSchema) + data := gojson.NewGoLoader(do) + + if result, err := gojson.Validate(schema, data); err != nil { + return err + } else if !result.Valid() { + var errors []string + for _, err := range result.Errors() { + errors = append(errors, fmt.Sprintf("%s\n", err)) + } + return errored.New(strings.Join(errors, "\n")) + } + + return nil +} + +func (do *dOptions) mapDriverOptions(dOpts map[string]interface{}) error { + content, err := json.Marshal(dOpts) + if err != nil { + return err + } + + if err := json.Unmarshal(content, do); err != nil { + return err + } + + if err := do.validate(); err != nil { + return err + } + + return nil } func (c *Driver) mkMountPath(poolName, intName string) (string, error) { @@ -64,14 +105,35 @@ func runWithTimeout(cmd *exec.Cmd, timeout time.Duration) (*executor.ExecResult, // NewMountDriver is a generator for Driver structs. It is used by the storage // framework to yield new drivers on every creation. -func NewMountDriver(mountpath string) (storage.MountDriver, error) { - return &Driver{mountpath: mountpath}, nil +func NewMountDriver(mountpath string, dOpts map[string]interface{}) (storage.MountDriver, error) { + if dOpts == nil { + return nil, errored.Errorf("Driver options required to create Mount driver") + } + + driverOpts := &dOptions{} + if err := driverOpts.mapDriverOptions(dOpts); err != nil { + return nil, err + } + return &Driver{mountpath: mountpath, dOpts: driverOpts}, nil } // NewCRUDDriver is a generator for Driver structs. It is used by the storage // framework to yield new drivers on every creation. -func NewCRUDDriver() (storage.CRUDDriver, error) { - return &Driver{}, nil +func NewCRUDDriver(dOpts map[string]interface{}) (storage.CRUDDriver, error) { + if dOpts == nil { + return nil, errored.Errorf("Driver options required to create CRUD driver") + } + + driverOpts := &dOptions{} + if err := driverOpts.mapDriverOptions(dOpts); err != nil { + return nil, err + } + + if len(strings.TrimSpace(driverOpts.PoolName)) == 0 { + return nil, errored.Errorf("Pool is required for ceph driver") + } + + return &Driver{dOpts: driverOpts}, nil } // NewSnapshotDriver is a generator for Driver structs. It is used by the storage @@ -115,7 +177,7 @@ func (c *Driver) Create(do storage.DriverOptions) error { return err } - cmd := exec.Command("rbd", "create", mkpool(do.Volume.Params["pool"], intName), "--size", strconv.FormatUint(do.Volume.Size, 10)) + cmd := exec.Command("rbd", "create", mkpool(c.dOpts.PoolName, intName), "--size", strconv.FormatUint(do.Volume.Size, 10)) er, err := runWithTimeout(cmd, do.Timeout) if er != nil { @@ -150,19 +212,18 @@ func (c *Driver) Format(do storage.DriverOptions) error { // Destroy a volume. func (c *Driver) Destroy(do storage.DriverOptions) error { - poolName := do.Volume.Params["pool"] intName, err := c.internalName(do.Volume.Name) if err != nil { return err } - cmd := exec.Command("rbd", "snap", "purge", mkpool(poolName, intName)) + cmd := exec.Command("rbd", "snap", "purge", mkpool(c.dOpts.PoolName, intName)) er, _ := runWithTimeout(cmd, do.Timeout) if er.ExitStatus != 0 { return errored.Errorf("Destroying snapshots for disk %q: %v", intName, er.Stderr) } - cmd = exec.Command("rbd", "rm", mkpool(poolName, intName)) + cmd = exec.Command("rbd", "rm", mkpool(c.dOpts.PoolName, intName)) er, _ = runWithTimeout(cmd, do.Timeout) if er.ExitStatus != 0 { return errored.Errorf("Destroying disk %q: %v (%v)", intName, er, er.Stdout) @@ -173,22 +234,20 @@ func (c *Driver) Destroy(do storage.DriverOptions) error { // List all volumes. func (c *Driver) List(lo storage.ListOptions) ([]storage.Volume, error) { - poolName := lo.Params["pool"] - retry: - er, err := executor.NewCapture(exec.Command("rbd", "ls", poolName, "--format", "json")).Run(context.Background()) + er, err := executor.NewCapture(exec.Command("rbd", "ls", c.dOpts.PoolName, "--format", "json")).Run(context.Background()) if err != nil { return nil, err } if er.ExitStatus != 0 { - return nil, errored.Errorf("Listing pool %q: %v", poolName, er) + return nil, errored.Errorf("Listing pool %q: %v", c.dOpts.PoolName, er) } textList := []string{} if err := json.Unmarshal([]byte(er.Stdout), &textList); err != nil { - logrus.Errorf("Unmarshalling ls for pool %q: %v. Retrying.", poolName, err) + logrus.Errorf("Unmarshalling ls for pool %q: %v. Retrying.", c.dOpts.PoolName, err) time.Sleep(100 * time.Millisecond) goto retry } @@ -196,7 +255,7 @@ retry: list := []storage.Volume{} for _, name := range textList { - list = append(list, storage.Volume{Name: c.externalName(strings.TrimSpace(name)), Params: storage.Params{"pool": poolName}}) + list = append(list, storage.Volume{Name: c.externalName(strings.TrimSpace(name)), Params: storage.Params{"pool": c.dOpts.PoolName}}) } return list, nil @@ -211,9 +270,7 @@ func (c *Driver) Mount(do storage.DriverOptions) (*storage.Mount, error) { return nil, err } - poolName := do.Volume.Params["pool"] - - volumePath, err := c.mkMountPath(poolName, intName) + volumePath, err := c.mkMountPath(c.dOpts.PoolName, intName) if err != nil { return nil, err } @@ -260,13 +317,12 @@ func (c *Driver) Mount(do storage.DriverOptions) (*storage.Mount, error) { // Unmount a volume. func (c *Driver) Unmount(do storage.DriverOptions) error { - poolName := do.Volume.Params["pool"] intName, err := c.internalName(do.Volume.Name) if err != nil { return err } - volumeDir, err := c.mkMountPath(poolName, intName) + volumeDir, err := c.mkMountPath(c.dOpts.PoolName, intName) if err != nil { return err } @@ -325,10 +381,8 @@ func (c *Driver) CreateSnapshot(snapName string, do storage.DriverOptions) error return err } - poolName := do.Volume.Params["pool"] - snapName = strings.Replace(snapName, " ", "-", -1) - cmd := exec.Command("rbd", "snap", "create", mkpool(poolName, intName), "--snap", snapName) + cmd := exec.Command("rbd", "snap", "create", mkpool(c.dOpts.PoolName, intName), "--snap", snapName) er, err := runWithTimeout(cmd, do.Timeout) if err != nil { return err @@ -348,9 +402,7 @@ func (c *Driver) RemoveSnapshot(snapName string, do storage.DriverOptions) error return err } - poolName := do.Volume.Params["pool"] - - cmd := exec.Command("rbd", "snap", "rm", mkpool(poolName, intName), "--snap", snapName) + cmd := exec.Command("rbd", "snap", "rm", mkpool(c.dOpts.PoolName, intName), "--snap", snapName) er, err := runWithTimeout(cmd, do.Timeout) if err != nil { return err @@ -371,9 +423,7 @@ func (c *Driver) ListSnapshots(do storage.DriverOptions) ([]string, error) { return nil, err } - poolName := do.Volume.Params["pool"] - - cmd := exec.Command("rbd", "snap", "ls", mkpool(poolName, intName)) + cmd := exec.Command("rbd", "snap", "ls", mkpool(c.dOpts.PoolName, intName)) ctx, _ := context.WithTimeout(context.Background(), do.Timeout) er, err := executor.NewCapture(cmd).Run(ctx) if err != nil { @@ -414,14 +464,12 @@ func (c *Driver) cleanupCopy(snapName, newName string, do storage.DriverOptions, return } - poolName := do.Volume.Params["pool"] - select { case err := <-errChan: newerr, ok := err.(*errored.Error) if ok && newerr.Contains(errors.SnapshotCopy) { logrus.Warnf("Error received while copying snapshot %q: %v. Attempting to cleanup... Snapshot %q may still be protected!", do.Volume.Name, err, snapName) - cmd := exec.Command("rbd", "rm", mkpool(poolName, intNewName)) + cmd := exec.Command("rbd", "rm", mkpool(c.dOpts.PoolName, intNewName)) if er, err := runWithTimeout(cmd, do.Timeout); err != nil || er.ExitStatus != 0 { logrus.Errorf("Error encountered removing new volume %q for volume %q, snapshot %q: %v, %v", intNewName, intOrigName, snapName, err, er.Stderr) return @@ -430,7 +478,7 @@ func (c *Driver) cleanupCopy(snapName, newName string, do storage.DriverOptions, if ok && newerr.Contains(errors.SnapshotProtect) { logrus.Warnf("Error received protecting snapshot %q: %v. Attempting to cleanup.", do.Volume.Name, err) - cmd := exec.Command("rbd", "snap", "unprotect", mkpool(poolName, intOrigName), "--snap", snapName) + cmd := exec.Command("rbd", "snap", "unprotect", mkpool(c.dOpts.PoolName, intOrigName), "--snap", snapName) if er, err := runWithTimeout(cmd, do.Timeout); err != nil || er.ExitStatus != 0 { logrus.Errorf("Error encountered unprotecting new volume %q for volume %q, snapshot %q: %v, %v", newName, intOrigName, snapName, err, er.Stderr) return @@ -453,9 +501,7 @@ func (c *Driver) CopySnapshot(do storage.DriverOptions, snapName, newName string return err } - poolName := do.Volume.Params["pool"] - - list, err := c.List(storage.ListOptions{Params: storage.Params{"pool": poolName}}) + list, err := c.List(storage.ListOptions{Params: storage.Params{"pool": c.dOpts.PoolName}}) for _, vol := range list { if intNewName == vol.Name { return errored.Errorf("Volume %q already exists", vol.Name) @@ -464,7 +510,7 @@ func (c *Driver) CopySnapshot(do storage.DriverOptions, snapName, newName string errChan := make(chan error, 1) - cmd := exec.Command("rbd", "snap", "protect", mkpool(poolName, intOrigName), "--snap", snapName) + cmd := exec.Command("rbd", "snap", "protect", mkpool(c.dOpts.PoolName, intOrigName), "--snap", snapName) er, err := runWithTimeout(cmd, do.Timeout) // EBUSY indicates that the snapshot is already protected. @@ -478,7 +524,7 @@ func (c *Driver) CopySnapshot(do storage.DriverOptions, snapName, newName string defer c.cleanupCopy(snapName, newName, do, errChan) - cmd = exec.Command("rbd", "clone", mkpool(poolName, intOrigName), mkpool(poolName, intNewName), "--snap", snapName) + cmd = exec.Command("rbd", "clone", mkpool(c.dOpts.PoolName, intOrigName), mkpool(c.dOpts.PoolName, intNewName), "--snap", snapName) er, err = runWithTimeout(cmd, do.Timeout) if err != nil && er.ExitStatus == 0 { var err2 *errored.Error @@ -554,9 +600,5 @@ func (c *Driver) Validate(do *storage.DriverOptions) error { return err } - if do.Volume.Params["pool"] == "" { - return errored.Errorf("Pool is missing in ceph storage driver.") - } - return nil } diff --git a/storage/backend/ceph/ceph_test.go b/storage/backend/ceph/ceph_test.go index c77f8a96..56858fc5 100644 --- a/storage/backend/ceph/ceph_test.go +++ b/storage/backend/ceph/ceph_test.go @@ -19,6 +19,15 @@ import ( const myMountpath = "/mnt/ceph" +var driverOptions = map[string]map[string]interface{}{ + "test": { + "pool": "test", + }, + "rbd": { + "pool": "rbd", + }, +} + var filesystems = map[string]storage.FSOptions{ "ext4": { Type: "ext4", @@ -106,9 +115,9 @@ func (s *cephSuite) TestMkfsVolume(c *C) { func (s *cephSuite) TestMountUnmountVolume(c *C) { // Create a new driver - crudDriver, err := NewCRUDDriver() + crudDriver, err := NewCRUDDriver(driverOptions["rbd"]) c.Assert(err, IsNil) - mountDriver, err := NewMountDriver(myMountpath) + mountDriver, err := NewMountDriver(myMountpath, driverOptions["rbd"]) c.Assert(err, IsNil) driverOpts := storage.DriverOptions{ @@ -147,6 +156,9 @@ again: Timeout: 5 * time.Second, } + crudDriver, err = NewCRUDDriver(driverOptions["test"]) + c.Assert(err, IsNil) + if done { return } @@ -159,7 +171,7 @@ again: func (s *cephSuite) TestSnapshots(c *C) { snapDrv, err := NewSnapshotDriver() c.Assert(err, IsNil) - crudDrv, err := NewCRUDDriver() + crudDrv, err := NewCRUDDriver(driverOptions["rbd"]) c.Assert(err, IsNil) driverOpts := storage.DriverOptions{ @@ -195,6 +207,9 @@ again: Timeout: 5 * time.Second, } + crudDrv, err = NewCRUDDriver(driverOptions["test"]) + c.Assert(err, IsNil) + if done { return } @@ -205,9 +220,9 @@ again: } func (s *cephSuite) TestRepeatedMountUnmount(c *C) { - mountDrv, err := NewMountDriver(myMountpath) + mountDrv, err := NewMountDriver(myMountpath, driverOptions["rbd"]) c.Assert(err, IsNil) - crudDrv, err := NewCRUDDriver() + crudDrv, err := NewCRUDDriver(driverOptions["rbd"]) c.Assert(err, IsNil) driverOpts := storage.DriverOptions{ @@ -225,12 +240,13 @@ again: defer mountDrv.Unmount(driverOpts) defer crudDrv.Destroy(driverOpts) + logrus.Infof("%#v", crudDrv) c.Assert(crudDrv.Create(driverOpts), IsNil) c.Assert(crudDrv.Format(driverOpts), IsNil) for i := 0; i < 10; i++ { _, err := mountDrv.Mount(driverOpts) c.Assert(err, IsNil) - s.readWriteTest(c, fmt.Sprintf("/mnt/ceph/%s/test.pithos", driverOpts.Volume.Params["pool"])) + s.readWriteTest(c, fmt.Sprintf("/mnt/ceph/%s/test.pithos", crudDrv.PoolName())) c.Assert(mountDrv.Unmount(driverOpts), IsNil) } c.Assert(crudDrv.Destroy(driverOpts), IsNil) @@ -241,12 +257,15 @@ again: Timeout: 5 * time.Second, } + crudDrv, err = NewCRUDDriver(driverOptions["test"]) + logrus.Infof("Towards end %#v", crudDrv) + c.Assert(err, IsNil) + if done { return } done = true - goto again } @@ -260,9 +279,9 @@ func (s *cephSuite) TestTemplateFSCmd(c *C) { } func (s *cephSuite) TestMounted(c *C) { - crudDrv, err := NewCRUDDriver() + crudDrv, err := NewCRUDDriver(driverOptions["rbd"]) c.Assert(err, IsNil) - mountDrv, err := NewMountDriver(myMountpath) + mountDrv, err := NewMountDriver(myMountpath, driverOptions["rbd"]) c.Assert(err, IsNil) driverOpts := storage.DriverOptions{ @@ -295,13 +314,16 @@ again: Device: "/dev/rbd0", DevMajor: 252, DevMinor: 0, - Path: strings.Join([]string{myMountpath, driverOpts.Volume.Params["pool"], intName}, "/"), + Path: strings.Join([]string{myMountpath, crudDrv.PoolName(), intName}, "/"), Volume: driverOpts.Volume, }) c.Assert(mountDrv.Unmount(driverOpts), IsNil) c.Assert(crudDrv.Destroy(driverOpts), IsNil) + crudDrv, err = NewCRUDDriver(driverOptions["test"]) + c.Assert(err, IsNil) + driverOpts = storage.DriverOptions{ Volume: volumeSpecTestPool, FSOptions: filesystems["ext4"], @@ -343,7 +365,7 @@ func (s *cephSuite) TestExternalInternalNames(c *C) { func (s *cephSuite) TestSnapshotClone(c *C) { snapDrv, err := NewSnapshotDriver() c.Assert(err, IsNil) - crudDrv, err := NewCRUDDriver() + crudDrv, err := NewCRUDDriver(driverOptions["rbd"]) c.Assert(err, IsNil) driverOpts := storage.DriverOptions{ @@ -362,7 +384,7 @@ again: c.Assert(snapDrv.CopySnapshot(driverOpts, "testsnap", "test/image"), IsNil) c.Assert(snapDrv.CopySnapshot(driverOpts, "test", "test/image"), NotNil) - content, err := exec.Command("rbd", "ls", driverOpts.Volume.Params["pool"]).CombinedOutput() + content, err := exec.Command("rbd", "ls", crudDrv.PoolName()).CombinedOutput() c.Assert(err, IsNil) c.Assert(strings.TrimSpace(string(content)), Equals, "test.image\ntest.pithos") c.Assert(snapDrv.CopySnapshot(driverOpts, "foo", "test/image"), NotNil) @@ -370,11 +392,14 @@ again: driverOpts.Volume.Name = "test/image" c.Assert(crudDrv.Destroy(driverOpts), IsNil) - exec.Command("rbd", "snap", "unprotect", mkpool(driverOpts.Volume.Params["pool"], "test.pithos"), "--snap", "test").Run() - exec.Command("rbd", "snap", "unprotect", mkpool(driverOpts.Volume.Params["pool"], "test.pithos"), "--snap", "testsnap").Run() + exec.Command("rbd", "snap", "unprotect", mkpool(crudDrv.PoolName(), "test.pithos"), "--snap", "test").Run() + exec.Command("rbd", "snap", "unprotect", mkpool(crudDrv.PoolName(), "test.pithos"), "--snap", "testsnap").Run() driverOpts.Volume.Name = "test/pithos" c.Assert(crudDrv.Destroy(driverOpts), IsNil) + crudDrv, err = NewCRUDDriver(driverOptions["test"]) + c.Assert(err, IsNil) + driverOpts = storage.DriverOptions{ Volume: volumeSpecTestPool, FSOptions: filesystems["ext4"], @@ -391,10 +416,10 @@ again: } func (s *cephSuite) TestMountScan(c *C) { - crudDriver, err := NewCRUDDriver() + crudDriver, err := NewCRUDDriver(driverOptions["rbd"]) c.Assert(err, IsNil) - mountDriver, err := NewMountDriver(myMountpath) + mountDriver, err := NewMountDriver(myMountpath, driverOptions["rbd"]) c.Assert(err, IsNil) c.Assert(crudDriver.Create(mountscanDriverOpts), IsNil) @@ -424,10 +449,10 @@ func (s *cephSuite) TestMountScan(c *C) { func (s *cephSuite) TestMountSource(c *C) { totalIterations := 5 - crudDriver, err := NewCRUDDriver() + crudDriver, err := NewCRUDDriver(driverOptions["rbd"]) c.Assert(err, IsNil) - mountDriver, err := NewMountDriver(myMountpath) + mountDriver, err := NewMountDriver(myMountpath, driverOptions["rbd"]) c.Assert(err, IsNil) driver := &Driver{} diff --git a/storage/backend/ceph/internals.go b/storage/backend/ceph/internals.go index 042d1dcf..2e4deb1d 100644 --- a/storage/backend/ceph/internals.go +++ b/storage/backend/ceph/internals.go @@ -23,7 +23,6 @@ type rbdMap map[string]struct { } func (c *Driver) mapImage(do storage.DriverOptions) (string, error) { - poolName := do.Volume.Params["pool"] intName, err := c.internalName(do.Volume.Name) if err != nil { return "", err @@ -32,7 +31,7 @@ func (c *Driver) mapImage(do storage.DriverOptions) (string, error) { retries := 0 retry: - cmd := exec.Command("rbd", "map", intName, "--pool", poolName) + cmd := exec.Command("rbd", "map", intName, "--pool", c.dOpts.PoolName) er, err := runWithTimeout(cmd, do.Timeout) if retries < 10 && err != nil { logrus.Errorf("Error mapping image: %v (%v) (%v). Retrying.", intName, er, err) @@ -52,14 +51,14 @@ retry: } for _, rbd := range rbdmap { - if rbd.Name == intName && rbd.Pool == do.Volume.Params["pool"] { + if rbd.Name == intName && rbd.Pool == c.dOpts.PoolName { device = rbd.Device break } } if device == "" { - return "", errored.Errorf("Volume %s in pool %s not found in RBD showmapped output", intName, do.Volume.Params["pool"]) + return "", errored.Errorf("Volume %s in pool %s not found in RBD showmapped output", intName, c.dOpts.PoolName) } logrus.Debugf("mapped volume %q as %q", intName, device) @@ -101,19 +100,17 @@ func (c *Driver) unmapImage(do storage.DriverOptions) error { } func (c *Driver) doUnmap(do storage.DriverOptions, rbdmap rbdMap) (bool, error) { - poolName := do.Volume.Params["pool"] - intName, err := c.internalName(do.Volume.Name) if err != nil { return false, err } for _, rbd := range rbdmap { - if rbd.Name == intName && rbd.Pool == do.Volume.Params["pool"] { - logrus.Debugf("Unmapping volume %s/%s at device %q", poolName, intName, strings.TrimSpace(rbd.Device)) + if rbd.Name == intName && rbd.Pool == c.dOpts.PoolName { + logrus.Debugf("Unmapping volume %s/%s at device %q", c.dOpts.PoolName, intName, strings.TrimSpace(rbd.Device)) if _, err := os.Stat(rbd.Device); err != nil { - logrus.Debugf("Trying to unmap device %q for %s/%s that does not exist, continuing", poolName, intName, rbd.Device) + logrus.Debugf("Trying to unmap device %q for %s/%s that does not exist, continuing", c.dOpts.PoolName, intName, rbd.Device) continue } @@ -187,7 +184,7 @@ func (c *Driver) getMapped(timeout time.Duration) ([]*storage.Mount, error) { Device: rbd.Device, Volume: storage.Volume{ Name: c.externalName(rbd.Name), - Params: map[string]string{ + Params: map[string]interface{}{ "pool": rbd.Pool, }, }, diff --git a/storage/backend/ceph/schema.go b/storage/backend/ceph/schema.go new file mode 100644 index 00000000..63083c34 --- /dev/null +++ b/storage/backend/ceph/schema.go @@ -0,0 +1,30 @@ +package ceph + +var ( + // DriverOptionsSchema defines json schema for ceph driver options + DriverOptionsSchema = `{ + "title": "Driver options validation", + "type": "object", + "properties": { + "pool": { "type": "string", "minLen": 1} + } + }` + + // GlusterSchema TODO + GlusterSchema = `{ + "title": "GlusterFS driverOpts validation", + "type": "object", + "properties": { + "replica": { "type": "number", "minimum": 1 }, + "transport": { "type": "string", "enum": [ "tcp", "rdma" ] }, + "stripe": { "type": "number", "minimum": 1 }, + "bricks": { + "type": "object", + "patternProperties": { + ".{1,}": { "type": "string" } + } + } + }, + "required": [ "bricks" ] + }` +) diff --git a/storage/backend/ceph/util.go b/storage/backend/ceph/util.go index caaaccb5..e396a3ca 100644 --- a/storage/backend/ceph/util.go +++ b/storage/backend/ceph/util.go @@ -7,13 +7,19 @@ import ( "github.com/contiv/volplugin/storage" ) +// PoolName returns the 'ceph' poolName for this instance +func (c *Driver) PoolName() string { + return c.dOpts.PoolName +} + // MountPath returns the path of a mount for a pool/volume. func (c *Driver) MountPath(do storage.DriverOptions) (string, error) { volName, err := c.internalName(do.Volume.Name) if err != nil { return "", err } - return filepath.Join(c.mountpath, do.Volume.Params["pool"], volName), nil + + return filepath.Join(c.mountpath, c.dOpts.PoolName, volName), nil } // FIXME maybe this belongs in storage/ as it's more general? diff --git a/storage/backend/nfs/mount.go b/storage/backend/nfs/mount.go index d15d6496..cad35e7f 100644 --- a/storage/backend/nfs/mount.go +++ b/storage/backend/nfs/mount.go @@ -35,7 +35,7 @@ func (d *Driver) Mounted(timeout time.Duration) ([]*storage.Mount, error) { Path: hostMount.MountPoint, Volume: storage.Volume{ Name: rel, - Params: map[string]string{ + Params: map[string]interface{}{ "mount": hostMount.MountSource, }, }, diff --git a/storage/backend/nfs/nfs.go b/storage/backend/nfs/nfs.go index 0c5af574..b1056c89 100644 --- a/storage/backend/nfs/nfs.go +++ b/storage/backend/nfs/nfs.go @@ -1,6 +1,7 @@ package nfs import ( + "encoding/json" "fmt" "net" "os" @@ -14,19 +15,59 @@ import ( "github.com/contiv/errored" "github.com/contiv/volplugin/storage" "github.com/vishvananda/netlink" + gojson "github.com/xeipuuv/gojsonschema" ) // Driver is a basic struct for controlling the NFS driver. type Driver struct { mountpath string + DOpts *DOptions +} + +// DOptions implements the nfs driver options +type DOptions struct { + Options string `json:"options"` } // BackendName is the name of the driver. const BackendName = "nfs" +func (do *DOptions) validate() error { + schema := gojson.NewStringLoader(DriverOptionsSchema) + data := gojson.NewGoLoader(do) + + if result, err := gojson.Validate(schema, data); err != nil { + return err + } else if !result.Valid() { + var errors []string + for _, err := range result.Errors() { + errors = append(errors, fmt.Sprintf("%s\n", err)) + } + return errored.New(strings.Join(errors, "\n")) + } + + return nil +} + // NewMountDriver constructs a new NFS driver. -func NewMountDriver(mountPath string) (storage.MountDriver, error) { - return &Driver{mountpath: mountPath}, nil +func NewMountDriver(mountPath string, dOpts map[string]interface{}) (storage.MountDriver, error) { + driverOpts := &DOptions{} + if dOpts != nil { + content, err := json.Marshal(dOpts) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(content, driverOpts); err != nil { + return nil, err + } + + if err := driverOpts.validate(); err != nil { + return nil, err + } + } + + return &Driver{mountpath: mountPath, DOpts: driverOpts}, nil } // Name returns the string associated with the storage backed of the driver @@ -80,7 +121,7 @@ func (d *Driver) mapOptionsToString(mapOpts map[string]string) string { } func (d *Driver) mkOpts(do storage.DriverOptions) (string, error) { - mapOpts, err := d.validateConvertOptions(do.Volume.Params["options"]) + mapOpts, err := d.validateConvertOptions(d.DOpts.Options) if err != nil { return "", err } diff --git a/storage/backend/nfs/nfs_test.go b/storage/backend/nfs/nfs_test.go index 11794108..d9ff87d2 100644 --- a/storage/backend/nfs/nfs_test.go +++ b/storage/backend/nfs/nfs_test.go @@ -87,7 +87,7 @@ func makeExport(c *C, name, mountArgs string) { } func (s *nfsSuite) TestSanity(c *C) { - d, err := NewMountDriver(mountPath) + d, err := NewMountDriver(mountPath, map[string]interface{}{}) c.Assert(err, IsNil) c.Assert(d.Name(), Equals, BackendName) @@ -110,7 +110,7 @@ func (s *nfsSuite) TestRepeatedMountSingleMountPoint(c *C) { makeExport(c, "basic", "rw") c.Assert(ioutil.WriteFile(path.Join(mkPath("basic"), "foo"), []byte{byte(i)}, 0644), IsNil) - d, err := NewMountDriver(mountPath) + d, err := NewMountDriver(mountPath, map[string]interface{}{}) c.Assert(err, IsNil) vol := storage.Volume{ @@ -261,7 +261,7 @@ func (s *nfsSuite) TestMountScan(c *C) { } makeExport(c, "mountscan", "rw") - mountD, err := NewMountDriver(mountPath) + mountD, err := NewMountDriver(mountPath, map[string]interface{}{}) c.Assert(err, IsNil) do := storage.DriverOptions{ diff --git a/storage/backend/nfs/schema.go b/storage/backend/nfs/schema.go new file mode 100644 index 00000000..3dae38ef --- /dev/null +++ b/storage/backend/nfs/schema.go @@ -0,0 +1,12 @@ +package nfs + +var ( + // DriverOptionsSchema defines json schema for nfs driver options + DriverOptionsSchema = `{ + "title":"Driver options validation", + "type":"object", + "properties":{ + "options":{ "type":"string" } + } + }` +) diff --git a/storage/control/volume.go b/storage/control/volume.go index 72eeed31..7e1ace84 100644 --- a/storage/control/volume.go +++ b/storage/control/volume.go @@ -39,7 +39,7 @@ func CreateVolume(policy *config.Policy, config *config.Volume, timeout time.Dur return storage.DriverOptions{}, err } - driver, err := backend.NewCRUDDriver(config.Backends.CRUD) + driver, err := backend.NewCRUDDriver(config.Backends.CRUD, config.DriverOptions) if err != nil { return storage.DriverOptions{}, err } @@ -73,7 +73,7 @@ func FormatVolume(config *config.Volume, do storage.DriverOptions) error { return errors.NoActionTaken } - driver, err := backend.NewCRUDDriver(config.Backends.CRUD) + driver, err := backend.NewCRUDDriver(config.Backends.CRUD, config.DriverOptions) if err != nil { return err } @@ -89,7 +89,7 @@ func ExistsVolume(config *config.Volume, timeout time.Duration) (bool, error) { return true, errors.NoActionTaken } - driver, err := backend.NewCRUDDriver(config.Backends.CRUD) + driver, err := backend.NewCRUDDriver(config.Backends.CRUD, config.DriverOptions) if err != nil { return false, err } @@ -112,7 +112,7 @@ func RemoveVolume(config *config.Volume, timeout time.Duration) error { return errors.NoActionTaken } - driver, err := backend.NewCRUDDriver(config.Backends.CRUD) + driver, err := backend.NewCRUDDriver(config.Backends.CRUD, config.DriverOptions) if err != nil { return err } diff --git a/storage/driver.go b/storage/driver.go index 855a7e83..51bbe481 100644 --- a/storage/driver.go +++ b/storage/driver.go @@ -2,6 +2,7 @@ package storage import ( "errors" + "strings" "time" "github.com/contiv/errored" @@ -13,7 +14,7 @@ var ( ) // Params are parameters that relate directly to the location of the storage. -type Params map[string]string +type Params map[string]interface{} // A Mount is the resulting attributes of a Mount or Unmount operation. type Mount struct { @@ -146,3 +147,19 @@ func (v Volume) Validate() error { return nil } + +// Get can be used to get only "string" types from element `driver` +func (p Params) Get(attr string, allowEmpty bool) (string, error) { + switch value := p[attr].(type) { + case string: + if !allowEmpty && len(strings.TrimSpace(value)) == 0 { + return "", errored.Errorf("Expected non-empty string for driver.%s", attr) + } + return value, nil + default: + if allowEmpty && value == nil { + return "", nil + } + return "", errored.Errorf("Expected string type for driver.%s", attr) + } +} diff --git a/storage/driver_test.go b/storage/driver_test.go index 4f9727f3..b96fd56c 100644 --- a/storage/driver_test.go +++ b/storage/driver_test.go @@ -17,7 +17,7 @@ func (s *storageSuite) TestDriverOptionsValidate(c *C) { c.Assert(do.Validate(), NotNil) - do = DriverOptions{Timeout: 1, Volume: Volume{Name: "hi", Params: map[string]string{}}} + do = DriverOptions{Timeout: 1, Volume: Volume{Name: "hi", Params: map[string]interface{}{}}} c.Assert(do.Validate(), IsNil) do.Timeout = 0 c.Assert(do.Validate(), NotNil) @@ -29,13 +29,13 @@ func (s *storageSuite) TestVolumeValidate(c *C) { v := Volume{} c.Assert(v.Validate(), NotNil) - v = Volume{Name: "name", Size: 100, Params: map[string]string{}} + v = Volume{Name: "name", Size: 100, Params: map[string]interface{}{}} c.Assert(v.Validate(), IsNil) v.Name = "" c.Assert(v.Validate(), NotNil) v.Name = "name" v.Params = nil c.Assert(v.Validate(), NotNil) - v.Params = map[string]string{} + v.Params = map[string]interface{}{} c.Assert(v.Validate(), IsNil) } diff --git a/volplugin/init.go b/volplugin/init.go index 039a2d66..ae8800bb 100644 --- a/volplugin/init.go +++ b/volplugin/init.go @@ -56,7 +56,7 @@ func (dc *DaemonConfig) getMounted() (map[string]*storage.Mount, map[string]int, } for driverName := range backend.MountDrivers { - cd, err := backend.NewMountDriver(driverName, dc.Global.MountPath) + cd, err := backend.NewMountDriver(driverName, dc.Global.MountPath, map[string]interface{}{}) if err != nil { return nil, nil, err }