github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/cmd/ndm_daemonset/probe/eventhandler_test.go (about)

     1  /*
     2  Copyright 2018 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 probe
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"testing"
    23  
    24  	apis "github.com/openebs/node-disk-manager/api/v1alpha1"
    25  	"github.com/openebs/node-disk-manager/blockdevice"
    26  	"github.com/openebs/node-disk-manager/cmd/ndm_daemonset/controller"
    27  	libudevwrapper "github.com/openebs/node-disk-manager/pkg/udev"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/client-go/kubernetes/scheme"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  	ndmFakeClientset "sigs.k8s.io/controller-runtime/pkg/client/fake"
    34  )
    35  
    36  var (
    37  	ignoreDiskDevPath = "/dev/sdZ"
    38  	fakeHostName      = "node-name"
    39  	fakeModel         = "fake-disk-model"
    40  	fakeSerial        = "fake-disk-serial"
    41  	fakeVendor        = "fake-disk-vendor"
    42  	fakeWWN           = "fake-WWN"
    43  	fakeBDType        = "blockdevice"
    44  )
    45  
    46  var (
    47  	fakeBD1 = blockdevice.BlockDevice{
    48  		Identifier: blockdevice.Identifier{
    49  			DevPath: "/dev/sdX",
    50  		},
    51  		DeviceAttributes: blockdevice.DeviceAttribute{
    52  			WWN:    fakeWWN,
    53  			Serial: fakeSerial,
    54  		},
    55  	}
    56  	fakeBD2 = blockdevice.BlockDevice{
    57  		Identifier: blockdevice.Identifier{
    58  			DevPath: ignoreDiskDevPath,
    59  		},
    60  		DeviceAttributes: blockdevice.DeviceAttribute{
    61  			WWN: fakeWWN,
    62  		},
    63  	}
    64  )
    65  
    66  var (
    67  	fakeBD1Uuid, _ = generateUUID(fakeBD1)
    68  	fakeBD2Uuid, _ = generateUUID(fakeBD2)
    69  )
    70  
    71  func mockEmptyBlockDeviceCr() apis.BlockDevice {
    72  	fakeBDr := apis.BlockDevice{}
    73  	fakeObjectMeta := metav1.ObjectMeta{
    74  		Labels: make(map[string]string),
    75  		Name:   fakeBD1Uuid,
    76  	}
    77  	fakeTypeMeta := metav1.TypeMeta{
    78  		Kind:       controller.NDMBlockDeviceKind,
    79  		APIVersion: controller.NDMVersion,
    80  	}
    81  	fakeBDr.ObjectMeta = fakeObjectMeta
    82  	fakeBDr.TypeMeta = fakeTypeMeta
    83  	fakeBDr.Status.State = controller.NDMActive
    84  	fakeBDr.Status.ClaimState = apis.BlockDeviceUnclaimed
    85  	fakeBDr.Spec.DevLinks = make([]apis.DeviceDevLink, 0)
    86  	return fakeBDr
    87  }
    88  
    89  func CreateFakeClient(t *testing.T) client.Client {
    90  
    91  	deviceR := &apis.BlockDevice{
    92  		ObjectMeta: metav1.ObjectMeta{
    93  			Labels: make(map[string]string),
    94  			Name:   "dummy-blockdevice",
    95  		},
    96  	}
    97  
    98  	deviceList := &apis.BlockDeviceList{
    99  		TypeMeta: metav1.TypeMeta{
   100  			Kind:       "BlockDevice",
   101  			APIVersion: "",
   102  		},
   103  	}
   104  
   105  	s := scheme.Scheme
   106  	s.AddKnownTypes(apis.GroupVersion, deviceR)
   107  	s.AddKnownTypes(apis.GroupVersion, deviceList)
   108  
   109  	fakeNdmClient := ndmFakeClientset.NewFakeClient()
   110  	if fakeNdmClient == nil {
   111  		fmt.Println("NDMClient is not created")
   112  	}
   113  	return fakeNdmClient
   114  }
   115  
   116  type fakeFilter struct{}
   117  
   118  func (nf *fakeFilter) Start() {}
   119  
   120  func (nf *fakeFilter) Include(fakeDiskInfo *blockdevice.BlockDevice) bool {
   121  	return true
   122  }
   123  
   124  func (nf *fakeFilter) Exclude(fakeDiskInfo *blockdevice.BlockDevice) bool {
   125  	return fakeDiskInfo.DevPath != ignoreDiskDevPath
   126  }
   127  
   128  func TestAddBlockDeviceEvent(t *testing.T) {
   129  	fakeNdmClient := CreateFakeClient(t)
   130  	nodeAttributes := make(map[string]string)
   131  	nodeAttributes[controller.HostNameKey] = fakeHostName
   132  	fakeController := &controller.Controller{
   133  		Clientset:      fakeNdmClient,
   134  		Mutex:          &sync.Mutex{},
   135  		Filters:        make([]*controller.Filter, 0),
   136  		Probes:         make([]*controller.Probe, 0),
   137  		NodeAttributes: nodeAttributes,
   138  		BDHierarchy:    make(blockdevice.Hierarchy),
   139  	}
   140  	//add one filter
   141  	filter := &fakeFilter{}
   142  	filter1 := &controller.Filter{
   143  		Name:      "filter1",
   144  		State:     true,
   145  		Interface: filter,
   146  	}
   147  	fakeController.AddNewFilter(filter1)
   148  	// add one probe
   149  	testProbe := &fakeProbe{}
   150  	probe1 := &controller.Probe{
   151  		Name:      "probe1",
   152  		State:     true,
   153  		Interface: testProbe,
   154  	}
   155  	fakeController.AddNewProbe(probe1)
   156  
   157  	probeEvent := &ProbeEvent{
   158  		Controller: fakeController,
   159  	}
   160  	// blockdevice-1 details
   161  	eventmsg := make([]*blockdevice.BlockDevice, 0)
   162  	eventmsg = append(eventmsg, &fakeBD1)
   163  	// blockdevice-2 details
   164  	eventmsg = append(eventmsg, &fakeBD2)
   165  	// Creating one event message
   166  	eventDetails := controller.EventMessage{
   167  		Action:  libudevwrapper.UDEV_ACTION_ADD,
   168  		Devices: eventmsg,
   169  	}
   170  	probeEvent.addBlockDeviceEvent(eventDetails)
   171  	// Retrieve disk resource
   172  	cdr1, err1 := fakeController.GetBlockDevice(fakeBD1Uuid)
   173  
   174  	// Retrieve disk resource
   175  	cdr2, _ := fakeController.GetBlockDevice(fakeBD2Uuid)
   176  	if cdr2 != nil {
   177  		t.Error("resource with ignoreDiskUuid should not be present in etcd")
   178  	}
   179  	// Create one fake disk resource
   180  	fakeDr := mockEmptyBlockDeviceCr()
   181  	fakeDr.ObjectMeta.Labels[controller.KubernetesHostNameLabel] = fakeController.NodeAttributes[controller.HostNameKey]
   182  	fakeDr.ObjectMeta.Labels[controller.NDMDeviceTypeKey] = fakeBDType
   183  	fakeDr.ObjectMeta.Labels[controller.NDMManagedKey] = controller.TrueString
   184  	fakeDr.Spec.Details.Model = fakeModel
   185  	fakeDr.Spec.Details.Serial = fakeSerial
   186  	fakeDr.Spec.Details.Vendor = fakeVendor
   187  	fakeDr.Spec.Partitioned = controller.NDMNotPartitioned
   188  	fakeDr.Spec.Path = "/dev/sdX"
   189  
   190  	tests := map[string]struct {
   191  		actualDisk    apis.BlockDevice
   192  		expectedDisk  apis.BlockDevice
   193  		actualError   error
   194  		expectedError error
   195  	}{
   196  		"resource with 'fake-disk-uid' uuid for create resource": {actualDisk: *cdr1, expectedDisk: fakeDr, actualError: err1, expectedError: nil},
   197  	}
   198  	for name, test := range tests {
   199  		t.Run(name, func(t *testing.T) {
   200  			compareBlockDevice(t, test.expectedDisk, test.actualDisk)
   201  			assert.Equal(t, test.expectedError, test.actualError)
   202  		})
   203  	}
   204  }
   205  
   206  func TestDeleteDiskEvent(t *testing.T) {
   207  	fakeNdmClient := CreateFakeClient(t)
   208  	probes := make([]*controller.Probe, 0)
   209  	nodeAttributes := make(map[string]string)
   210  	nodeAttributes[controller.HostNameKey] = fakeHostName
   211  	mutex := &sync.Mutex{}
   212  	fakeController := &controller.Controller{
   213  		Clientset:      fakeNdmClient,
   214  		Probes:         probes,
   215  		Mutex:          mutex,
   216  		NodeAttributes: nodeAttributes,
   217  		BDHierarchy: blockdevice.Hierarchy{
   218  			"/dev/sdX": fakeBD1,
   219  		},
   220  	}
   221  
   222  	// Create one fake block device resource
   223  	fakeBDr := mockEmptyBlockDeviceCr()
   224  	fakeBDr.ObjectMeta.Labels[controller.KubernetesHostNameLabel] = fakeController.NodeAttributes[controller.HostNameKey]
   225  	fakeBDr.ObjectMeta.Labels[controller.NDMDeviceTypeKey] = fakeBDType
   226  	fakeBDr.ObjectMeta.Labels[controller.NDMManagedKey] = controller.TrueString
   227  	fakeController.CreateBlockDevice(fakeBDr)
   228  
   229  	probeEvent := &ProbeEvent{
   230  		Controller: fakeController,
   231  	}
   232  	eventmsg := make([]*blockdevice.BlockDevice, 0)
   233  	eventmsg = append(eventmsg, &fakeBD1)
   234  	eventDetails := controller.EventMessage{
   235  		Action:  libudevwrapper.UDEV_ACTION_REMOVE,
   236  		Devices: eventmsg,
   237  	}
   238  	probeEvent.deleteBlockDeviceEvent(eventDetails)
   239  
   240  	// Retrieve resources
   241  	bdR1, err1 := fakeController.GetBlockDevice(fakeBD1Uuid)
   242  
   243  	fakeBDr.Status.State = controller.NDMInactive
   244  	tests := map[string]struct {
   245  		actualBD      apis.BlockDevice
   246  		expectedBD    apis.BlockDevice
   247  		actualError   error
   248  		expectedError error
   249  	}{
   250  		"remove resource with 'fake-disk-uid' uuid": {actualBD: *bdR1, expectedBD: fakeBDr, actualError: err1, expectedError: nil},
   251  	}
   252  	for name, test := range tests {
   253  		t.Run(name, func(t *testing.T) {
   254  			compareBlockDevice(t, test.expectedBD, test.actualBD)
   255  			assert.Equal(t, test.expectedError, test.actualError)
   256  		})
   257  	}
   258  }
   259  
   260  // compareBlockDevice is the custom blockdevice comparison function. Only those values that need to be checked
   261  // for equality will be checked here. Resource version field will not be checked as it
   262  // will be updated on every write. Refer https://github.com/kubernetes-sigs/controller-runtime/pull/620
   263  func compareBlockDevice(t *testing.T, bd1, bd2 apis.BlockDevice) {
   264  	assert.Equal(t, bd1.Name, bd2.Name)
   265  	assert.Equal(t, bd1.Labels, bd2.Labels)
   266  	// devlinks will be compared separately
   267  	assert.Equal(t, len(bd1.Spec.DevLinks), len(bd2.Spec.DevLinks))
   268  	if len(bd1.Spec.DevLinks) != len(bd2.Spec.DevLinks) {
   269  		assert.Fail(t, "Devlinks, expected: %+v \n actual: %+v", bd1.Spec.DevLinks, bd2.Spec.DevLinks)
   270  		return
   271  	}
   272  	// compare each set of devlinks
   273  	for i := 0; i < len(bd1.Spec.DevLinks); i++ {
   274  		assert.True(t, unorderedEqual(bd1.Spec.DevLinks[i].Links, bd2.Spec.DevLinks[i].Links))
   275  	}
   276  	// links will be made nil since they are already compared
   277  	bd1.Spec.DevLinks = nil
   278  	bd2.Spec.DevLinks = nil
   279  
   280  	assert.Equal(t, bd1.Spec, bd2.Spec)
   281  	assert.Equal(t, bd1.Status, bd2.Status)
   282  
   283  }
   284  
   285  // compareBlockDeviceList is the custom comparison function for blockdevice list
   286  func compareBlockDeviceList(t *testing.T, bdList1, bdList2 apis.BlockDeviceList) {
   287  	assert.Equal(t, len(bdList1.Items), len(bdList2.Items))
   288  	for i := 0; i < len(bdList2.Items); i++ {
   289  		compareBlockDevice(t, bdList1.Items[i], bdList2.Items[i])
   290  	}
   291  }
   292  
   293  func TestIsParentOrSlaveDevice(t *testing.T) {
   294  	tests := map[string]struct {
   295  		bd             blockdevice.BlockDevice
   296  		erroredDevices []string
   297  		want           bool
   298  	}{
   299  		"no devices in errored state": {
   300  			bd: blockdevice.BlockDevice{
   301  				Identifier: blockdevice.Identifier{
   302  					DevPath: "/dev/sda1",
   303  				},
   304  				DependentDevices: blockdevice.DependentBlockDevices{
   305  					Parent:     "/dev/sda",
   306  					Partitions: nil,
   307  					Holders:    nil,
   308  					Slaves:     nil,
   309  				},
   310  			},
   311  			erroredDevices: nil,
   312  			want:           false,
   313  		},
   314  		"multiple devices in errored state with no matching BD": {
   315  			bd: blockdevice.BlockDevice{
   316  				Identifier: blockdevice.Identifier{
   317  					DevPath: "/dev/sda1",
   318  				},
   319  				DependentDevices: blockdevice.DependentBlockDevices{
   320  					Parent:     "/dev/sda",
   321  					Partitions: nil,
   322  					Holders:    nil,
   323  					Slaves:     nil,
   324  				},
   325  			},
   326  			erroredDevices: []string{"/dev/sdb", "/dev/sdc"},
   327  			want:           false,
   328  		},
   329  		"one device in errored state that is the parent of the given BD": {
   330  			bd: blockdevice.BlockDevice{
   331  				Identifier: blockdevice.Identifier{
   332  					DevPath: "/dev/sda1",
   333  				},
   334  				DependentDevices: blockdevice.DependentBlockDevices{
   335  					Parent:     "/dev/sda",
   336  					Partitions: nil,
   337  					Holders:    nil,
   338  					Slaves:     nil,
   339  				},
   340  			},
   341  			erroredDevices: []string{"/dev/sda"},
   342  			want:           true,
   343  		},
   344  		"one device in errored state that that the BD is a slave to": {
   345  			bd: blockdevice.BlockDevice{
   346  				Identifier: blockdevice.Identifier{
   347  					DevPath: "/dev/dm-0",
   348  				},
   349  				DependentDevices: blockdevice.DependentBlockDevices{
   350  					Parent:     "",
   351  					Partitions: nil,
   352  					Holders:    nil,
   353  					Slaves:     []string{"/dev/sda", "/dev/sdb"},
   354  				},
   355  			},
   356  			erroredDevices: []string{"/dev/sda", "/dev/sdc"},
   357  			want:           true,
   358  		},
   359  	}
   360  	for name, tt := range tests {
   361  		t.Run(name, func(t *testing.T) {
   362  			got := isParentOrSlaveDevice(tt.bd, tt.erroredDevices)
   363  			assert.Equal(t, tt.want, got)
   364  		})
   365  	}
   366  }