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 }