d139e939创建于 2025年12月24日历史提交
// Copyright (c) 2024 Huawei Technologies Co., Ltd.
// openFuyao is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

package controller

import (
	"context"
	"fmt"
	"github.com/stretchr/testify/require"
	"reflect"
	"strings"
	"testing"

	"github.com/agiledragon/gomonkey/v2"
	monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	"github.com/stretchr/testify/assert"
	appsv1 "k8s.io/api/apps/v1"
	coordinationv1 "k8s.io/api/coordination/v1"
	corev1 "k8s.io/api/core/v1"
	nodev1 "k8s.io/api/node/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/client-go/kubernetes/scheme"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"volcano.sh/apis/pkg/apis/batch/v1alpha1"
	busv1alpha1 "volcano.sh/apis/pkg/apis/bus/v1alpha1"
	flowv1alpha1 "volcano.sh/apis/pkg/apis/flow/v1alpha1"
	nodeinfov1alpha1 "volcano.sh/apis/pkg/apis/nodeinfo/v1alpha1"
	"volcano.sh/apis/pkg/apis/scheduling"

	v1 "openfuyao.com/npu-operator/api/v1"
)

func getInstance() *v1.NPUClusterPolicy {
	return &v1.NPUClusterPolicy{
		TypeMeta: metav1.TypeMeta{
			Kind:       "NPUClusterPolicy",
			APIVersion: v1.GroupVersion.String(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: "cluster",
		},
		Spec: v1.NPUClusterPolicySpec{
			Driver:       v1.DriverSpec{Managed: true},
			OCIRuntime:   v1.OCIRuntimeSpec{Managed: true},
			VCScheduler:  v1.SchedulerSpec{Managed: true},
			VCController: v1.ControllerSpec{Managed: true},
			DevicePlugin: v1.DevicePluginSpec{Managed: true},
			Trainer:      v1.TrainerSpec{Managed: false},
			NodeD:        v1.NodeDSpec{Managed: true},
			ClusterD:     v1.ClusterDSpec{Managed: true},
			RSController: v1.RSControllerSpec{Managed: true},
			Exporter:     v1.ExporterSpec{Managed: true},
			MindIOTFT:    v1.MindIOTFTSpec{Managed: true},
			MindIOACP:    v1.MindIOACPSpec{Managed: true},
		},
	}
}

func getManagerAndReconciler(
	f func(*fake.ClientBuilder) *fake.ClientBuilder,
	instance *v1.NPUClusterPolicy,
) (ctrl.Manager, NPUClusterPolicyReconciler) {
	scheme := runtime.NewScheme()
	utilruntime.Must(v1.AddToScheme(scheme))
	utilruntime.Must(corev1.AddToScheme(scheme))
	utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
	builder := fake.NewClientBuilder().WithScheme(scheme)
	if f != nil {
		builder = f(builder)
	}
	if instance != nil {
		builder = builder.WithObjects(instance).WithStatusSubresource(instance)
	}
	client := builder.Build()

	mgr, err := ctrl.NewManager(&rest.Config{}, ctrl.Options{
		Scheme: client.Scheme(),
	})
	if err != nil {
		return nil, NPUClusterPolicyReconciler{}
	}
	r := NPUClusterPolicyReconciler{
		Client:   client,
		Scheme:   client.Scheme(),
		instance: instance,
	}
	return mgr, r
}

func TestClone(t *testing.T) {
	obj := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test",
		},
	}
	res := typedComponentResource[corev1.Namespace, *corev1.Namespace]{
		obj:                             obj,
		componentResourceReconcileHooks: namespaceReconcileHooks{},
	}
	tests := []struct {
		name string
	}{
		{
			name: "ok",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			re := res.clone()
			if !reflect.DeepEqual(re, res) {
				t.Errorf("clone() = %v, want %v", re, res)
			}
		})
	}
}

