diff --git a/cmd/provisioner-localpv/app/config.go b/cmd/provisioner-localpv/app/config.go index 129d90a8b8..eac37cb518 100644 --- a/cmd/provisioner-localpv/app/config.go +++ b/cmd/provisioner-localpv/app/config.go @@ -37,12 +37,47 @@ const ( //KeyPVStorageType defines if the PV should be backed // a hostpath ( sub directory or a storage device) KeyPVStorageType = "StorageType" + //KeyPVBasePath defines base directory for hostpath volumes // can be configured via the StorageClass annotations. KeyPVBasePath = "BasePath" + //KeyPVFSType defines filesystem type to be used with devices // and can be configured via the StorageClass annotations. KeyPVFSType = "FSType" + + //KeyBDTag defines the value for the Block Device Tag + //label selector configured via the StorageClass annotations. + //User can group block devices across nodes by setting the + //label on block devices as: + // openebs.io/block-device-tag= + // + //The used above can be passsed to the + //Local PV device provisioner via the StorageClass + //CAS annotations, to specify that Local PV (device) + //should only make use of those block devices that + //tagged with the given . + // + //Example: Local PV device StorageClass for picking devices + //labeled as: openebs.io/block-device-tag=tag-x + //will be as follows + // + // kind: StorageClass + // metadata: + // name: openebs-device-tag-x + // annotations: + // openebs.io/cas-type: local + // cas.openebs.io/config: | + // - name: StorageType + // value: "device" + // - name: BlockDeviceTag + // value: "tag-x" + // provisioner: openebs.io/local + // volumeBindingMode: WaitForFirstConsumer + // reclaimPolicy: Delete + // + KeyBDTag = "BlockDeviceTag" + //KeyPVRelativePath defines the alternate folder name under the BasePath // By default, the pv name will be used as the folder name. // KeyPVBasePath can be useful for providing the same underlying folder @@ -139,6 +174,20 @@ func (c *VolumeConfig) GetFSType() string { return fsType } +//GetBDTagValue returns the block device tag +//value configured in StorageClass. +// +//Default is "", no device tag will be set and any +//available block device (without labelled with tag) +//can be used for creating Local PV(device). +func (c *VolumeConfig) GetBDTagValue() string { + bdTagValue := c.getValue(KeyBDTag) + if len(strings.TrimSpace(bdTagValue)) == 0 { + return "" + } + return bdTagValue +} + //GetPath returns a valid PV path based on the configuration // or an error. The Path is constructed using the following rules: // If AbsolutePath is specified return it. (Future) diff --git a/cmd/provisioner-localpv/app/helper_blockdevice.go b/cmd/provisioner-localpv/app/helper_blockdevice.go index f2c3238278..1237a28d8f 100644 --- a/cmd/provisioner-localpv/app/helper_blockdevice.go +++ b/cmd/provisioner-localpv/app/helper_blockdevice.go @@ -65,6 +65,10 @@ type HelperBlockDeviceOptions struct { bdcName string // volumeMode of PVC volumeMode corev1.PersistentVolumeMode + + //bdTagValue is the value passed for + // BlockDeviceTag via StorageClass config + bdTagValue string } // validate checks that the required fields to create BDC @@ -120,14 +124,20 @@ func (p *Provisioner) createBlockDeviceClaim(blkDevOpts *HelperBlockDeviceOption return nil } - bdcObj, err := blockdeviceclaim.NewBuilder(). + bdcObjBuilder := blockdeviceclaim.NewBuilder(). WithNamespace(p.namespace). WithName(bdcName). WithHostName(blkDevOpts.nodeHostname). WithCapacity(blkDevOpts.capacity). WithFinalizer(LocalPVFinalizer). - WithBlockVolumeMode(blkDevOpts.volumeMode). - Build() + WithBlockVolumeMode(blkDevOpts.volumeMode) + + // If bdTagValue is configure, set it on the BDC + if len(blkDevOpts.bdTagValue) > 0 { + bdcObjBuilder.WithBlockDeviceTag(blkDevOpts.bdTagValue) + } + + bdcObj, err := bdcObjBuilder.Build() if err != nil { //TODO : Need to relook at this error diff --git a/cmd/provisioner-localpv/app/provisioner_blockdevice.go b/cmd/provisioner-localpv/app/provisioner_blockdevice.go index 18bec2649e..0c97088d90 100644 --- a/cmd/provisioner-localpv/app/provisioner_blockdevice.go +++ b/cmd/provisioner-localpv/app/provisioner_blockdevice.go @@ -45,6 +45,7 @@ func (p *Provisioner) ProvisionBlockDevice(opts pvController.VolumeOptions, volu name: name, capacity: capacity.String(), volumeMode: *opts.PVC.Spec.VolumeMode, + bdTagValue: volumeConfig.GetBDTagValue(), } path, blkPath, err := p.getBlockDevicePath(blkDevOpts) diff --git a/pkg/blockdeviceclaim/v1alpha1/build.go b/pkg/blockdeviceclaim/v1alpha1/build.go index eb896413ef..27b806d6d5 100644 --- a/pkg/blockdeviceclaim/v1alpha1/build.go +++ b/pkg/blockdeviceclaim/v1alpha1/build.go @@ -31,8 +31,13 @@ const ( // StoragePoolKindCSPC holds the value of CStorPoolCluster StoragePoolKindCSPC = "CStorPoolCluster" + // APIVersion holds the value of OpenEBS version APIVersion = "openebs.io/v1alpha1" + + // bdTagKey defines the label selector key + // used for grouping block devices using a tag. + bdTagKey = "openebs.io/block-device-tag" ) // Builder is the builder object for BlockDeviceClaim @@ -323,3 +328,28 @@ func (b *Builder) WithBlockVolumeMode(mode corev1.PersistentVolumeMode) *Builder return b } + +// WithBlockDeviceTag appends (or creates) the BDC Label Selector +// by setting the provided value to the fixed key +// openebs.io/block-device-tag +// This will enable the NDM to pick only devices that +// match the node (hostname) and block device tag value. +func (b *Builder) WithBlockDeviceTag(bdTagValue string) *Builder { + if len(bdTagValue) == 0 { + b.errs = append( + b.errs, + errors.New("failed to build BDC object: missing block device tag value"), + ) + return b + } + + if b.BDC.Object.Spec.Selector == nil { + b.BDC.Object.Spec.Selector = &metav1.LabelSelector{} + } + if b.BDC.Object.Spec.Selector.MatchLabels == nil { + b.BDC.Object.Spec.Selector.MatchLabels = map[string]string{} + } + + b.BDC.Object.Spec.Selector.MatchLabels[bdTagKey] = bdTagValue + return b +} diff --git a/pkg/blockdeviceclaim/v1alpha1/build_test.go b/pkg/blockdeviceclaim/v1alpha1/build_test.go index da1245747a..916ede042d 100644 --- a/pkg/blockdeviceclaim/v1alpha1/build_test.go +++ b/pkg/blockdeviceclaim/v1alpha1/build_test.go @@ -206,16 +206,46 @@ func TestBuildWithCapacity(t *testing.T) { } } +func TestBuilderWithBlockDeviceTag(t *testing.T) { + tests := map[string]struct { + tag string + expectErr bool + }{ + "Test Builder with tag": { + tag: "test", + expectErr: false, + }, + "Test Builder without tag": { + tag: "", + expectErr: true, + }, + } + for name, mock := range tests { + name, mock := name, mock + t.Run(name, func(t *testing.T) { + b := NewBuilder().WithBlockDeviceTag(mock.tag) + if mock.expectErr && len(b.errs) == 0 { + t.Fatalf("Test %q failed: expected error not to be nil", name) + } + if !mock.expectErr && len(b.errs) > 0 { + t.Fatalf("Test %q failed: expected error to be nil", name) + } + }) + } +} + func TestBuild(t *testing.T) { tests := map[string]struct { name string capacity string + tagValue string expectedBDC *apis.BlockDeviceClaim expectedErr bool }{ "BDC with correct details": { name: "BDC1", capacity: "10Ti", + tagValue: "", expectedBDC: &apis.BlockDeviceClaim{ ObjectMeta: metav1.ObjectMeta{Name: "BDC1"}, Spec: apis.DeviceClaimSpec{ @@ -228,9 +258,31 @@ func TestBuild(t *testing.T) { }, expectedErr: false, }, + "BDC with correct details, including device pool": { + name: "BDC1", + capacity: "10Ti", + tagValue: "test", + expectedBDC: &apis.BlockDeviceClaim{ + ObjectMeta: metav1.ObjectMeta{Name: "BDC1"}, + Spec: apis.DeviceClaimSpec{ + Resources: apis.DeviceClaimResources{ + Requests: corev1.ResourceList{ + corev1.ResourceName(ndm.ResourceStorage): fakeCapacity("10Ti"), + }, + }, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + bdTagKey: "test", + }, + }, + }, + }, + expectedErr: false, + }, "BDC with error": { name: "", capacity: "500Gi", + tagValue: "test", expectedBDC: nil, expectedErr: true, }, @@ -238,7 +290,16 @@ func TestBuild(t *testing.T) { for name, mock := range tests { name, mock := name, mock t.Run(name, func(t *testing.T) { - bdcObj, err := NewBuilder().WithName(mock.name).WithCapacity(mock.capacity).Build() + bdcObjBuilder := NewBuilder(). + WithName(mock.name). + WithCapacity(mock.capacity) + + if len(mock.tagValue) > 0 { + bdcObjBuilder.WithBlockDeviceTag(mock.tagValue) + } + + bdcObj, err := bdcObjBuilder.Build() + if mock.expectedErr && err == nil { t.Fatalf("Test %q failed: expected error not to be nil", name) } diff --git a/tests/localpv/README.md b/tests/localpv/README.md new file mode 100644 index 0000000000..e4e0c99e7c --- /dev/null +++ b/tests/localpv/README.md @@ -0,0 +1,40 @@ +# Local PV Provisioner BDD + +Local PV Provisioner BDD tests are developed using ginkgo & gomega libraries. + +## How to run the tests? + +### Pre-requisites + +- Install Ginkgo and Gomega on your development machine. + ``` + $ go get github.com/onsi/ginkgo/ginkgo + $ go get github.com/onsi/gomega/... + ``` +- Get your Kubernetes Cluster ready and make sure you can run + kubectl from your development machine. + Note down the path to the `kubeconfig` file used by kubectl + to access your cluster. Example: /home//.kube/config + +- (Optional) Set the KUBECONFIG environment variable on your + development machine to point to the kubeconfig file. + Example: KUBECONFIG=/home//.kube/config + + If you do not set this ENV, you will have to pass the file + to the ginkgo CLI + +- Some of the tests require block devices (that are not mounted) + to be available in the cluster. + +- Install required OpenEBS components. + Example: `kubectl apply -f openebs-operator.yaml` + +### Run tests + +- Run the tests by being in the localpv tests folder. + `$ cd $GOPATH/src/github.com/openebs/maya/tests/localpv/` + `$ ginkgo -v --` + + In case the KUBECONFIG env is not configured, you can run: + `$ ginkgo -v -- -kubeconfig=/path/to/kubeconfig` + diff --git a/tests/localpv/suite_test.go b/tests/localpv/suite_test.go index 9c89cf38cf..fed54a348d 100644 --- a/tests/localpv/suite_test.go +++ b/tests/localpv/suite_test.go @@ -16,6 +16,7 @@ package localpv import ( "flag" + "os" "testing" . "github.com/onsi/ginkgo" @@ -45,7 +46,7 @@ func TestSource(t *testing.T) { } func init() { - flag.StringVar(&kubeConfigPath, "kubeconfig", "", "path to kubeconfig to invoke kubernetes API calls") + flag.StringVar(&kubeConfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "path to kubeconfig to invoke kubernetes API calls") } var ops *tests.Operations