Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Make Code Testable #697

Merged
merged 12 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/operator-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
verbose: true

code_quality_golang_ci_lint:
needs: [gofmt, govet, gotest]
needs: [gofmt, govet]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Expand All @@ -79,7 +79,7 @@ jobs:
args: --timeout=5m0s ./...

container_quality_dockerfile_lint:
needs: [gofmt, govet, gotest]
needs: [gofmt, govet]
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
COPY k8sutils/ k8sutils/
COPY mocks/ mocks/

# Build
ARG LDFLAGS="-s -w"
Expand Down
10 changes: 7 additions & 3 deletions controllers/redis_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,19 @@
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// RedisReconciler reconciles a Redis object
type RedisReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
K8sClient kubernetes.Interface
Dk8sClinet dynamic.Interface
Log logr.Logger
Scheme *runtime.Scheme
}

// Reconcile is part of the main kubernetes reconciliation loop which aims
Expand All @@ -53,7 +57,7 @@
reqLogger.Info("Found annotations redis.opstreelabs.in/skip-reconcile, so skipping reconcile")
return ctrl.Result{RequeueAfter: time.Second * 10}, nil
}
if err = k8sutils.HandleRedisFinalizer(instance, r.Client); err != nil {
if err = k8sutils.HandleRedisFinalizer(r.Client, r.K8sClient, r.Log, instance); err != nil {

Check warning on line 60 in controllers/redis_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/redis_controller.go#L60

Added line #L60 was not covered by tests
return ctrl.Result{}, err
}

Expand Down
34 changes: 14 additions & 20 deletions k8sutils/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,28 @@
"k8s.io/client-go/tools/clientcmd"
)