func TestGVK(t *testing.T) {
	test := []struct {
		name string
		obj  client.Object
		gvk  schema.GroupVersionKind
	}{
		{
			name: "deployment",
			obj: &appsv1.Deployment{
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
			},
			gvk: gvkDeployment,
		},
		{
			name: "daemonset",
			obj:  &appsv1.DaemonSet{},
			gvk:  gvkDaemonSet,
		},
	}
	for _, tt := range test {
		t.Run(tt.name, func(t *testing.T) {
			tt.obj.GetObjectKind().SetGroupVersionKind(tt.gvk)
			rc := resourceCreators[tt.gvk](tt.obj)
			got := rc.gvk()
			parts := strings.Split(got, "/")
			gk := parts[0]
			v := parts[1]
			gkparts := strings.SplitN(gk, ".", 2)
			k := gkparts[0]
			g := gkparts[1]
			if g != tt.gvk.Group || k != tt.gvk.Kind || v != tt.gvk.Version {
				t.Errorf("no equal got %v, want %v", got, tt.gvk)
			}
		})
	}
}

func TestResourceReconcile(t *testing.T) {
	tests := []struct {
		name        string
		gvk         schema.GroupVersionKind
		obj         client.Object
		managed     bool
		transforErr error
		inspectErr  error
		want        *v1.ComponentState
		wantErr     error
	}{
		{
			name: "reconcile namespace",
			obj: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test",
					Namespace: "default",
				},
			},
			gvk:     gvkNamespace,
			managed: true,
		},
		{
			name: "reconcile daemonset",
			obj: &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{
				Name:      nodedDaemonsetName,
				Namespace: placeholderValue,
			}},
			gvk:     gvkDaemonSet,
			managed: true,
		},
		{
			name: "reconcile driver daemonset",
			obj: &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{
				Name:      driverDaemonSetName,
				Namespace: placeholderValue,
			}},
			gvk:     gvkDaemonSet,
			managed: false,
		},
		{
			name: "reconcile device plugin daemonset",
			obj: &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{
				Name:      devicePluginDaemonSetName,
				Namespace: placeholderValue,
			}},
			gvk:     gvkDaemonSet,
			managed: false,
		},
		{
			name: "reconcile exporter daemonset",
			obj: &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{
				Name:      exporterDaemonsetName,
				Namespace: placeholderValue,
			}},
			gvk:     gvkDaemonSet,
			managed: false,
		},
		{
			name: "reconcile driver daemonset",
			obj: &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{
				Name:      mindIOACPDaemonsetName,
				Namespace: placeholderValue,
			}},
			gvk:     gvkDaemonSet,
			managed: false,
		},
		{
			name: "reconcile runtime daemonset",
			obj: &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{
				Name:      runtimeDaemonSetNamePrefix,
				Namespace: placeholderValue,
			}},
			gvk:     gvkDaemonSet,
			managed: false,
		},
		{
			name: "reconcile controller deployment",
			obj: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
				Name:      vcControllerDeploymentName,
				Namespace: placeholderValue,
			},
			},
			gvk:     gvkDeployment,
			managed: false,
		},
		{
			name: "reconcile runtime daemonset",
			obj: &appsv1.DaemonSet{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "ascend-runtime-",
					Namespace: "def",
				},
				Status: appsv1.DaemonSetStatus{
					DesiredNumberScheduled: 1,
					NumberUnavailable:      0,
				},
				Spec: appsv1.DaemonSetSpec{
					Selector: &metav1.LabelSelector{
						MatchLabels: map[string]string{},
					},
					Template: corev1.PodTemplateSpec{
						Spec: corev1.PodSpec{
							NodeSelector: map[string]string{"openfuyao.com/container.runtime": "containerd"},
						},
					},
				},
			},
			gvk:     gvkDaemonSet,
			managed: true,
		},
		{
			name:    "reconcile CRD",
			obj:     &apiextensionsv1.CustomResourceDefinition{},
			gvk:     gvkCRD,
			managed: false,
		},
		{
			name:    "reconcile runtimeclass",
			obj:     &nodev1.RuntimeClass{},
			gvk:     gvkRuntimeClass,
			managed: false,
		},
		{
			name:    "reconcile serviceaccount",
			obj:     &corev1.ServiceAccount{},
			gvk:     gvkServiceAccount,
			managed: false,
		},
		{
			name:    "reconcile clusterrole",
			obj:     &rbacv1.ClusterRole{},
			gvk:     gvkClusterRole,
			managed: false,
		},
		{
			name:    "reconcile clusterrolebinding",
			obj:     &rbacv1.ClusterRoleBinding{},
			gvk:     gvkClusterRoleBinding,
			managed: false,
		},
		{
			name:    "reconcile servicemonitor",
			obj:     &monitoringv1.ServiceMonitor{},
			gvk:     gvkServiceMonitor,
			managed: false,
		},
		{
			name:    "reconcile service",
			obj:     &corev1.Service{},
			gvk:     gvkService,
			managed: false,
		},
		{
			name:    "reconcile lease",
			obj:     &coordinationv1.Lease{},
			gvk:     gvkLease,
			managed: false,
		},
		{
			name:    "reconcile configmap",
			obj:     &corev1.ConfigMap{},
			gvk:     gvkConfigMap,
			managed: false,
		},
		{
			name:    "reconcile volcano.batch.job",
			obj:     &v1alpha1.Job{},
			gvk:     gvkVolcanoBatchJob,
			managed: false,
		},
		{
			name:    "reconcile volcano.bus.command",
			obj:     &busv1alpha1.Command{},
			gvk:     gvkVolcanoBusCommand,
			managed: false,
		},
		{
			name:    "reconcile pog group",
			obj:     &scheduling.PodGroup{},
			gvk:     gvkSchedulingPodgroup,
			managed: false,
		},
		{
			name:    "reconcile queue",
			gvk:     gvkSchedulerQueue,
			obj:     &scheduling.Queue{},
			managed: false,
		},
		{
			gvk:     gvkNodeInfo,
			obj:     &nodeinfov1alpha1.Numatopology{},
			name:    "reconcile numatopology",
			managed: false,
		},
		{
			name:    "reconcile jobtemplate",
			gvk:     gvkJobTemplate,
			obj:     &flowv1alpha1.JobTemplate{},
			managed: false,
		},
		{
			name:    "reconcile jobflow",
			gvk:     gvkJobFlow,
			obj:     &flowv1alpha1.JobFlow{},
			managed: false,
		},
	}
	instance := getInstance()
	_, r := getManagerAndReconciler(nil, instance)
	r.hasNPUNodes = true
	r.hasNFDLabels = true
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.obj.GetObjectKind().SetGroupVersionKind(tt.gvk)
			res := resourceCreators[tt.gvk](tt.obj)
			state, err := res.reconcile(context.Background(), &r, tt.managed)
			assert.Equal(t, tt.want, state)
			assert.Equal(t, tt.wantErr, err)
		})
	}
}

