Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
277 changes: 277 additions & 0 deletions pkg/controllers/v1alpha1/goosefs/goosefs_runtime_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/*
Copyright 2026 The Fluid Authors.

Licensed 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 goosefs

import (
"context"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
cruntime "github.com/fluid-cloudnative/fluid/pkg/runtime"
"github.com/fluid-cloudnative/fluid/pkg/utils/fake"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

// buildTestReconcileCtx builds a minimal ReconcileRequestContext for unit tests.
func buildTestReconcileCtx(req ctrl.Request) cruntime.ReconcileRequestContext {
return cruntime.ReconcileRequestContext{
Context: context.Background(),
NamespacedName: req.NamespacedName,
Log: zap.New(zap.UseDevMode(true)),
}
}

const (
testRuntimeName = "test-goosefs-runtime"
testRuntimeNamespace = "default"
envtestRuntimeName = "envtest-runtime"
)

var _ = Describe("RuntimeReconciler", func() {
var (
testScheme *runtime.Scheme
)

BeforeEach(func() {
testScheme = runtime.NewScheme()
Expect(datav1alpha1.AddToScheme(testScheme)).To(Succeed())
})

Describe("NewRuntimeReconciler", func() {
It("should create a RuntimeReconciler with non-nil fields", func() {
fakeClient := fake.NewFakeClientWithScheme(testScheme)
fakeRecorder := record.NewFakeRecorder(10)
log := zap.New(zap.UseDevMode(true))

reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)

Expect(reconciler).NotTo(BeNil())
Expect(reconciler.Scheme).NotTo(BeNil())
Expect(reconciler.RuntimeReconciler).NotTo(BeNil())
Expect(reconciler.engines).NotTo(BeNil())
Expect(reconciler.mutex).NotTo(BeNil())
})
})

Describe("ControllerName", func() {
It("should return the expected controller name", func() {
fakeClient := fake.NewFakeClientWithScheme(testScheme)
fakeRecorder := record.NewFakeRecorder(10)
log := zap.New(zap.UseDevMode(true))

reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
Expect(reconciler.ControllerName()).To(Equal(controllerName))
})
})

Describe("Reconcile", func() {
Context("when the GooseFSRuntime does not exist", func() {
It("should return empty result and no error (not-found is ignored)", func() {
fakeClient := fake.NewFakeClientWithScheme(testScheme)
fakeRecorder := record.NewFakeRecorder(10)
log := zap.New(zap.UseDevMode(true))

reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)

req := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "nonexistent-runtime",
Namespace: testRuntimeNamespace,
},
}
result, err := reconciler.Reconcile(context.Background(), req)

Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(ctrl.Result{}))
})
})

Context("when a GooseFSRuntime exists", func() {
It("should attempt to reconcile the runtime without crashing", func() {
runtime := &datav1alpha1.GooseFSRuntime{
ObjectMeta: metav1.ObjectMeta{
Name: testRuntimeName,
Namespace: testRuntimeNamespace,
},
Spec: datav1alpha1.GooseFSRuntimeSpec{},
}

fakeClient := fake.NewFakeClientWithScheme(testScheme, runtime)
fakeRecorder := record.NewFakeRecorder(10)
log := zap.New(zap.UseDevMode(true))

reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)

req := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: testRuntimeName,
Namespace: testRuntimeNamespace,
},
}
// Reconcile will call ReconcileInternal which requires a full engine;
// the result may be an error from engine creation but must not panic.
_, _ = reconciler.Reconcile(context.Background(), req)
// Primary assertion: reconciler does not panic and returns
Comment thread
hxrshxz marked this conversation as resolved.
Comment thread
hxrshxz marked this conversation as resolved.
Comment thread
hxrshxz marked this conversation as resolved.
Comment thread
hxrshxz marked this conversation as resolved.
})
})
})

Describe("GetOrCreateEngine", func() {
It("should return an error when EngineImpl is unset (no engine created)", func() {
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
ObjectMeta: metav1.ObjectMeta{
Name: testRuntimeName,
Namespace: testRuntimeNamespace,
},
}

fakeClient := fake.NewFakeClientWithScheme(testScheme, gooseFSRuntime)
fakeRecorder := record.NewFakeRecorder(10)
log := zap.New(zap.UseDevMode(true))

reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
Expect(reconciler.engines).To(BeEmpty())

ctx := cruntime.ReconcileRequestContext{
NamespacedName: types.NamespacedName{
Name: testRuntimeName,
Namespace: testRuntimeNamespace,
},
Client: fakeClient,
Log: log,
Recorder: fakeRecorder,
Runtime: gooseFSRuntime,
// EngineImpl intentionally left empty — simulates unknown impl
}

engine, err := reconciler.GetOrCreateEngine(ctx)
// An empty EngineImpl causes CreateEngine to return an error; no engine stored.
Expect(err).To(HaveOccurred())
Expect(engine).To(BeNil())
Expect(reconciler.engines).To(BeEmpty())
})
Comment thread
hxrshxz marked this conversation as resolved.
})
Comment thread
hxrshxz marked this conversation as resolved.

Describe("RemoveEngine", func() {
It("should not panic when removing a non-existent engine", func() {
fakeClient := fake.NewFakeClientWithScheme(testScheme)
fakeRecorder := record.NewFakeRecorder(10)
log := zap.New(zap.UseDevMode(true))

reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)

ctx := cruntime.ReconcileRequestContext{
NamespacedName: types.NamespacedName{
Name: testRuntimeName,
Namespace: testRuntimeNamespace,
},
Client: fakeClient,
Log: log,
Recorder: fakeRecorder,
}

// RemoveEngine on an empty map must not panic.
Expect(func() {
reconciler.RemoveEngine(ctx)
}).NotTo(Panic())
Comment thread
hxrshxz marked this conversation as resolved.
Expect(reconciler.engines).To(BeEmpty())
})
})
Comment thread
hxrshxz marked this conversation as resolved.

Describe("getRuntime", func() {
It("should return error when runtime does not exist", func() {
fakeClient := fake.NewFakeClientWithScheme(testScheme)
fakeRecorder := record.NewFakeRecorder(10)
log := zap.New(zap.UseDevMode(true))

reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)

req := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "missing",
Namespace: testRuntimeNamespace,
},
}
ctx := buildTestReconcileCtx(req)
rt, err := reconciler.getRuntime(ctx)
Expect(err).To(HaveOccurred())
Expect(rt).To(BeNil())
})

It("should return the runtime when it exists", func() {
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
ObjectMeta: metav1.ObjectMeta{
Name: testRuntimeName,
Namespace: testRuntimeNamespace,
},
Spec: datav1alpha1.GooseFSRuntimeSpec{},
}

fakeClient := fake.NewFakeClientWithScheme(testScheme, gooseFSRuntime)
fakeRecorder := record.NewFakeRecorder(10)
log := zap.New(zap.UseDevMode(true))

reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)

req := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: testRuntimeName,
Namespace: testRuntimeNamespace,
},
}
ctx := buildTestReconcileCtx(req)
rt, err := reconciler.getRuntime(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(rt).NotTo(BeNil())
Expect(rt.Name).To(Equal(testRuntimeName))
Expect(rt.Namespace).To(Equal(testRuntimeNamespace))
})
})

Describe("GooseFSRuntime CRD via envtest", func() {
It("should create and retrieve a GooseFSRuntime via the k8sClient", func() {
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
ObjectMeta: metav1.ObjectMeta{
Name: envtestRuntimeName,
Namespace: "default",
},
Spec: datav1alpha1.GooseFSRuntimeSpec{},
}

By("creating the GooseFSRuntime")
Expect(k8sClient.Create(context.Background(), gooseFSRuntime)).To(Succeed())

By("retrieving the GooseFSRuntime")
var fetched datav1alpha1.GooseFSRuntime
Expect(k8sClient.Get(context.Background(), types.NamespacedName{
Name: envtestRuntimeName,
Namespace: "default",
}, &fetched)).To(Succeed())
Expect(fetched.Name).To(Equal(envtestRuntimeName))

By("deleting the GooseFSRuntime")
Expect(k8sClient.Delete(context.Background(), gooseFSRuntime)).To(Succeed())
})
})
})
25 changes: 10 additions & 15 deletions pkg/controllers/v1alpha1/goosefs/suite_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2021 The Fluid Authors.
Copyright 2026 The Fluid Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -17,7 +17,6 @@ limitations under the License.
package goosefs

import (
"os"
"path/filepath"
"testing"

Expand All @@ -40,7 +39,6 @@ import (
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
var useExistingCluster = false

func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
Expand All @@ -49,36 +47,33 @@ func TestAPIs(t *testing.T) {
"Controller Suite")
}

var _ = BeforeSuite(func(done Done) {
var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
if env := os.Getenv("USE_EXISTING_CLUSTER"); env == "true" {
useExistingCluster = true
}

By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
ErrorIfCRDPathMissing: true,
}

var err error
cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())

err = datav1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

// +kubebuilder:scaffold:scheme

k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())

close(done)
}, 60)
})

var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
Expect(err).NotTo(HaveOccurred())
})
Loading