// generateK8sClient create client for kubernetes
func generateK8sClient() *kubernetes.Clientset {
config, err := generateK8sConfig()
if err != nil {
panic(err.Error())
}
clientset, err := kubernetes.NewForConfig(config)
type K8sConfigProvider = func() (*rest.Config, error)

// GenerateK8sClient create client for kubernetes
func GenerateK8sClient(configProvider K8sConfigProvider) (kubernetes.Interface, error) {
config, err := configProvider()
if err != nil {
panic(err.Error())
return nil, err
}
return clientset
return kubernetes.NewForConfig(config)
}

// generateK8sClient create Dynamic client for kubernetes
func generateK8sDynamicClient() dynamic.Interface {
config, err := generateK8sConfig()
if err != nil {
panic(err.Error())
}
dynamicClientset, err := dynamic.NewForConfig(config)
// GenerateK8sClient create Dynamic client for kubernetes
func GenerateK8sDynamicClient(configProvider K8sConfigProvider) (dynamic.Interface, error) {
config, err := configProvider()
if err != nil {
panic(err.Error())
return nil, err
}
return dynamicClientset
return dynamic.NewForConfig(config)
}

// generateK8sConfig will load the kube config file
func generateK8sConfig() (*rest.Config, error) {
// GenerateK8sConfig will load the kube config file
func GenerateK8sConfig() (*rest.Config, error) {

Check warning on line 32 in k8sutils/client.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/client.go#L32

Added line #L32 was not covered by tests
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// if you want to change the loading rules (which files in which order), you can do so here
configOverrides := &clientcmd.ConfigOverrides{}
Expand Down
78 changes: 78 additions & 0 deletions k8sutils/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package k8sutils

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/client-go/rest"
)

func mockK8sConfigProvider() (*rest.Config, error) {
return &rest.Config{}, nil
}
func mockInvalidK8sConfigProvider() (*rest.Config, error) {
return nil, errors.New("invalid configuration")
}

func TestGenerateK8sClient(t *testing.T) {
tests := []struct {
name string
configProvider func() (*rest.Config, error)
wantErr bool
}{
{
name: "valid config",
configProvider: mockK8sConfigProvider,
wantErr: false,
},
{
name: "invalid config",
configProvider: mockInvalidK8sConfigProvider,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := GenerateK8sClient(tt.configProvider)
if tt.wantErr {
assert.Error(t, err, "GenerateK8sClient() should return an error for invalid config")
} else {
assert.NoError(t, err, "GenerateK8sClient() should not return an error for valid config")
assert.NotNil(t, client, "expected a non-nil Kubernetes client")
}
})
}
}

func TestGenerateK8sDynamicClient(t *testing.T) {
tests := []struct {
name string
configProvider func() (*rest.Config, error)
wantErr bool
}{
{
name: "valid config",
configProvider: mockK8sConfigProvider,
wantErr: false,
},
{
name: "invalid config",
configProvider: mockInvalidK8sConfigProvider,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := GenerateK8sDynamicClient(tt.configProvider)
if tt.wantErr {
assert.Error(t, err, "GenerateK8sDynamicClient() should return an error for invalid config")
} else {
assert.NoError(t, err, "GenerateK8sDynamicClient() should not return an error for valid config")
assert.NotNil(t, client, "expected a non-nil Kubernetes client")
}
})
}
}
37 changes: 24 additions & 13 deletions k8sutils/finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import (
"context"
"fmt"
"strconv"

redisv1beta2 "github.com/OT-CONTAINER-KIT/redis-operator/api/v1beta2"
"github.com/OT-CONTAINER-KIT/redis-operator/mocks"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
Expand All @@ -26,16 +29,15 @@
}

// HandleRedisFinalizer finalize resource if instance is marked to be deleted
func HandleRedisFinalizer(cr *redisv1beta2.Redis, cl client.Client) error {
logger := finalizerLogger(cr.Namespace, RedisFinalizer)
func HandleRedisFinalizer(ctrlclient client.Client, k8sClient kubernetes.Interface, logger logr.Logger, cr *redisv1beta2.Redis) error {

Check warning on line 32 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L32

Added line #L32 was not covered by tests
if cr.GetDeletionTimestamp() != nil {
if controllerutil.ContainsFinalizer(cr, RedisFinalizer) {
if err := finalizeRedisPVC(cr); err != nil {
if err := finalizeRedisPVC(k8sClient, logger, cr); err != nil {

Check warning on line 35 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L35

Added line #L35 was not covered by tests
return err
}
controllerutil.RemoveFinalizer(cr, RedisFinalizer)
if err := cl.Update(context.TODO(), cr); err != nil {
logger.Error(err, "Could not remove finalizer "+RedisFinalizer)
if err := ctrlclient.Update(context.TODO(), cr); err != nil {
logger.Error(err, "Could not remove finalizer", "finalizer", RedisFinalizer)

Check warning on line 40 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L39-L40

Added lines #L39 - L40 were not covered by tests
return err
}
}
Expand Down Expand Up @@ -134,12 +136,11 @@
}

// finalizeRedisPVC delete PVC
func finalizeRedisPVC(cr *redisv1beta2.Redis) error {
logger := finalizerLogger(cr.Namespace, RedisFinalizer)
PVCName := cr.Name + "-" + cr.Name + "-0"
err := generateK8sClient().CoreV1().PersistentVolumeClaims(cr.Namespace).Delete(context.TODO(), PVCName, metav1.DeleteOptions{})
func finalizeRedisPVC(client kubernetes.Interface, logger mocks.LoggerInterface, cr *redisv1beta2.Redis) error {
PVCName := fmt.Sprintf("%s-%s-0", cr.Name, cr.Name)
err := client.CoreV1().PersistentVolumeClaims(cr.Namespace).Delete(context.TODO(), PVCName, metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
logger.Error(err, "Could not delete Persistent Volume Claim "+PVCName)
logger.Error(err, "Could not delete Persistent Volume Claim", "PVCName", PVCName)

Check warning on line 143 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L143

Added line #L143 was not covered by tests
return err
}
return nil
Expand All @@ -148,10 +149,15 @@
// finalizeRedisClusterPVC delete PVCs
func finalizeRedisClusterPVC(cr *redisv1beta2.RedisCluster) error {
logger := finalizerLogger(cr.Namespace, RedisClusterFinalizer)
client, err := GenerateK8sClient(GenerateK8sConfig)
if err != nil {
logger.Error(err, "Could not generate kubernetes client")
return err
}

Check warning on line 156 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L152-L156

Added lines #L152 - L156 were not covered by tests
for _, role := range []string{"leader", "follower"} {
for i := 0; i < int(cr.Spec.GetReplicaCounts(role)); i++ {
PVCName := cr.Name + "-" + cr.Name + "-" + role + "-" + strconv.Itoa(i)
err := generateK8sClient().CoreV1().PersistentVolumeClaims(cr.Namespace).Delete(context.TODO(), PVCName, metav1.DeleteOptions{})
err := client.CoreV1().PersistentVolumeClaims(cr.Namespace).Delete(context.TODO(), PVCName, metav1.DeleteOptions{})

Check warning on line 160 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L160

Added line #L160 was not covered by tests
if err != nil && !errors.IsNotFound(err) {
logger.Error(err, "Could not delete Persistent Volume Claim "+PVCName)
return err
Expand All @@ -160,7 +166,7 @@
if cr.Spec.Storage.NodeConfVolume {
for i := 0; i < int(cr.Spec.GetReplicaCounts(role)); i++ {
PVCName := "node-conf" + cr.Name + "-" + role + "-" + strconv.Itoa(i)
err := generateK8sClient().CoreV1().PersistentVolumeClaims(cr.Namespace).Delete(context.TODO(), PVCName, metav1.DeleteOptions{})
err := client.CoreV1().PersistentVolumeClaims(cr.Namespace).Delete(context.TODO(), PVCName, metav1.DeleteOptions{})

Check warning on line 169 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L169

Added line #L169 was not covered by tests
if err != nil && !errors.IsNotFound(err) {
logger.Error(err, "Could not delete Persistent Volume Claim "+PVCName)
return err
Expand All @@ -175,9 +181,14 @@
// finalizeRedisReplicationPVC delete PVCs
func finalizeRedisReplicationPVC(cr *redisv1beta2.RedisReplication) error {
logger := finalizerLogger(cr.Namespace, RedisReplicationFinalizer)
client, err := GenerateK8sClient(GenerateK8sConfig)
if err != nil {
logger.Error(err, "Could not generate kubernetes client")
return err
}

Check warning on line 188 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L184-L188

Added lines #L184 - L188 were not covered by tests
for i := 0; i < int(cr.Spec.GetReplicationCounts("replication")); i++ {
PVCName := cr.Name + "-" + cr.Name + "-" + strconv.Itoa(i)
err := generateK8sClient().CoreV1().PersistentVolumeClaims(cr.Namespace).Delete(context.TODO(), PVCName, metav1.DeleteOptions{})
err := client.CoreV1().PersistentVolumeClaims(cr.Namespace).Delete(context.TODO(), PVCName, metav1.DeleteOptions{})

Check warning on line 191 in k8sutils/finalizer.go

View check run for this annotation

Codecov / codecov/patch

k8sutils/finalizer.go#L191

Added line #L191 was not covered by tests
if err != nil && !errors.IsNotFound(err) {
logger.Error(err, "Could not delete Persistent Volume Claim "+PVCName)
return err
Expand Down
Loading
Loading