func TestDaemonsetInspect(t *testing.T) {
	test := []struct {
		name    string
		obj     client.Object
		gvk     schema.GroupVersionKind
		want    *v1.ComponentState
		wantErr error
	}{
		{
			name: "deployment",
			obj: &appsv1.Deployment{
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
				Spec: appsv1.DeploymentSpec{
					Selector: &metav1.LabelSelector{
						MatchLabels: map[string]string{},
					},
				},
			},
			gvk:  gvkDeployment,
			want: &v1.ComponentState{Phase: "pending", Reason: "PodNotRunning", Message: "Deployment Pod pod1 ,state "},
		},
		{
			name: "daemonset",
			obj: &appsv1.DaemonSet{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "ok",
					Namespace: "def",
				},
				Status: appsv1.DaemonSetStatus{
					DesiredNumberScheduled: 1,
					NumberUnavailable:      0,
				},
				Spec: appsv1.DaemonSetSpec{
					Selector: &metav1.LabelSelector{
						MatchLabels: map[string]string{},
					},
				},
			},
			gvk:  gvkDaemonSet,
			want: &v1.ComponentState{Phase: "pending", Reason: "PodNotRunning", Message: "Daemonset Pod pod1 ,state "},
		},
	}
	for _, tt := range test {
		t.Run(tt.name, func(t *testing.T) {
			tt.obj.GetObjectKind().SetGroupVersionKind(tt.gvk)
			pod1 := &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name: "pod1",
				},
			}
			scheme := runtime.NewScheme()
			_ = corev1.AddToScheme(scheme)
			_ = clientgoscheme.AddToScheme(scheme)
			fakeClient := fake.NewClientBuilder().
				WithScheme(scheme).
				WithObjects(pod1, tt.obj).
				Build()
			r := mockReconciler()
			r.Client = fakeClient
			a := gomonkey.ApplyMethod(r, "Get", func(_ *NPUClusterPolicyReconciler, _ context.Context, _ client.ObjectKey, _ client.Object, _ ...client.GetOption) error {
				return nil
			})
			b := gomonkey.ApplyMethod(r, "List", func(_ *NPUClusterPolicyReconciler, _ context.Context, _ client.ObjectList, _ ...client.ListOption) error {
				return nil
			})
			defer a.Reset()
			defer b.Reset()
			switch obj := tt.obj.(type) {
			case *appsv1.DaemonSet:
				h := &daemonSetReconcileHooks{}
				state, err := h.inspect(context.Background(), r, obj)
				assert.Equal(t, tt.want, state)
				assert.Equal(t, tt.wantErr, err)
			case *appsv1.Deployment:
				h := &deploymentReconcileHooks{}
				state, err := h.inspect(context.Background(), r, obj)
				assert.Equal(t, tt.want, state)
				assert.Equal(t, tt.wantErr, err)
			case *corev1.Namespace:
				h := &namespaceReconcileHooks{}
				state, err := h.inspect(context.Background(), r, obj)
				assert.Equal(t, tt.want, state)
				assert.Equal(t, tt.wantErr, err)
			default:
				t.Fatalf("unsupported object type: %T", tt.obj)
			}
		})
	}
}

