diff --git a/pkg/sys/sys_test.go b/pkg/sys/sys_test.go index 985344a35..db49e296d 100644 --- a/pkg/sys/sys_test.go +++ b/pkg/sys/sys_test.go @@ -99,83 +99,3 @@ func TestGetBlockFile(t1 *testing.T) { } } - -func TestGetRootBlockFile(t1 *testing.T) { - - testCases := []struct { - name string - devName string - rootFile string - }{ - { - name: "test1", - devName: "/dev/xvdb", - rootFile: "/dev/xvdb", - }, - { - name: "test2", - devName: "/dev/xvdb1", - rootFile: "/dev/xvdb1", - }, - { - name: "test3", - devName: "/var/lib/direct-csi/devices/xvdb", - rootFile: "/dev/xvdb", - }, - { - name: "test4", - devName: "/var/lib/direct-csi/devices/xvdb-part-3", - rootFile: "/dev/xvdb3", - }, - { - name: "test5", - devName: "/var/lib/direct-csi/devices/xvdb-part-15", - rootFile: "/dev/xvdb15", - }, - { - name: "test6", - devName: "/var/lib/direct-csi/devices/nvmen1p-part-4", - rootFile: "/dev/nvmen1p4", - }, - { - name: "test7", - devName: "/var/lib/direct-csi/devices/nvmen12p-part-11", - rootFile: "/dev/nvmen12p11", - }, - { - name: "test8", - devName: "/var/lib/direct-csi/devices/loop0", - rootFile: "/dev/loop0", - }, - { - name: "test9", - devName: "/var/lib/direct-csi/devices/loop-part-5", - rootFile: "/dev/loop5", - }, - { - name: "test10", - devName: "/var/lib/direct-csi/devices/loop-part-12", - rootFile: "/dev/loop12", - }, - { - name: "test11", - devName: "loop12", - rootFile: "/dev/loop12", - }, - { - name: "test12", - devName: "loop0", - rootFile: "/dev/loop0", - }, - } - - for _, tt := range testCases { - t1.Run(tt.name, func(t1 *testing.T) { - rootFile := getRootBlockFile(tt.devName) - if rootFile != tt.rootFile { - t1.Errorf("Test case name %s: Expected root file = (%s) got: %s", tt.name, tt.rootFile, rootFile) - } - }) - } - -} diff --git a/pkg/sys/sys_utils.go b/pkg/sys/sys_utils.go index 3a75fb685..0ce6e9042 100644 --- a/pkg/sys/sys_utils.go +++ b/pkg/sys/sys_utils.go @@ -50,21 +50,6 @@ func makeBlockDeviceName(devName string) string { return strings.Join([]string{dName, partNumStr}, DirectCSIPartitionInfix) } -func getRootBlockFile(devName string) string { - if strings.Contains(devName, DirectCSIDevRoot) { - return getRootBlockFile(filepath.Base(devName)) - } - if strings.HasPrefix(devName, HostDevRoot) { - return devName - } - return filepath.Join(HostDevRoot, makeRootDeviceName(devName)) -} - -func makeRootDeviceName(devName string) string { - cleanPrefix := strings.Replace(devName, DirectCSIPartitionInfix, "", 1) - return strings.ReplaceAll(cleanPrefix, DirectCSIPartitionInfix, HostPartitionInfix) -} - func splitDevAndPartNum(s string) (string, int) { possibleNum := strings.Builder{} toRet := strings.Builder{} diff --git a/pkg/uevent/indexer.go b/pkg/uevent/indexer.go index 5c2e91e07..86d2a76ea 100644 --- a/pkg/uevent/indexer.go +++ b/pkg/uevent/indexer.go @@ -29,7 +29,6 @@ import ( var ( errNotDirectCSIDriveObject = errors.New("not a directcsidrive object") - errNoMatchFound = errors.New("no matching drive found") ) type indexer struct { @@ -120,15 +119,6 @@ func (i *indexer) validateDevice(device *sys.Device) (bool, error) { return true, nil } -func (i *indexer) getMatchingDrive(device *sys.Device) (*directcsi.DirectCSIDrive, error) { - filteredDrives, err := i.filterDrivesByUEventFSUUID(device.UeventFSUUID) - if err != nil { - return nil, err - } - // To-Do/Fix-me: run matching algorithm to find matching drive - return filteredDrives[0], errNoMatchFound -} - func (i *indexer) filterDrivesByUEventFSUUID(fsuuid string) ([]*directcsi.DirectCSIDrive, error) { objects := i.store.List() filteredDrives := []*directcsi.DirectCSIDrive{} @@ -147,3 +137,19 @@ func (i *indexer) filterDrivesByUEventFSUUID(fsuuid string) ([]*directcsi.Direct } return filteredDrives, nil } + +func (i *indexer) listDrives() ([]*directcsi.DirectCSIDrive, error) { + objects := i.store.List() + drives := []*directcsi.DirectCSIDrive{} + for _, obj := range objects { + directCSIDrive, ok := obj.(*directcsi.DirectCSIDrive) + if !ok { + return nil, errNotDirectCSIDriveObject + } + if directCSIDrive.Status.NodeName != i.nodeID { + continue + } + drives = append(drives, directCSIDrive) + } + return drives, nil +} diff --git a/pkg/uevent/listener.go b/pkg/uevent/listener.go index 3b51dddec..b76f273b9 100644 --- a/pkg/uevent/listener.go +++ b/pkg/uevent/listener.go @@ -41,6 +41,8 @@ const ( Add action = "add" Change action = "change" Remove action = "remove" + // internal + Sync action = "sync" ) var ( @@ -190,46 +192,74 @@ func (l *listener) handle(ctx context.Context, dEvent *deviceEvent) error { if err := device.ProbeHostInfo(); err != nil { // if drive is deleted + // FIXME: should we ignore errNotExist event for update, add and sync? if !errors.Is(err, os.ErrNotExist) { return err } } + drives, err := l.indexer.listDrives() + if err != nil { + return err + } + drive, matchResult := runMatchers(drives, device, stageOneMatchers, stageTwoMatchers) + switch dEvent.action { - case Add, Change: - return l.processUpdate(ctx, device) + case Add: + return l.processAdd(ctx, matchResult, device, drive) + case Change, Sync: + return l.processUpdate(ctx, matchResult, device, drive) case Remove: - return l.processRemove(ctx, device) + return l.processRemove(ctx, matchResult, device, drive) default: return fmt.Errorf("invalid device action: %s", dEvent.action) } } -func (l *listener) processUpdate(ctx context.Context, device *sys.Device) error { - drive, err := l.indexer.getMatchingDrive(device) - switch { - case errors.Is(err, errNoMatchFound): +func (l *listener) processAdd(ctx context.Context, matchResult matchResult, device *sys.Device, drive *directcsi.DirectCSIDrive) error { + switch matchResult { + case noMatch: + return l.handler.Add(ctx, device) + case changed, noChange: + klog.V(3).Infof("ignoring ADD action for the device %s as the corresponding drive match %s is found", device.DevPath(), drive.Name) + return nil + case tooManyMatches: + klog.V(3).Infof("ignoring ADD action as too many matches are found for the device %s", device.DevPath()) + return nil + default: + return fmt.Errorf("invalid match result: %v", matchResult) + } +} + +func (l *listener) processUpdate(ctx context.Context, matchResult matchResult, device *sys.Device, drive *directcsi.DirectCSIDrive) error { + switch matchResult { + case noMatch: return l.handler.Add(ctx, device) - case err == nil: + case changed: return l.handler.Change(ctx, device, drive) + case noChange: + return nil + case tooManyMatches: + return fmt.Errorf("too many matches found for device %s while processing UPDATE", device.DevPath()) default: - return err + return fmt.Errorf("invalid match result: %v", matchResult) } } -func (l *listener) processRemove(ctx context.Context, device *sys.Device) error { - drive, err := l.indexer.getMatchingDrive(device) - switch { - case errors.Is(err, errNoMatchFound): +func (l *listener) processRemove(ctx context.Context, matchResult matchResult, device *sys.Device, drive *directcsi.DirectCSIDrive) error { + switch matchResult { + case noMatch: klog.V(3).InfoS( "matching drive not found", "ACTION", Remove, "DEVICE", device.Name) return nil - case err == nil: + case changed, noChange: return l.handler.Remove(ctx, device, drive) + case tooManyMatches: + return fmt.Errorf("too many matches found for device %s while processing DELETE", device.DevPath()) default: - return err + return fmt.Errorf("invalid match result: %v", matchResult) } } @@ -253,7 +283,7 @@ func (l *listener) getNextDeviceUEvent(ctx context.Context) (*deviceEvent, error func (dEvent *deviceEvent) collectUDevData() error { switch dEvent.action { - case Add, Change: + case Add, Change, Sync: // Older kernels like in CentOS 7 does not send all information about the device, // hence read relevant data from /run/udev/data/b: runUdevDataMap, err := sys.ReadRunUdevDataByMajorMinor(dEvent.udevData.Major, dEvent.udevData.Minor) @@ -300,19 +330,6 @@ func (dEvent *deviceEvent) fillMissingUdevData(runUdevData *sys.UDevData) error return errValueMismatch(dEvent.udevData.Path, "partitionnum", dEvent.udevData.Partition, runUdevData.Partition) } - // Alternate pattern :- - // - // if runUdevData.WWID != "" { - // switch dEvent.udevData.WWID { - // case "": - // dEvent.udevData.WWID = runUdevData.WWID - // case runUdevData.WWID: - // default: - // errValueMismatch(dEvent.udevData.WWID, "WWID", dEvent.udevData.WWID, runUdevData.WWID) - // } - // } - // - if runUdevData.WWID != "" { if dEvent.udevData.WWID == "" { dEvent.udevData.WWID = runUdevData.WWID @@ -437,15 +454,11 @@ func (l *listener) sync() error { event := &deviceEvent{ created: time.Now().UTC(), - action: Change, + action: Sync, udevData: runUdevData, devPath: runUdevData.Path, } - if err = event.fillMissingUdevData(runUdevData); err != nil { - return err - } - l.eventQueue.push(event) } diff --git a/pkg/uevent/match.go b/pkg/uevent/match.go new file mode 100644 index 000000000..7fef7f127 --- /dev/null +++ b/pkg/uevent/match.go @@ -0,0 +1,258 @@ +// This file is part of MinIO DirectPV +// Copyright (c) 2021, 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package uevent + +import ( + directcsi "github.com/minio/directpv/pkg/apis/direct.csi.min.io/v1beta3" + "github.com/minio/directpv/pkg/sys" + "k8s.io/klog/v2" +) + +// If the corresponding field is empty in the drive, return consider +// Else, return True if matched or False if not matched +type matchFn func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) + +type matchResult string + +const ( + noMatch matchResult = "nomatch" + tooManyMatches matchResult = "toomanymatches" + changed matchResult = "changed" + noChange matchResult = "nochange" +) + +// prioritiy based matchers +var stageOneMatchers = []matchFn{ + // HW matchers + partitionNumberMatcher, + ueventSerialNumberMatcher, + wwidMatcher, + modelNumberMatcher, + vendorMatcher, + // SW matchers + partitionTableUUIDMatcher, + partitionUUIDMatcher, + dmUUIDMatcher, + mdUUIDMatcher, + filesystemMatcher, + ueventFSUUIDMatcher, + // v1beta2 matchers + fsUUIDMatcher, + serialNumberMatcher, + // size matchers + logicalBlocksizeMatcher, + physicalBlocksizeMatcher, + totalCapacityMatcher, + allocatedCapacityMatcher, +} + +// stageTwoMatchers are the conslusive matchers for more than one matching drives +var stageTwoMatchers = []matchFn{ + majMinMatcher, + pathMatcher, +} + +// ------------------------ +// - Run the list of matchers on the list of drives +// - Match function SHOULD return matched (100% match) and considered (if the field is empty) results +// - If MORE THAN ONE matching drive is found, pass the matching drives to the next matching function in the priority list +// If NO matching drive is found +// AND If the considered list is empty, return the empty list (NEW DRIVE). +// Else, pass the considered list to the next matching function in the priority list +// (NOTE: the returning results can be more than one incase of duplicate/identical drives) +// ------------------------ +func runMatchers(drives []*directcsi.DirectCSIDrive, + device *sys.Device, + stageOneMatchers, stageTwoMatchers []matchFn) (*directcsi.DirectCSIDrive, matchResult) { + var matchedDrives, consideredDrives []*directcsi.DirectCSIDrive + var matched, updated bool + var err error + + for _, matchFn := range stageOneMatchers { + if len(drives) == 0 { + break + } + matchedDrives, consideredDrives, err = match(drives, device, matchFn) + if err != nil { + klog.V(3).Infof("error while matching drive %s: %v", device.DevPath(), err) + continue + } + switch { + case len(matchedDrives) > 0: + if len(matchedDrives) == 1 { + matched = true + } + drives = matchedDrives + default: + if len(consideredDrives) == 1 && matched { + updated = true + } + drives = consideredDrives + } + } + + if len(drives) > 1 { + for _, matchFn := range stageTwoMatchers { + // run identity matcher for more than one matched drives + drives, _, err = match(drives, device, matchFn) + if err != nil { + klog.V(3).Infof("error while matching drive %s: %v", device.DevPath(), err) + continue + } + } + } + + switch len(drives) { + case 0: + return nil, noMatch + case 1: + if updated { + return drives[0], changed + } + return drives[0], noChange + default: + return nil, tooManyMatches + } +} + +func majMinMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + if drive.Status.MajorNumber != uint32(device.Major) { + return false, false, nil + } + if drive.Status.MinorNumber != uint32(device.Minor) { + return false, false, nil + } + return true, true, nil +} + +func pathMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + if getRootBlockPath(drive.Status.Path) != device.DevPath() { + return false, false, nil + } + return true, true, nil +} + +// ------------------------ +// - Run the list of drives over the provided match function +// - If matched, append the drive to the matched list +// - If considered, append the the drive to the considered list +// ------------------------ +func match(drives []*directcsi.DirectCSIDrive, + device *sys.Device, + matchFn matchFn) ([]*directcsi.DirectCSIDrive, []*directcsi.DirectCSIDrive, error) { + var matchedDrives, consideredDrives []*directcsi.DirectCSIDrive + for _, drive := range drives { + if drive.Status.DriveStatus == directcsi.DriveStatusTerminating { + continue + } + match, consider, err := matchFn(drive, device) + if err != nil { + return nil, nil, err + } + if match { + matchedDrives = append(matchedDrives, drive) + } else if consider { + consideredDrives = append(consideredDrives, drive) + } + } + return matchedDrives, consideredDrives, nil +} + +func fsUUIDMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func ueventFSUUIDMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func serialNumberMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func ueventSerialNumberMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func wwidMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func modelNumberMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func vendorMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func partitionNumberMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func dmUUIDMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func mdUUIDMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func partitionUUIDMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func partitionTableUUIDMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func logicalBlocksizeMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func physicalBlocksizeMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func filesystemMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func totalCapacityMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} + +func allocatedCapacityMatcher(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + // To-Do: impelement matcher function + return +} diff --git a/pkg/uevent/match_test.go b/pkg/uevent/match_test.go new file mode 100644 index 000000000..6c4dd1a97 --- /dev/null +++ b/pkg/uevent/match_test.go @@ -0,0 +1,315 @@ +// This file is part of MinIO DirectPV +// Copyright (c) 2021, 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package uevent + +import ( + "reflect" + "testing" + + directcsi "github.com/minio/directpv/pkg/apis/direct.csi.min.io/v1beta3" + "github.com/minio/directpv/pkg/sys" + "github.com/minio/directpv/pkg/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestRunMatchers(t *testing.T) { + validDriveObjs := []*directcsi.DirectCSIDrive{ + { + TypeMeta: utils.DirectCSIDriveTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: "test_drive_1", + }, + Status: directcsi.DirectCSIDriveStatus{ + UeventSerial: "SERIAL1", + FilesystemUUID: "d9877501-e1b5-4bac-b73f-178b29974ed5", + }, + }, + { + TypeMeta: utils.DirectCSIDriveTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: "test_drive_2", + }, + Status: directcsi.DirectCSIDriveStatus{ + UeventSerial: "SERIAL2", + FilesystemUUID: "ertsdfff-e1b5-4bac-b73f-178b29974ed5", + }, + }, + } + + terminatingDriveObjects := []*directcsi.DirectCSIDrive{ + { + TypeMeta: utils.DirectCSIDriveTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: "test_drive_3", + }, + Status: directcsi.DirectCSIDriveStatus{ + UeventSerial: "SERIAL2", + FilesystemUUID: "ertsdfff-e1b5-4bac-b73f-178b29974ed5", + DriveStatus: directcsi.DriveStatusTerminating, + }, + }, + { + TypeMeta: utils.DirectCSIDriveTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: "test_drive_4", + }, + Status: directcsi.DirectCSIDriveStatus{ + UeventSerial: "SERIAL3", + FilesystemUUID: "ertsdfff-e1b5-4bac-b73f-178b29974ed5", + DriveStatus: directcsi.DriveStatusTerminating, + }, + }, + } + + driveObjects := append(validDriveObjs, terminatingDriveObjects...) + + testDevice := &sys.Device{ + FSUUID: "d9877501-e1b5-4bac-b73f-178b29974ed5", + UeventSerial: "SERIAL1", + } + + var matchCounter int + var stageTwoHit bool + + testCases := []struct { + name string + stageOnematchers []matchFn + stageTwoMatchers []matchFn + stageTwoHit bool + expectedDrive *directcsi.DirectCSIDrive + expectedMatchResult matchResult + expectedMatchHit int + }{ + { + name: "no_match_test", + stageOnematchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = false + consider = false + return + }, + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = false + consider = false + return + }, + }, + stageTwoMatchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + stageTwoHit = true + return + }, + }, + stageTwoHit: false, + expectedDrive: nil, + expectedMatchResult: noMatch, + expectedMatchHit: 1 * len(validDriveObjs), + }, + { + name: "more_than_one_considered_drives_test", + stageOnematchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = false + consider = true + return + }, + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = false + consider = true + return + }, + }, + stageTwoMatchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + stageTwoHit = true + match = true + consider = false + return + }, + }, + stageTwoHit: true, + expectedDrive: nil, + expectedMatchResult: tooManyMatches, + expectedMatchHit: 2 * len(validDriveObjs), + }, + { + name: "one_considered_drive_test", + stageOnematchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = drive.Status.UeventSerial == device.UeventSerial + consider = false + return + }, + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = false + consider = true + return + }, + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = false + consider = true + return + }, + }, + stageTwoMatchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + stageTwoHit = true + return + }, + }, + stageTwoHit: false, + expectedDrive: &directcsi.DirectCSIDrive{ + TypeMeta: utils.DirectCSIDriveTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: "test_drive_1", + }, + Status: directcsi.DirectCSIDriveStatus{ + FilesystemUUID: "d9877501-e1b5-4bac-b73f-178b29974ed5", + UeventSerial: "SERIAL1", + }, + }, + expectedMatchResult: changed, + expectedMatchHit: 1*len(validDriveObjs) + 1 + 1, + }, + { + name: "more_than_one_matched_test", + stageOnematchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = true + consider = false + return + }, + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = true + consider = false + return + }, + }, + stageTwoMatchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + stageTwoHit = true + match = true + consider = true + return + }, + }, + stageTwoHit: true, + expectedDrive: nil, + expectedMatchResult: tooManyMatches, + expectedMatchHit: 2 * len(validDriveObjs), + }, + { + name: "one_matched_drive_test", + stageOnematchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = drive.Status.UeventSerial == device.UeventSerial + consider = false + return + }, + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = drive.Status.FilesystemUUID == device.FSUUID + consider = false + return + }, + }, + stageTwoMatchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + stageTwoHit = true + return + }, + }, + stageTwoHit: false, + expectedDrive: &directcsi.DirectCSIDrive{ + TypeMeta: utils.DirectCSIDriveTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: "test_drive_1", + }, + Status: directcsi.DirectCSIDriveStatus{ + FilesystemUUID: "d9877501-e1b5-4bac-b73f-178b29974ed5", + UeventSerial: "SERIAL1", + }, + }, + expectedMatchResult: noChange, + expectedMatchHit: 1*len(validDriveObjs) + 1, + }, + { + name: "matched_and_considered_drives_test", + stageOnematchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = true + consider = true + return + }, + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + matchCounter++ + match = drive.Status.FilesystemUUID == device.FSUUID + consider = true + return + }, + }, + stageTwoMatchers: []matchFn{ + func(drive *directcsi.DirectCSIDrive, device *sys.Device) (match bool, consider bool, err error) { + stageTwoHit = true + return + }, + }, + stageTwoHit: false, + expectedDrive: &directcsi.DirectCSIDrive{ + TypeMeta: utils.DirectCSIDriveTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: "test_drive_1", + }, + Status: directcsi.DirectCSIDriveStatus{ + FilesystemUUID: "d9877501-e1b5-4bac-b73f-178b29974ed5", + UeventSerial: "SERIAL1", + }, + }, + expectedMatchResult: noChange, + expectedMatchHit: 2 * len(validDriveObjs), + }, + } + + for _, tt := range testCases { + matchCounter = 0 + stageTwoHit = false + drive, matchResult := runMatchers(driveObjects, testDevice, tt.stageOnematchers, tt.stageTwoMatchers) + if !reflect.DeepEqual(drive, tt.expectedDrive) { + t.Errorf("test: %s expected drive: %v but got: %v", tt.name, tt.expectedDrive, drive) + } + if matchCounter != tt.expectedMatchHit { + t.Errorf("test: %s expected mactchHit: %d but got: %d", tt.name, tt.expectedMatchHit, matchCounter) + } + if matchResult != tt.expectedMatchResult { + t.Errorf("test: %s expected matchResult: %v but got: %v", tt.name, tt.expectedMatchResult, matchResult) + } + if stageTwoHit != tt.stageTwoHit { + t.Errorf("test: %s expected stageTwoHit: %v but got: %v", tt.name, tt.stageTwoHit, stageTwoHit) + } + } +} diff --git a/pkg/uevent/utils.go b/pkg/uevent/utils.go index 0265d6116..e1490c560 100644 --- a/pkg/uevent/utils.go +++ b/pkg/uevent/utils.go @@ -17,7 +17,9 @@ package uevent import ( + "path/filepath" "strconv" + "strings" "github.com/minio/directpv/pkg/sys" ) @@ -65,3 +67,19 @@ func mapToUdevData(eventMap map[string]string) (*sys.UDevData, error) { FSType: eventMap["ID_FS_TYPE"], }, nil } + +func getRootBlockPath(devName string) string { + switch { + case strings.HasPrefix(devName, sys.HostDevRoot): + return devName + case strings.Contains(devName, sys.DirectCSIDevRoot): + return getRootBlockPath(filepath.Base(devName)) + default: + name := strings.ReplaceAll( + strings.Replace(devName, sys.DirectCSIPartitionInfix, "", 1), + sys.DirectCSIPartitionInfix, + sys.HostPartitionInfix, + ) + return filepath.Join(sys.HostDevRoot, name) + } +} diff --git a/pkg/uevent/utils_test.go b/pkg/uevent/utils_test.go index 2bbbf9d3c..e4231c0df 100644 --- a/pkg/uevent/utils_test.go +++ b/pkg/uevent/utils_test.go @@ -71,3 +71,88 @@ func TestMapToEventData(t *testing.T) { t.Fatalf("expected udevdata: %v, got: %v", udevData, expectedUEventData) } } + +func TestGetRootBlockPath(t1 *testing.T) { + + testCases := []struct { + name string + devName string + rootFile string + }{ + { + name: "test1", + devName: "/dev/xvdb", + rootFile: "/dev/xvdb", + }, + { + name: "test2", + devName: "/dev/xvdb1", + rootFile: "/dev/xvdb1", + }, + { + name: "test3", + devName: "/var/lib/direct-csi/devices/xvdb", + rootFile: "/dev/xvdb", + }, + { + name: "test4", + devName: "/var/lib/direct-csi/devices/xvdb-part-3", + rootFile: "/dev/xvdb3", + }, + { + name: "test5", + devName: "/var/lib/direct-csi/devices/xvdb-part-15", + rootFile: "/dev/xvdb15", + }, + { + name: "test6", + devName: "/var/lib/direct-csi/devices/nvmen1p-part-4", + rootFile: "/dev/nvmen1p4", + }, + { + name: "test7", + devName: "/var/lib/direct-csi/devices/nvmen12p-part-11", + rootFile: "/dev/nvmen12p11", + }, + { + name: "test8", + devName: "/var/lib/direct-csi/devices/loop0", + rootFile: "/dev/loop0", + }, + { + name: "test9", + devName: "/var/lib/direct-csi/devices/loop-part-5", + rootFile: "/dev/loop5", + }, + { + name: "test10", + devName: "/var/lib/direct-csi/devices/loop-part-12", + rootFile: "/dev/loop12", + }, + { + name: "test11", + devName: "loop12", + rootFile: "/dev/loop12", + }, + { + name: "test12", + devName: "loop0", + rootFile: "/dev/loop0", + }, + { + name: "test13", + devName: "/var/lib/direct-csi/devices/nvmen-part-1-part-4", + rootFile: "/dev/nvmen1p4", + }, + } + + for _, tt := range testCases { + t1.Run(tt.name, func(t1 *testing.T) { + rootFile := getRootBlockPath(tt.devName) + if rootFile != tt.rootFile { + t1.Errorf("Test case name %s: Expected root file = (%s) got: %s", tt.name, tt.rootFile, rootFile) + } + }) + } + +}