github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/select/blockdevice/filters.go (about)

     1  /*
     2  Copyright 2019 The OpenEBS Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package blockdevice
    18  
    19  import (
    20  	"strings"
    21  
    22  	apis "github.com/openebs/node-disk-manager/api/v1alpha1"
    23  	"github.com/openebs/node-disk-manager/blockdevice"
    24  	"github.com/openebs/node-disk-manager/cmd/ndm_daemonset/controller"
    25  	"github.com/openebs/node-disk-manager/db/kubernetes"
    26  	"github.com/openebs/node-disk-manager/pkg/select/verify"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/labels"
    29  	"k8s.io/apimachinery/pkg/selection"
    30  	"k8s.io/klog/v2"
    31  )
    32  
    33  const (
    34  	// FilterActive is the filter for getting active BDs
    35  	FilterActive = "filterActive"
    36  	// FilterUnclaimed is the filter for getting unclaimed BDs
    37  	FilterUnclaimed = "filterUnclaimed"
    38  	// FilterDeviceType is the filter for matching DeviceType like SSD, HDD, sparse
    39  	FilterDeviceType = "filterDeviceType"
    40  	// FilterVolumeMode is the  filter for filtering based on Volume Mode required
    41  	FilterVolumeMode = "filterVolumeMode"
    42  	// FilterBlockDeviceName is the filter for getting a BD based on a name
    43  	FilterBlockDeviceName = "filterBlockDeviceName"
    44  	// FilterResourceStorage is the filter for matching resource storage
    45  	FilterResourceStorage = "filterResourceStorage"
    46  	// FilterOutSparseBlockDevices is used to filter out sparse BDs
    47  	FilterOutSparseBlockDevices = "filterSparseBlockDevice"
    48  	// FilterNodeName is used to filter based on nodename
    49  	FilterNodeName = "filterNodeName"
    50  	// FilterBlockDeviceTag is used to filter out blockdevices having
    51  	// openebs.io/blockdevice-tag label
    52  	FilterBlockDeviceTag = "filterBlockDeviceTag"
    53  	// FilterOutLegacyAnnotation is used to filter out devices with legacy annotation
    54  	FilterOutLegacyAnnotation = "filterOutLegacyAnnotation"
    55  )
    56  
    57  const (
    58  	internalUUIDSchemeAnnotation = "internal.openebs.io/uuid-scheme"
    59  	legacyUUIDScheme             = "legacy"
    60  )
    61  
    62  // filterFunc is the func type for the filter functions
    63  type filterFunc func(original *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList
    64  
    65  var filterFuncMap = map[string]filterFunc{
    66  	FilterActive:                filterActive,
    67  	FilterUnclaimed:             filterUnclaimed,
    68  	FilterDeviceType:            filterDeviceType,
    69  	FilterVolumeMode:            filterVolumeMode,
    70  	FilterBlockDeviceName:       filterBlockDeviceName,
    71  	FilterResourceStorage:       filterResourceStorage,
    72  	FilterOutSparseBlockDevices: filterOutSparseBlockDevice,
    73  	FilterNodeName:              filterNodeName,
    74  	FilterBlockDeviceTag:        filterBlockDeviceTag,
    75  	FilterOutLegacyAnnotation:   filterOutLegacyAnnotation,
    76  }
    77  
    78  // ApplyFilters apply the filter specified in the filterkeys on the given BD List,
    79  func (c *Config) ApplyFilters(bdList *apis.BlockDeviceList, filterKeys ...string) *apis.BlockDeviceList {
    80  	filteredList := bdList
    81  	for _, key := range filterKeys {
    82  		filteredList = filterFuncMap[key](filteredList, c.ClaimSpec)
    83  	}
    84  	return filteredList
    85  }
    86  
    87  // filterActive filters out active Blockdevices from the BDList
    88  func filterActive(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
    89  	filteredBDList := &apis.BlockDeviceList{
    90  		TypeMeta: metav1.TypeMeta{
    91  			Kind:       "BlockDevice",
    92  			APIVersion: "openebs.io/v1alpha1",
    93  		},
    94  	}
    95  
    96  	for _, bd := range originalBD.Items {
    97  		if bd.Status.State == controller.NDMActive {
    98  			filteredBDList.Items = append(filteredBDList.Items, bd)
    99  		}
   100  	}
   101  	return filteredBDList
   102  }
   103  
   104  // filterUnclaimed returns only unclaimed devices
   105  func filterUnclaimed(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   106  	filteredBDList := &apis.BlockDeviceList{
   107  		TypeMeta: metav1.TypeMeta{
   108  			Kind:       "BlockDevice",
   109  			APIVersion: "openebs.io/v1alpha1",
   110  		},
   111  	}
   112  
   113  	for _, bd := range originalBD.Items {
   114  		if bd.Status.ClaimState == apis.BlockDeviceUnclaimed {
   115  			filteredBDList.Items = append(filteredBDList.Items, bd)
   116  		}
   117  	}
   118  	return filteredBDList
   119  }
   120  
   121  // filterDeviceType returns only BDs which match the device type
   122  func filterDeviceType(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   123  
   124  	// if no device type is specified, filter will not be effective
   125  	if spec.DeviceType == "" {
   126  		return originalBD
   127  	}
   128  
   129  	filteredBDList := &apis.BlockDeviceList{
   130  		TypeMeta: metav1.TypeMeta{
   131  			Kind:       "BlockDevice",
   132  			APIVersion: "openebs.io/v1alpha1",
   133  		},
   134  	}
   135  
   136  	for _, bd := range originalBD.Items {
   137  		if bd.Spec.Details.DeviceType == spec.DeviceType {
   138  			filteredBDList.Items = append(filteredBDList.Items, bd)
   139  		}
   140  	}
   141  	return filteredBDList
   142  }
   143  
   144  // filterVolumeMode returns only BDs which matches the specified volume mode
   145  func filterVolumeMode(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   146  
   147  	volumeMode := spec.Details.BlockVolumeMode
   148  
   149  	// if volume mode is not specified in claim spec, this filter will not be effective
   150  	if volumeMode == "" {
   151  		return originalBD
   152  	}
   153  
   154  	filteredBDList := &apis.BlockDeviceList{
   155  		TypeMeta: metav1.TypeMeta{
   156  			Kind:       "BlockDevice",
   157  			APIVersion: "openebs.io/v1alpha1",
   158  		},
   159  	}
   160  
   161  	for _, bd := range originalBD.Items {
   162  		switch volumeMode {
   163  		case apis.VolumeModeBlock:
   164  			// if blockvolume mode, FS and Mountpoint should be empty
   165  			if bd.Spec.FileSystem.Type != "" || bd.Spec.FileSystem.Mountpoint != "" {
   166  				continue
   167  			}
   168  
   169  		case apis.VolumeModeFileSystem:
   170  			// in FSVolume Mode,
   171  			// In BD: FS and Mountpoint should not be empty. If empty the BD
   172  			// is removed by filter
   173  			if bd.Spec.FileSystem.Type == "" || bd.Spec.FileSystem.Mountpoint == "" {
   174  				continue
   175  			}
   176  			// In BDC: If DeviceFormat is specified, then it should match with BD,
   177  			// else do not compare FSType. If the check fails, the BD is removed by the filter.
   178  			if spec.Details.DeviceFormat != "" && bd.Spec.FileSystem.Type != spec.Details.DeviceFormat {
   179  				continue
   180  			}
   181  		}
   182  		filteredBDList.Items = append(filteredBDList.Items, bd)
   183  	}
   184  	return filteredBDList
   185  }
   186  
   187  // filterBlockDeviceName returns a single BD in the list, which matches the BDName
   188  func filterBlockDeviceName(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   189  	filteredBDList := &apis.BlockDeviceList{
   190  		TypeMeta: metav1.TypeMeta{
   191  			Kind:       "BlockDevice",
   192  			APIVersion: "openebs.io/v1alpha1",
   193  		},
   194  	}
   195  
   196  	for _, bd := range originalBD.Items {
   197  		if bd.Name == spec.BlockDeviceName {
   198  			filteredBDList.Items = append(filteredBDList.Items, bd)
   199  			break
   200  		}
   201  	}
   202  	return filteredBDList
   203  }
   204  
   205  // filterResourceStorage gets the devices which match the storage resource requirement
   206  func filterResourceStorage(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   207  	filteredBDList := &apis.BlockDeviceList{
   208  		TypeMeta: metav1.TypeMeta{
   209  			Kind:       "BlockDevice",
   210  			APIVersion: "openebs.io/v1alpha1",
   211  		},
   212  	}
   213  
   214  	capacity, _ := verify.GetRequestedCapacity(spec.Resources.Requests)
   215  
   216  	for _, bd := range originalBD.Items {
   217  		if bd.Spec.Capacity.Storage >= uint64(capacity) {
   218  			filteredBDList.Items = append(filteredBDList.Items, bd)
   219  			break
   220  		}
   221  	}
   222  	return filteredBDList
   223  }
   224  
   225  // filterOutSparseBlockDevice returns only non sparse devices
   226  func filterOutSparseBlockDevice(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   227  	filteredBDList := &apis.BlockDeviceList{
   228  		TypeMeta: metav1.TypeMeta{
   229  			Kind:       "BlockDevice",
   230  			APIVersion: "openebs.io/v1alpha1",
   231  		},
   232  	}
   233  
   234  	for _, bd := range originalBD.Items {
   235  		if bd.Spec.Details.DeviceType != blockdevice.SparseBlockDeviceType {
   236  			filteredBDList.Items = append(filteredBDList.Items, bd)
   237  		}
   238  	}
   239  	return filteredBDList
   240  }
   241  
   242  func filterNodeName(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   243  
   244  	// if node name is not given in BDC, this filter will not work
   245  	if len(spec.BlockDeviceNodeAttributes.NodeName) == 0 {
   246  		return originalBD
   247  	}
   248  
   249  	filteredBDList := &apis.BlockDeviceList{
   250  		TypeMeta: metav1.TypeMeta{
   251  			Kind:       "BlockDevice",
   252  			APIVersion: "openebs.io/v1alpha1",
   253  		},
   254  	}
   255  
   256  	for _, bd := range originalBD.Items {
   257  		if bd.Spec.NodeAttributes.NodeName == spec.BlockDeviceNodeAttributes.NodeName {
   258  			filteredBDList.Items = append(filteredBDList.Items, bd)
   259  		}
   260  	}
   261  	return filteredBDList
   262  }
   263  
   264  // filterBlockDeviceTag is used to filter out BlockDevices which do not have the
   265  // block-device-tag label. This filter works on a block device list which has
   266  // already been filtered by the given selector.
   267  func filterBlockDeviceTag(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   268  
   269  	/// remove BDs with empty tag in all cases
   270  	emptyTagFilteredBDList := &apis.BlockDeviceList{
   271  		TypeMeta: metav1.TypeMeta{
   272  			Kind:       "BlockDevice",
   273  			APIVersion: "openebs.io/v1alpha1",
   274  		},
   275  	}
   276  	// This is given as a separate filter loop that works in all cases, irrespective of whether the user
   277  	// has specified the the label selector in the BDC or not.
   278  	for _, bd := range originalBD.Items {
   279  		// if the tag label value is empty then the BD should not be selected
   280  		if val, ok := bd.Labels[kubernetes.BlockDeviceTagLabel]; ok {
   281  			if len(strings.TrimSpace(val)) == 0 {
   282  				continue
   283  			}
   284  		}
   285  		emptyTagFilteredBDList.Items = append(emptyTagFilteredBDList.Items, bd)
   286  	}
   287  
   288  	// if the block-device-tag label was already included in the selector
   289  	// given in the BDC by the user, then this filter is not required. This
   290  	// is because it would have already performed the filter operation with the
   291  	// label. If the label is not present, a new selector is made to remove
   292  	// devices which have that label.
   293  	if !isBDTagDoesNotExistSelectorRequired(spec.Selector) {
   294  		return emptyTagFilteredBDList
   295  	}
   296  
   297  	// a DoesNotExist requirement is created to filter out devices which have
   298  	// the block-device-tag label
   299  	blockDeviceTagRequirement, err := labels.NewRequirement(kubernetes.BlockDeviceTagLabel, selection.DoesNotExist, []string{})
   300  
   301  	// this error should never occur, because error for DoesNotExist happen
   302  	// only when non zero length of values are passed
   303  	if err != nil {
   304  		klog.Info("could not create requirement for label ", kubernetes.BlockDeviceTagLabel)
   305  		return emptyTagFilteredBDList
   306  	}
   307  
   308  	blockDeviceTagDoesNotExistSelector := labels.NewSelector()
   309  	blockDeviceTagDoesNotExistSelector =
   310  		blockDeviceTagDoesNotExistSelector.Add(*blockDeviceTagRequirement)
   311  
   312  	filteredBDList := &apis.BlockDeviceList{
   313  		TypeMeta: metav1.TypeMeta{
   314  			Kind:       "BlockDevice",
   315  			APIVersion: "openebs.io/v1alpha1",
   316  		},
   317  	}
   318  
   319  	for _, bd := range emptyTagFilteredBDList.Items {
   320  		// if the tag label is not present, the BD will be included in the list
   321  		if blockDeviceTagDoesNotExistSelector.Matches(labels.Set(bd.Labels)) {
   322  			filteredBDList.Items = append(filteredBDList.Items, bd)
   323  		}
   324  	}
   325  	return filteredBDList
   326  }
   327  
   328  // filterOutLegacyAnnotation removes all blockdevices which has the legacy annotation
   329  // TODO @akhilerm add test cases for this function.
   330  func filterOutLegacyAnnotation(originalBD *apis.BlockDeviceList, spec *apis.DeviceClaimSpec) *apis.BlockDeviceList {
   331  	filteredBDList := &apis.BlockDeviceList{
   332  		TypeMeta: metav1.TypeMeta{
   333  			Kind:       "BlockDevice",
   334  			APIVersion: "openebs.io/v1alpha1",
   335  		},
   336  	}
   337  
   338  	for _, bd := range originalBD.Items {
   339  		if bd.Annotations != nil {
   340  			if uuidScheme, ok := bd.Annotations[internalUUIDSchemeAnnotation]; ok &&
   341  				uuidScheme == legacyUUIDScheme {
   342  				continue
   343  			}
   344  		}
   345  		filteredBDList.Items = append(filteredBDList.Items, bd)
   346  	}
   347  
   348  	return filteredBDList
   349  }
   350  
   351  // isBDTagDoesNotExistSelectorRequired is used to check whether a selector
   352  // was present on the BDC. It is used to decide whether a `does not exist` selector
   353  // for the block-device-tag label should be applied or not.
   354  //
   355  // all the filters are applied after the List call which uses the selector.
   356  // therefore, we don't need to again apply a label selector.
   357  func isBDTagDoesNotExistSelectorRequired(options *metav1.LabelSelector) bool {
   358  	if options == nil {
   359  		return true
   360  	}
   361  	if _, ok := options.MatchLabels[kubernetes.BlockDeviceTagLabel]; ok {
   362  		return false
   363  	}
   364  	requirements := options.MatchExpressions
   365  	for _, req := range requirements {
   366  		if req.Key == kubernetes.BlockDeviceTagLabel {
   367  			return false
   368  		}
   369  	}
   370  	return true
   371  }