func TestDelete(t *testing.T) {
	_ = corev1.AddToScheme(scheme.Scheme)

	owner := &v1.NPUClusterPolicy{
		ObjectMeta: metav1.ObjectMeta{
			Name: "cluster",
			UID:  uuid.NewUUID(),
		},
		TypeMeta: metav1.TypeMeta{
			Kind: "NPU ClusterPolicy",
		},
	}

	ctx := context.TODO()

	tests := []struct {
		name       string
		obj        *corev1.Namespace
		existing   client.Object
		ownerRef   bool
		shouldPass bool
	}{
		{
			name: "OwnerReference matches",
			obj: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
			},
			existing: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					OwnerReferences: []metav1.OwnerReference{
						{
							Kind: "NPU ClusterPolicy",
							Name: "cluster",
							UID:  owner.UID,
						},
					},
				},
			},
			shouldPass: true,
		},
		{
			name: "OwnerReference does not match",
			obj: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
			},
			existing: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					OwnerReferences: []metav1.OwnerReference{
						{
							Kind: "OtherKind",
							Name: "other",
							UID:  "xxxx",
						},
					},
				},
			},
			shouldPass: false,
		},
		{
			name: "No OwnerReference",
			obj: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
			},
			existing: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
			},
			shouldPass: false,
		},
		{
			name: "NotFound error",
			obj: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{Name: "not-exist"},
			},
			existing:   nil,
			shouldPass: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			builder := fake.NewClientBuilder().WithScheme(scheme.Scheme)
			if tt.existing != nil {
				builder = builder.WithObjects(tt.existing)
			}
			cl := builder.Build()

			r := &NPUClusterPolicyReconciler{
				Client:    cl,
				Scheme:    scheme.Scheme,
				namespace: "default",
				instance:  owner,
			}
			hook := defaultResourceReconcileHooks[*corev1.Namespace]{}
			obj, ok := hook.canDelete(ctx, r, tt.obj)

			if tt.shouldPass {
				assert.True(t, ok)
				assert.NotNil(t, obj)
			} else {
				assert.False(t, ok)
				assert.Nil(t, obj)
			}
		})
	}
}

func Test_runtimeDaemonSetTransformer(t *testing.T) {
	tests := []struct {
		name       string
		dsName     string
		runtimes   map[string]struct{}
		expectSkip bool
	}{
		{
			name:     "runtime exists",
			dsName:   "ascend-runtime-containerd",
			runtimes: map[string]struct{}{"containerd": {}},
		},
		{
			name:       "runtime not found",
			dsName:     "ascend-runtime-docker",
			runtimes:   map[string]struct{}{"containerd": {}},
			expectSkip: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ds := &appsv1.DaemonSet{
				ObjectMeta: metav1.ObjectMeta{
					Name: tt.dsName,
				},
			}

			r := &NPUClusterPolicyReconciler{
				runtimes: tt.runtimes,
			}

			err := runtimeDaemonSetTransformer(context.TODO(), r, ds)

			if tt.expectSkip {
				assert.Equal(t, errSkipReconciliation, err)
			} else {
				assert.NoError(t, err)
				runtime := strings.TrimPrefix(tt.dsName, runtimeDaemonSetNamePrefix)
				assert.Equal(t, runtime, ds.Spec.Template.Spec.NodeSelector[runtimeLabelKey])
				assert.Equal(t, "", ds.Spec.Template.Annotations[runtimeEndpointAnnotationKey])
			}
		})
	}
}

