From d5d70e3e4d076e0c62925ced8c716256d4c928b6 Mon Sep 17 00:00:00 2001 From: Peter Bacsko Date: Tue, 14 May 2024 12:20:16 +0200 Subject: [PATCH] [YUNIKORN-2623] Create unit tests for Clients (#839) Closes: #839 Signed-off-by: Peter Bacsko --- pkg/client/apifactory_mock.go | 61 ++++++++++-- pkg/client/clients_test.go | 86 +++++++++++++++++ pkg/common/test/configmap_informer_mock.go | 12 ++- pkg/common/test/namespaceinformer_mock.go | 8 +- pkg/common/test/nodeinformer_mock.go | 4 +- pkg/common/test/podinformer_mock.go | 4 +- .../test/priorityclass_informer_mock.go | 8 +- pkg/common/test/shared_informer_mock.go | 94 +++++++++++++++++++ 8 files changed, 255 insertions(+), 22 deletions(-) create mode 100644 pkg/client/clients_test.go create mode 100644 pkg/common/test/shared_informer_mock.go diff --git a/pkg/client/apifactory_mock.go b/pkg/client/apifactory_mock.go index 35decd4c9..733923901 100644 --- a/pkg/client/apifactory_mock.go +++ b/pkg/client/apifactory_mock.go @@ -85,12 +85,13 @@ func NewMockedAPIProvider(showError bool) *MockedAPIProvider { PodInformer: test.NewMockedPodInformer(), NodeInformer: test.NewMockedNodeInformer(), ConfigMapInformer: test.NewMockedConfigMapInformer(), - PVInformer: &MockedPersistentVolumeInformer{}, - PVCInformer: &MockedPersistentVolumeClaimInformer{}, - StorageInformer: &MockedStorageClassInformer{}, + PVInformer: NewMockedPersistentVolumeInformer(), + PVCInformer: NewMockedPersistentVolumeClaimInformer(), + StorageInformer: NewMockedStorageClassInformer(), VolumeBinder: test.NewVolumeBinderMock(), NamespaceInformer: test.NewMockNamespaceInformer(false), PriorityClassInformer: test.NewMockPriorityClassInformer(), + CSINodeInformer: NewMockedCSINodeInformer(), InformerFactory: informers.NewSharedInformerFactory(k8fake.NewSimpleClientset(), time.Second*60), }, events: make(chan informerEvent), @@ -416,10 +417,18 @@ func (m *MockedAPIProvider) GetBoundPods(clear bool) []BoundPod { } // MockedPersistentVolumeInformer implements PersistentVolumeInformer interface -type MockedPersistentVolumeInformer struct{} +type MockedPersistentVolumeInformer struct { + informer cache.SharedIndexInformer +} + +func NewMockedPersistentVolumeInformer() *MockedPersistentVolumeInformer { + return &MockedPersistentVolumeInformer{ + informer: &test.SharedInformerMock{}, + } +} func (m *MockedPersistentVolumeInformer) Informer() cache.SharedIndexInformer { - return nil + return m.informer } func (m *MockedPersistentVolumeInformer) Lister() corev1.PersistentVolumeLister { @@ -427,10 +436,18 @@ func (m *MockedPersistentVolumeInformer) Lister() corev1.PersistentVolumeLister } // MockedPersistentVolumeClaimInformer implements PersistentVolumeClaimInformer interface -type MockedPersistentVolumeClaimInformer struct{} +type MockedPersistentVolumeClaimInformer struct { + informer cache.SharedIndexInformer +} + +func NewMockedPersistentVolumeClaimInformer() *MockedPersistentVolumeClaimInformer { + return &MockedPersistentVolumeClaimInformer{ + informer: &test.SharedInformerMock{}, + } +} func (m *MockedPersistentVolumeClaimInformer) Informer() cache.SharedIndexInformer { - return nil + return m.informer } func (m *MockedPersistentVolumeClaimInformer) Lister() corev1.PersistentVolumeClaimLister { @@ -438,16 +455,42 @@ func (m *MockedPersistentVolumeClaimInformer) Lister() corev1.PersistentVolumeCl } // MockedStorageClassInformer implements StorageClassInformer interface -type MockedStorageClassInformer struct{} +type MockedStorageClassInformer struct { + informer cache.SharedIndexInformer +} + +func NewMockedStorageClassInformer() *MockedStorageClassInformer { + return &MockedStorageClassInformer{ + informer: &test.SharedInformerMock{}, + } +} func (m *MockedStorageClassInformer) Informer() cache.SharedIndexInformer { - return nil + return m.informer } func (m *MockedStorageClassInformer) Lister() storagev1.StorageClassLister { return nil } +type MockedCSINodeInformer struct { + informer cache.SharedIndexInformer +} + +func NewMockedCSINodeInformer() *MockedCSINodeInformer { + return &MockedCSINodeInformer{ + informer: &test.SharedInformerMock{}, + } +} + +func (m *MockedCSINodeInformer) Informer() cache.SharedIndexInformer { + return m.informer +} + +func (m *MockedCSINodeInformer) Lister() storagev1.CSINodeLister { + return nil +} + func (m *MockedAPIProvider) SetVolumeBinder(binder volumebinding.SchedulerVolumeBinder) { m.clients.VolumeBinder = binder } diff --git a/pkg/client/clients_test.go b/pkg/client/clients_test.go new file mode 100644 index 000000000..4d7715dc3 --- /dev/null +++ b/pkg/client/clients_test.go @@ -0,0 +1,86 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package client + +import ( + "testing" + "time" + + "gotest.tools/v3/assert" + + "github.com/apache/yunikorn-core/pkg/common" + "github.com/apache/yunikorn-k8shim/pkg/common/test" +) + +const ( + noOfInformers = 9 // total number of active informers +) + +func TestWaitForSync(t *testing.T) { + clients := getClients() + test.SyncDone.Store(false) + go func() { + time.Sleep(500 * time.Millisecond) + test.SyncDone.Store(true) + }() + + start := time.Now() + clients.WaitForSync() + diff := time.Since(start) + assert.Equal(t, int64(1000), diff.Truncate(time.Second).Milliseconds(), "WaitForSync() didn't block for 1 second") +} + +func TestRun(t *testing.T) { + stopped := false + clients := getClients() + test.RunningInformers.Store(0) + stop := make(chan struct{}) + defer func() { + if !stopped { + close(stop) + } + }() + + clients.Run(stop) + err := common.WaitForCondition(func() bool { + return test.RunningInformers.Load() == noOfInformers + }, 10*time.Millisecond, time.Second) + assert.NilError(t, err, "number of running informers: expected %d got %d", noOfInformers, test.RunningInformers.Load()) + + close(stop) + stopped = true + err = common.WaitForCondition(func() bool { + return test.RunningInformers.Load() == 0 + }, 10*time.Millisecond, time.Second) + assert.NilError(t, err, "no. of informers still running: %d", test.RunningInformers.Load()) +} + +func getClients() *Clients { + return &Clients{ + PodInformer: test.NewMockedPodInformer(), + NodeInformer: test.NewMockedNodeInformer(), + ConfigMapInformer: test.NewMockedConfigMapInformer(), + PVInformer: NewMockedPersistentVolumeInformer(), + PVCInformer: NewMockedPersistentVolumeClaimInformer(), + StorageInformer: NewMockedStorageClassInformer(), + CSINodeInformer: NewMockedCSINodeInformer(), + NamespaceInformer: test.NewMockNamespaceInformer(false), + PriorityClassInformer: test.NewMockPriorityClassInformer(), + } +} diff --git a/pkg/common/test/configmap_informer_mock.go b/pkg/common/test/configmap_informer_mock.go index c1b0e616e..66823d358 100644 --- a/pkg/common/test/configmap_informer_mock.go +++ b/pkg/common/test/configmap_informer_mock.go @@ -24,19 +24,21 @@ import ( ) type ConfigMapInformerMock struct { - lister v1.ConfigMapLister + lister v1.ConfigMapLister + informer cache.SharedIndexInformer } func NewMockedConfigMapInformer() *ConfigMapInformerMock { return &ConfigMapInformerMock{ - lister: NewConfigMapListerMock(), + lister: NewConfigMapListerMock(), + informer: &SharedInformerMock{}, } } -func (c ConfigMapInformerMock) Informer() cache.SharedIndexInformer { - return nil +func (c *ConfigMapInformerMock) Informer() cache.SharedIndexInformer { + return c.informer } -func (c ConfigMapInformerMock) Lister() v1.ConfigMapLister { +func (c *ConfigMapInformerMock) Lister() v1.ConfigMapLister { return c.lister } diff --git a/pkg/common/test/namespaceinformer_mock.go b/pkg/common/test/namespaceinformer_mock.go index 432ac976e..26bc1ca3a 100644 --- a/pkg/common/test/namespaceinformer_mock.go +++ b/pkg/common/test/namespaceinformer_mock.go @@ -25,17 +25,19 @@ import ( ) type MockNamespaceInformer struct { - lister listersV1.NamespaceLister + lister listersV1.NamespaceLister + informer cache.SharedIndexInformer } func NewMockNamespaceInformer(errIfNotFound bool) informersV1.NamespaceInformer { return &MockNamespaceInformer{ - lister: NewMockNamespaceLister(errIfNotFound), + lister: NewMockNamespaceLister(errIfNotFound), + informer: &SharedInformerMock{}, } } func (nsi *MockNamespaceInformer) Informer() cache.SharedIndexInformer { - return nil + return nsi.informer } func (nsi *MockNamespaceInformer) Lister() listersV1.NamespaceLister { diff --git a/pkg/common/test/nodeinformer_mock.go b/pkg/common/test/nodeinformer_mock.go index 9f9a7b838..d3d42b465 100644 --- a/pkg/common/test/nodeinformer_mock.go +++ b/pkg/common/test/nodeinformer_mock.go @@ -25,16 +25,18 @@ import ( type MockedNodeInformer struct { nodeLister v1.NodeLister + informer cache.SharedIndexInformer } func NewMockedNodeInformer() *MockedNodeInformer { return &MockedNodeInformer{ nodeLister: NewNodeListerMock(), + informer: &SharedInformerMock{}, } } func (m *MockedNodeInformer) Informer() cache.SharedIndexInformer { - return nil + return m.informer } func (m *MockedNodeInformer) Lister() v1.NodeLister { diff --git a/pkg/common/test/podinformer_mock.go b/pkg/common/test/podinformer_mock.go index 0862697f5..00d261f48 100644 --- a/pkg/common/test/podinformer_mock.go +++ b/pkg/common/test/podinformer_mock.go @@ -25,16 +25,18 @@ import ( type MockedPodInformer struct { podLister v1.PodLister + informer cache.SharedIndexInformer } func NewMockedPodInformer() *MockedPodInformer { return &MockedPodInformer{ podLister: NewPodListerMock(), + informer: &SharedInformerMock{}, } } func (m *MockedPodInformer) Informer() cache.SharedIndexInformer { - return nil + return m.informer } func (m *MockedPodInformer) Lister() v1.PodLister { diff --git a/pkg/common/test/priorityclass_informer_mock.go b/pkg/common/test/priorityclass_informer_mock.go index 3abec3037..7bb16e266 100644 --- a/pkg/common/test/priorityclass_informer_mock.go +++ b/pkg/common/test/priorityclass_informer_mock.go @@ -25,17 +25,19 @@ import ( ) type MockPriorityClassInformer struct { - lister listersV1.PriorityClassLister + lister listersV1.PriorityClassLister + informer cache.SharedIndexInformer } func NewMockPriorityClassInformer() informersV1.PriorityClassInformer { return &MockPriorityClassInformer{ - lister: NewMockPriorityClassLister(), + lister: NewMockPriorityClassLister(), + informer: &SharedInformerMock{}, } } func (nsi *MockPriorityClassInformer) Informer() cache.SharedIndexInformer { - return nil + return nsi.informer } func (nsi *MockPriorityClassInformer) Lister() listersV1.PriorityClassLister { diff --git a/pkg/common/test/shared_informer_mock.go b/pkg/common/test/shared_informer_mock.go new file mode 100644 index 000000000..ce887497e --- /dev/null +++ b/pkg/common/test/shared_informer_mock.go @@ -0,0 +1,94 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package test + +import ( + "sync/atomic" + "time" + + "k8s.io/client-go/tools/cache" +) + +func init() { + SyncDone.Store(true) // shouldn't block by default +} + +var ( + SyncDone atomic.Bool + RunningInformers atomic.Int64 + + _ cache.SharedIndexInformer = &SharedInformerMock{} +) + +type SharedInformerMock struct { +} + +func (s *SharedInformerMock) AddEventHandler(_ cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) { + return nil, nil +} + +func (s *SharedInformerMock) AddEventHandlerWithResyncPeriod(_ cache.ResourceEventHandler, _ time.Duration) (cache.ResourceEventHandlerRegistration, error) { + return nil, nil +} + +func (s *SharedInformerMock) RemoveEventHandler(handle cache.ResourceEventHandlerRegistration) error { + return nil +} + +func (s *SharedInformerMock) GetStore() cache.Store { + return nil +} + +func (s *SharedInformerMock) GetController() cache.Controller { + return nil +} + +func (s *SharedInformerMock) Run(stopCh <-chan struct{}) { + RunningInformers.Add(1) + <-stopCh + RunningInformers.Add(-1) +} + +func (s *SharedInformerMock) HasSynced() bool { + return SyncDone.Load() +} + +func (s *SharedInformerMock) LastSyncResourceVersion() string { + return "" +} + +func (s *SharedInformerMock) SetWatchErrorHandler(_ cache.WatchErrorHandler) error { + return nil +} + +func (s *SharedInformerMock) SetTransform(_ cache.TransformFunc) error { + return nil +} + +func (s *SharedInformerMock) IsStopped() bool { + return false +} + +func (s *SharedInformerMock) AddIndexers(_ cache.Indexers) error { + return nil +} + +func (s *SharedInformerMock) GetIndexer() cache.Indexer { + return nil +}