...

Source file src/edge-infra.dev/pkg/sds/devices/k8s/apis/v1/deviceclass_test.go

Documentation: edge-infra.dev/pkg/sds/devices/k8s/apis/v1

     1  //nolint:typecheck
     2  package v1
     3  
     4  import (
     5  	"context"
     6  	_ "embed"
     7  	"encoding/json"
     8  	"io/fs"
     9  	"log/slog"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  	"time"
    14  
    15  	testfs "gotest.tools/v3/fs"
    16  
    17  	. "github.com/onsi/ginkgo/v2" //nolint:revive
    18  	. "github.com/onsi/gomega"    //nolint:revive
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/runtime"
    21  
    22  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    23  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    24  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    25  
    26  	"edge-infra.dev/pkg/lib/kernel/devices"
    27  	"edge-infra.dev/pkg/sds/devices/logger"
    28  	v1 "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
    29  )
    30  
    31  var (
    32  	subSystemUSB = "usb"
    33  	timeout      = 5 * time.Second
    34  	testPath     = etcPath
    35  	etcPath      = "/etc"
    36  	soundName    = "sound"
    37  )
    38  
    39  //go:embed testdata/deviceclass_valid.json
    40  var deviceClassValidBytes []byte
    41  
    42  func TestDeviceRead(t *testing.T) {
    43  	dir := testfs.NewDir(t, etcPath)
    44  	defer dir.Remove()
    45  	testPath = dir.Path()
    46  
    47  	RegisterFailHandler(Fail)
    48  	RunSpecs(t, "Device Class Validation Tests")
    49  }
    50  
    51  var _ = BeforeEach(func() {
    52  	_ = os.Remove(filepath.Join(testPath, "deviceclasses.json"))
    53  })
    54  
    55  var _ = Describe("Validation Tests", func() {
    56  	It("Valid name and includes subsystem", func() {
    57  		validName := "display"
    58  		cr := generateDeviceClassTestCR(validName, 1)
    59  		Expect(cr.Validate()).To(BeNil())
    60  	})
    61  
    62  	It("Valid name but device set is missing subsystem", func() {
    63  		validName := "display"
    64  		classCR := generateDeviceClassTestCR(validName, 1)
    65  		setCR := generateDeviceSetsTestCR(validName, false, map[string]string{}, map[string]devices.Device{})
    66  		Expect(classCR.Validate()).To(BeNil())
    67  		Expect(setCR.Validate()).To(MatchError(ErrInvalidMissingSubsystem))
    68  	})
    69  
    70  	It("Invalid name", func() {
    71  		invalidName := "displaydevices.edge.ncr.com/display"
    72  		cr := generateDeviceClassTestCR(invalidName, 1)
    73  		Expect(cr.Validate()).To(MatchError(ErrInvalidName))
    74  	})
    75  })
    76  
    77  var _ = Describe("Should Block Tests", func() {
    78  	It("Should block", func() {
    79  		cr := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, map[string]devices.Device{})
    80  		shouldBlock, _ := cr.WillBlock()
    81  		Expect(shouldBlock).To(BeTrue())
    82  	})
    83  
    84  	It("Should not block", func() {
    85  		cr := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, map[string]devices.Device{"dev1": &dummyDevice{subsystem: subSystemUSB}})
    86  		shouldBlock, _ := cr.WillBlock()
    87  		Expect(shouldBlock).To(BeFalse())
    88  	})
    89  })
    90  
    91  var _ = Describe("Caching Tests", func() {
    92  	It("Should not cache", func() {
    93  		classCR := generateDeviceClassTestCR(soundName, 1)
    94  		setCR := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, nil)
    95  		c := fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(&classCR, &setCR).Build()
    96  
    97  		ctx := context.Background()
    98  		log := logger.New(logger.WithLevel(slog.LevelDebug))
    99  		ctx = logger.IntoContext(ctx, log)
   100  		ctx, cancelFn := context.WithTimeout(ctx, timeout)
   101  		defer cancelFn()
   102  
   103  		opts := []ListOption{
   104  			WithPersistence(false),
   105  			WithPersistencePath(testPath),
   106  		}
   107  
   108  		deviceClassMap, err := ListFromClient(ctx, c, opts...)
   109  		Expect(err).To(BeNil())
   110  		Expect(deviceClassMap).To(HaveLen(1))
   111  
   112  		filePath := filepath.Join(testPath, "deviceclasses.json")
   113  		_, err = os.Stat(filePath)
   114  		Expect(err).To(MatchError(fs.ErrNotExist))
   115  	})
   116  
   117  	It("Should cache", func() {
   118  		classCR := generateDeviceClassTestCR(soundName, 1)
   119  		setCR := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, nil)
   120  		c := fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(&classCR, &setCR).Build()
   121  
   122  		ctx := context.Background()
   123  		log := logger.New(logger.WithLevel(slog.LevelDebug))
   124  		ctx = logger.IntoContext(ctx, log)
   125  		ctx, cancelFn := context.WithTimeout(ctx, timeout)
   126  		defer cancelFn()
   127  
   128  		opts := []ListOption{
   129  			WithPersistence(true),
   130  			WithPersistencePath(testPath),
   131  		}
   132  
   133  		deviceClassMap, err := ListFromClient(ctx, c, opts...)
   134  		Expect(err).To(BeNil())
   135  		Expect(deviceClassMap).To(HaveLen(1))
   136  
   137  		filePath := filepath.Join(testPath, deviceClassCacheName)
   138  		cachedClasses, err := os.ReadFile(filePath)
   139  		Expect(err).To(BeNil())
   140  
   141  		deviceClasses := DeviceClassList{}
   142  		Expect(json.Unmarshal(cachedClasses, &deviceClasses)).To(BeNil())
   143  		Expect(deviceClasses.Items).To(HaveLen(1))
   144  		Expect(deviceClasses.Items[0].ObjectMeta.Name).To(Equal(classCR.ObjectMeta.Name))
   145  		Expect(deviceClasses.Items[0].ObjectMeta.Generation).To(Equal(classCR.ObjectMeta.Generation))
   146  
   147  		filePath = filepath.Join(testPath, deviceCacheName)
   148  		cachedSets, err := os.ReadFile(filePath)
   149  		Expect(err).To(BeNil())
   150  
   151  		deviceSets := DeviceSetList{}
   152  		Expect(json.Unmarshal(cachedSets, &deviceSets)).To(BeNil())
   153  		Expect(deviceSets.Items).To(HaveLen(1))
   154  		Expect(deviceSets.Items[0].ObjectMeta.Name).To(Equal(setCR.ObjectMeta.Name))
   155  		Expect(deviceSets.Items[0].ObjectMeta.Generation).To(Equal(setCR.ObjectMeta.Generation))
   156  	})
   157  
   158  	It("Generation has changed", func() {
   159  		setCR := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, nil)
   160  		oldCR := generateDeviceClassTestCR("sound", 1)
   161  		newCR := generateDeviceClassTestCR("sound", 2)
   162  
   163  		ctx := context.Background()
   164  		log := logger.New(logger.WithLevel(slog.LevelDebug))
   165  		ctx = logger.IntoContext(ctx, log)
   166  		ctx, cancelFn := context.WithTimeout(ctx, timeout)
   167  		defer cancelFn()
   168  
   169  		Expect(writeDeviceClassCache(ctx, testPath, []DeviceClass{oldCR})).To(BeNil())
   170  
   171  		c := fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(&newCR, &setCR).Build()
   172  
   173  		opts := []ListOption{
   174  			WithPersistence(true),
   175  			WithPersistencePath(testPath),
   176  		}
   177  
   178  		deviceClassMap, err := ListFromClient(ctx, c, opts...)
   179  		Expect(err).To(BeNil())
   180  		Expect(deviceClassMap).To(HaveLen(1))
   181  
   182  		Expect(deviceClassMap[newCR.ClassName()]).ToNot(BeNil())
   183  		Expect(deviceClassMap[newCR.ClassName()].ObjectMeta.Generation).To(Equal(newCR.ObjectMeta.Generation))
   184  
   185  		filePath := filepath.Join(testPath, deviceClassCacheName)
   186  		cachedClasses, err := os.ReadFile(filePath)
   187  		Expect(err).To(BeNil())
   188  
   189  		deviceClasses := DeviceClassList{}
   190  		Expect(json.Unmarshal(cachedClasses, &deviceClasses)).To(BeNil())
   191  		Expect(deviceClasses.Items).To(HaveLen(1))
   192  		Expect(deviceClasses.Items[0].ObjectMeta.Name).To(Equal(newCR.ObjectMeta.Name))
   193  	})
   194  })
   195  
   196  var _ = Describe("Device System CR Type Conversion", func() {
   197  	It("Should convert successfully to dsv1.DeviceClass", func() {
   198  		deviceClass := &DeviceClass{}
   199  		Expect(json.Unmarshal(deviceClassValidBytes, deviceClass)).To(BeNil())
   200  	})
   201  })
   202  
   203  func generateDeviceClassTestCR(name string, generation int64) DeviceClass {
   204  	return DeviceClass{
   205  		ObjectMeta: metav1.ObjectMeta{
   206  			Name:       name,
   207  			Generation: generation,
   208  		},
   209  		Spec: DeviceClassSpec{
   210  			Devices: []DeviceRef{
   211  				{
   212  					Name: name,
   213  				},
   214  			},
   215  			Logging: Logging{
   216  				Level: "info",
   217  			},
   218  		},
   219  	}
   220  }
   221  
   222  func generateDeviceSetsTestCR(name string, shouldBlock bool, deviceSetProperties map[string]string, devices map[string]devices.Device) DeviceSet {
   223  	deviceSet := DeviceSetReference{
   224  		Name:       name,
   225  		Properties: []Rule{},
   226  	}
   227  
   228  	if shouldBlock {
   229  		deviceSet.Blocking = &Blocking{}
   230  	}
   231  
   232  	for key, value := range deviceSetProperties {
   233  		deviceSet.Properties = append(deviceSet.Properties, Rule{
   234  			Name:       key,
   235  			RegexValue: value,
   236  		})
   237  	}
   238  
   239  	return DeviceSet{
   240  		ObjectMeta: metav1.ObjectMeta{
   241  			Name:       name,
   242  			Generation: 1,
   243  		},
   244  		Spec: DeviceSpec{
   245  			DeviceSets: []DeviceSetReference{
   246  				deviceSet,
   247  			},
   248  		},
   249  		DeviceStatus: deviceStatus{
   250  			devices: devices,
   251  		},
   252  	}
   253  }
   254  
   255  func createScheme() *runtime.Scheme {
   256  	scheme := runtime.NewScheme()
   257  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
   258  	utilruntime.Must(v1.AddToScheme(scheme))
   259  	utilruntime.Must(AddToScheme(scheme))
   260  	return scheme
   261  }
   262  
   263  // mock implmentations of device and device node
   264  type dummyDevice struct{ subsystem string }
   265  type dummyNode struct{}
   266  
   267  var devType = "char"
   268  
   269  // dummy device implementation
   270  func (dd *dummyDevice) Path() string                             { return "" }
   271  func (dd *dummyDevice) Node() (devices.Node, error)              { return &dummyNode{}, nil }
   272  func (dd *dummyDevice) Attribute(_ string) (string, bool, error) { return "", true, nil }
   273  func (dd *dummyDevice) Property(_ string) (string, bool, error) {
   274  	return dd.subsystem, true, nil
   275  }
   276  
   277  // dummy node implementation
   278  func (dn *dummyNode) Path() string                   { return "" }
   279  func (dn *dummyNode) Type() (string, error)          { return devType, nil }
   280  func (dn *dummyNode) GroupID() (int64, error)        { return 1, nil }
   281  func (dn *dummyNode) UserID() (int64, error)         { return 1, nil }
   282  func (dn *dummyNode) FileMode() (os.FileMode, error) { return 0, nil }
   283  

View as plain text