func Test_runtimeDaemonSetInspector(t *testing.T) {
	const (
		nodeName   = "npu-node"
		podName    = "npu-pod"
		endpoint   = "unix:///var/run/containerd.sock"
		ns         = "test-ns"
		labelKey   = "app"
		labelValue = "npu"
	)

	selector := map[string]string{labelKey: labelValue}

	tests := []struct {
		name          string
		pods          []corev1.Pod
		nodeEndpoints map[string]string
		expectUpdated bool
		expectErr     bool
	}{
		{
			name: "pod matches node endpoint and gets annotation",
			pods: []corev1.Pod{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name:      podName,
						Namespace: ns,
						Labels:    selector,
					},
					Spec: corev1.PodSpec{
						NodeName: nodeName,
					},
				},
			},
			nodeEndpoints: map[string]string{
				nodeName: endpoint,
			},
			expectUpdated: true,
		},
		{
			name: "pod node not in endpoint map, no update",
			pods: []corev1.Pod{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name:      podName,
						Namespace: ns,
						Labels:    selector,
					},
					Spec: corev1.PodSpec{
						NodeName: "unknown-node",
					},
				},
			},
			nodeEndpoints: map[string]string{
				nodeName: endpoint,
			},
			expectUpdated: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_ = corev1.AddToScheme(scheme.Scheme)

			podObjs := make([]client.Object, len(tt.pods))
			for i := range tt.pods {
				podObjs[i] = &tt.pods[i]
			}

			cl := fake.NewClientBuilder().
				WithScheme(scheme.Scheme).
				WithObjects(podObjs...).
				Build()

			r := &NPUClusterPolicyReconciler{
				Client:               cl,
				Scheme:               scheme.Scheme,
				nodeRuntimeEndpoints: tt.nodeEndpoints,
				namespace:            ns,
			}

			ds := &appsv1.DaemonSet{
				Spec: appsv1.DaemonSetSpec{
					Selector: &metav1.LabelSelector{
						MatchLabels: selector,
					},
				},
			}

			err := runtimeDaemonSetInspector(context.TODO(), r, ds)
			if tt.expectErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}

			if tt.expectUpdated {
				var updatedPod corev1.Pod
				err := cl.Get(context.TODO(), client.ObjectKey{Namespace: ns, Name: podName}, &updatedPod)
				assert.NoError(t, err)
				assert.Equal(t, endpoint, updatedPod.Annotations[runtimeEndpointAnnotationKey])
			}
		})
	}
}

func mockLog(*v1.NPUClusterPolicySpec) *v1.LogRotate {
	return &v1.LogRotate{
		LogLevel: v1.LogInfo,
		LogFile:  "/var/log/npu.log",
		Rotate:   5,
		MaxAge:   7,
		Compress: true,
	}
}

func Test_LogRotateTransformer_DaemonSet(t *testing.T) {
	ds := &appsv1.DaemonSet{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test",
		},
		Spec: appsv1.DaemonSetSpec{
			Template: corev1.PodTemplateSpec{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "npu",
							Env: []corev1.EnvVar{
								{Name: logRotateLogLevelEnvName, Value: "old"},
								{Name: logRotateLogFileEnvName, Value: "old"},
								{Name: logRotateRotateEnvName, Value: "old"},
								{Name: logRotateMaxAgeEnvName, Value: "old"},
								{Name: logRotateCompressEnvName, Value: "old"},
							},
						},
					},
				},
			},
		},
	}

	r := mockReconciler()
	r.instance = getInstance()
	transformer := newWorkloadLogRotateTransformer[appsv1.DaemonSet](mockLog)
	err := transformer(context.TODO(), r, ds)
	assert.NoError(t, err)
	envMap := map[string]string{}
	for _, env := range ds.Spec.Template.Spec.Containers[0].Env {
		envMap[env.Name] = env.Value
	}
	assert.Equal(t, fmt.Sprintf("%d", v1.LogInfoValue), envMap[logRotateLogLevelEnvName])
	assert.Equal(t, "/var/log/npu.log", envMap[logRotateLogFileEnvName])
	assert.Equal(t, "5", envMap[logRotateRotateEnvName])
	assert.Equal(t, "7", envMap[logRotateMaxAgeEnvName])
	assert.Equal(t, "true", envMap[logRotateCompressEnvName])
}

func Test_configMapReconcileHooks_transform_volcanoConfig(t *testing.T) {
	const (
		configMapName          = "volcano-scheduler-configmap"
		namespace              = "test-ns"
		nodeName               = "node01"
		arch                   = "amd64"
		volcanoVersion         = "7.1.RC1"
		expectedLine           = "  - name: volcano-npu_7.1.RC1_linux-x86_64"
		initialSchedulerConfig = `actions: "enqueue, allocate"
tiers:
- plugins:
  - name: volcano-npu_OLD_linux-aarch64`
	)
	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      configMapName,
			Namespace: namespace,
		},
		Data: map[string]string{},
	}
	node := &corev1.Node{
		ObjectMeta: metav1.ObjectMeta{Name: nodeName},
		Status: corev1.NodeStatus{
			NodeInfo: corev1.NodeSystemInfo{Architecture: arch},
		},
	}
	s := scheme.Scheme
	_ = corev1.AddToScheme(s)
	_ = clientgoscheme.AddToScheme(s)
	_ = v1.AddToScheme(s)
	cl := fake.NewClientBuilder().WithScheme(s).WithObjects(configMap, node).Build()
	r := &NPUClusterPolicyReconciler{
		Client:    cl,
		Scheme:    s,
		namespace: namespace,
		instance: &v1.NPUClusterPolicy{
			Spec: v1.NPUClusterPolicySpec{
				VCScheduler: v1.SchedulerSpec{
					SchedulerConfigMap: initialSchedulerConfig,
				},
			},
		},
	}
	t.Setenv("OPERATOR_NODE_NAME", nodeName)
	patchVolcanoVer := gomonkey.ApplyFuncReturn(ObtainVolcanoVersion, volcanoVersion, nil)
	defer patchVolcanoVer.Reset()
	h := configMapReconcileHooks{}
	err := h.transform(context.TODO(), r, configMap)
	assert.NoError(t, err)
	result := configMap.Data["volcano-scheduler.conf"]
	t.Logf("configmap:\n%s", result)
	assert.NotEmpty(t, result, "ConfigMap should not be empty")
	assert.Contains(t, result, expectedLine)
	assert.NotContains(t, result, "volcano-npu_OLD_linux-aarch64")
}

func TestNewWorkloadVolumesTransformerMergeExisting(t *testing.T) {
	spec := &v1.NPUClusterPolicySpec{
		DevicePlugin: v1.DevicePluginSpec{
			VolumeSpec: v1.VolumesSpec{
				Volumes: []corev1.Volume{
					{Name: "config", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
				},
				VolumeMounts: []corev1.VolumeMount{
					{Name: "config", MountPath: "/etc/config"},
				},
			},
		},
	}
	transformer := newWorkloadVolumesTransformer[appsv1.DaemonSet](devicePluginVolumesGetter)
	ds := &appsv1.DaemonSet{
		Spec: appsv1.DaemonSetSpec{
			Template: corev1.PodTemplateSpec{
				Spec: corev1.PodSpec{
					Volumes: []corev1.Volume{
						{Name: "existing", VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/tmp"}}},
						{Name: "config", VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/old"}}},
					},
					Containers: []corev1.Container{
						{
							Name: "dp",
							VolumeMounts: []corev1.VolumeMount{
								{Name: "existing", MountPath: "/tmp"},
								{Name: "config", MountPath: "/old"},
							},
						},
					},
				},
			},
		},
	}
	r := &NPUClusterPolicyReconciler{
		instance: &v1.NPUClusterPolicy{Spec: *spec},
	}
	err := transformer(context.Background(), r, ds)
	require.NoError(t, err)
	assert.Len(t, ds.Spec.Template.Spec.Volumes, 2)
	volMap := make(map[string]corev1.Volume)
	for _, v := range ds.Spec.Template.Spec.Volumes {
		volMap[v.Name] = v
	}
	assert.Equal(t, "/tmp", volMap["existing"].HostPath.Path)
	assert.NotNil(t, volMap["config"].EmptyDir) // replaced by EmptyDir
	mountMap := make(map[string]corev1.VolumeMount)
	for _, m := range ds.Spec.Template.Spec.Containers[0].VolumeMounts {
		mountMap[m.Name] = m
	}
	assert.Equal(t, "/tmp", mountMap["existing"].MountPath)
	assert.Equal(t, "/etc/config", mountMap["config"].MountPath) // updated
}

type testCase struct {
	name     string
	src      client.Object
	dst      client.Object
	validate func(*testing.T, client.Object)
}

func runIntoTest[T any, PT clientObject[T]](t *testing.T, tc testCase) {
	t.Run(tc.name, func(t *testing.T) {

		res := typedComponentResource[T, PT]{
			obj: tc.src.(PT),
		}

		res.into(tc.dst)

		tc.validate(t, tc.dst)
	})
}

// TestInto_Metadata verifies that Labels, Annotations, OwnerRefs are merged/copied correctly.
func TestIntoMetadata(t *testing.T) {
	src := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Labels:          map[string]string{"app": "npu", "new": "val"},
			Annotations:     map[string]string{"anno": "yes"},
			OwnerReferences: []metav1.OwnerReference{{Name: "owner"}},
			Finalizers:      []string{"finalizer.npu"},
		},
	}
	dst := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      map[string]string{"existing": "old", "app": "old"},
			Annotations: map[string]string{"other": "no"},
		},
	}

	tc := testCase{
		name: "Metadata Merge",
		src:  src,
		dst:  dst,
		validate: func(t *testing.T, obj client.Object) {
			labels := obj.GetLabels()
			assert.Equal(t, "npu", labels["app"])
			assert.Equal(t, "old", labels["existing"])
			assert.Equal(t, "val", labels["new"])

			annos := obj.GetAnnotations()
			assert.Equal(t, "yes", annos["anno"])
			assert.Equal(t, "no", annos["other"])

			refs := obj.GetOwnerReferences()
			if assert.NotEmpty(t, refs) {
				assert.Equal(t, "owner", refs[0].Name)
			}

			finals := obj.GetFinalizers()
			if assert.NotEmpty(t, finals) {
				assert.Equal(t, "finalizer.npu", finals[0])
			}
		},
	}
	runIntoTest[appsv1.Deployment, *appsv1.Deployment](t, tc)
}

func TestIntoWorkloads(t *testing.T) {
	tests := []testCase{
		{
			name: "Deployment Spec Copy",
			src:  &appsv1.Deployment{Spec: appsv1.DeploymentSpec{Replicas: int32Ptr(3)}},
			dst:  &appsv1.Deployment{Spec: appsv1.DeploymentSpec{Replicas: int32Ptr(1)}},
			validate: func(t *testing.T, obj client.Object) {
				assert.Equal(t, int32(3), *obj.(*appsv1.Deployment).Spec.Replicas)
			},
		},
		{
			name: "DaemonSet Spec Copy",
			src:  &appsv1.DaemonSet{Spec: appsv1.DaemonSetSpec{MinReadySeconds: 10}},
			dst:  &appsv1.DaemonSet{Spec: appsv1.DaemonSetSpec{MinReadySeconds: 0}},
			validate: func(t *testing.T, obj client.Object) {
				assert.Equal(t, int32(10), obj.(*appsv1.DaemonSet).Spec.MinReadySeconds)
			},
		},
		{
			name: "Volcano Job Spec Copy",
			src:  &v1alpha1.Job{Spec: v1alpha1.JobSpec{MinAvailable: 5}},
			dst:  &v1alpha1.Job{Spec: v1alpha1.JobSpec{MinAvailable: 1}},
			validate: func(t *testing.T, obj client.Object) {
				assert.Equal(t, int32(5), obj.(*v1alpha1.Job).Spec.MinAvailable)
			},
		},
	}

	runIntoTest[appsv1.Deployment, *appsv1.Deployment](t, tests[0])
	runIntoTest[appsv1.DaemonSet, *appsv1.DaemonSet](t, tests[1])
	runIntoTest[v1alpha1.Job, *v1alpha1.Job](t, tests[2])
}

func TestIntoCoreResources(t *testing.T) {
	tests := []testCase{
		{
			name: "Service Preserve ClusterIP",
			src:  &corev1.Service{Spec: corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 80}}}},
			dst:  &corev1.Service{Spec: corev1.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []corev1.ServicePort{{Port: 90}}}},
			validate: func(t *testing.T, obj client.Object) {
				svc := obj.(*corev1.Service)
				assert.Equal(t, "1.2.3.4", svc.Spec.ClusterIP, "ClusterIP should be preserved")
				assert.Equal(t, int32(80), svc.Spec.Ports[0].Port, "Ports should be updated")
			},
		},
		{
			name: "ConfigMap Data Copy",
			src:  &corev1.ConfigMap{Data: map[string]string{"key": "new"}},
			dst:  &corev1.ConfigMap{Data: map[string]string{"key": "old"}},
			validate: func(t *testing.T, obj client.Object) {
				assert.Equal(t, "new", obj.(*corev1.ConfigMap).Data["key"])
			},
		},
		{
			name: "ServiceAccount Copy",
			src:  &corev1.ServiceAccount{AutomountServiceAccountToken: boolPtr(true)},
			dst:  &corev1.ServiceAccount{AutomountServiceAccountToken: boolPtr(false)},
			validate: func(t *testing.T, obj client.Object) {
				assert.True(t, *obj.(*corev1.ServiceAccount).AutomountServiceAccountToken)
			},
		},
		{
			name: "Namespace Spec Copy",
			src:  &corev1.Namespace{Spec: corev1.NamespaceSpec{Finalizers: []corev1.FinalizerName{"test"}}},
			dst:  &corev1.Namespace{},
			validate: func(t *testing.T, obj client.Object) {
				assert.Equal(t, corev1.FinalizerName("test"), obj.(*corev1.Namespace).Spec.Finalizers[0])
			},
		},
	}

	runIntoTest[corev1.Service, *corev1.Service](t, tests[0])
	runIntoTest[corev1.ConfigMap, *corev1.ConfigMap](t, tests[1])
	runIntoTest[corev1.ServiceAccount, *corev1.ServiceAccount](t, tests[2])
	runIntoTest[corev1.Namespace, *corev1.Namespace](t, tests[3])
}

func TestIntoVolcanoResources(t *testing.T) {
	cmdTest := testCase{
		name: "Volcano Command Fields Copy",
		src:  &busv1alpha1.Command{Action: "Restart", Message: "Updating"},
		dst:  &busv1alpha1.Command{Action: "Stop", Message: "Old"},
		validate: func(t *testing.T, obj client.Object) {
			cmd := obj.(*busv1alpha1.Command)
			assert.Equal(t, "Restart", cmd.Action)
			assert.Equal(t, "Updating", cmd.Message)
		},
	}
	runIntoTest[busv1alpha1.Command, *busv1alpha1.Command](t, cmdTest)

	pgTest := testCase{
		name: "PodGroup Spec Copy",
		src:  &scheduling.PodGroup{Spec: scheduling.PodGroupSpec{MinMember: 2}},
		dst:  &scheduling.PodGroup{Spec: scheduling.PodGroupSpec{MinMember: 1}},
		validate: func(t *testing.T, obj client.Object) {
			assert.Equal(t, int32(2), obj.(*scheduling.PodGroup).Spec.MinMember)
		},
	}
	runIntoTest[scheduling.PodGroup, *scheduling.PodGroup](t, pgTest)

}

func TestIntoExtendedResources(t *testing.T) {
	tests := []testCase{
		{
			name: "RuntimeClass Fields Copy",
			src:  &nodev1.RuntimeClass{Handler: "runv", Overhead: &nodev1.Overhead{}},
			dst:  &nodev1.RuntimeClass{Handler: "runc"},
			validate: func(t *testing.T, obj client.Object) {
				rc := obj.(*nodev1.RuntimeClass)
				assert.Equal(t, "runv", rc.Handler)
				assert.NotNil(t, rc.Overhead)
			},
		},
		{
			name: "ClusterRole Rules Copy",
			src:  &rbacv1.ClusterRole{Rules: []rbacv1.PolicyRule{{Verbs: []string{"get"}}}},
			dst:  &rbacv1.ClusterRole{Rules: []rbacv1.PolicyRule{}},
			validate: func(t *testing.T, obj client.Object) {
				assert.Equal(t, "get", obj.(*rbacv1.ClusterRole).Rules[0].Verbs[0])
			},
		},
	}

	runIntoTest[nodev1.RuntimeClass, *nodev1.RuntimeClass](t, tests[0])
	runIntoTest[rbacv1.ClusterRole, *rbacv1.ClusterRole](t, tests[1])
}

func int32Ptr(i int32) *int32 { return &i }
func boolPtr(b bool) *bool    { return &b }