diff --git a/pkg/resourceinterpreter/customized/declarative/luavm/kube.go b/pkg/resourceinterpreter/customized/declarative/luavm/kube.go index d7e24b2cb..ab83bf3a5 100644 --- a/pkg/resourceinterpreter/customized/declarative/luavm/kube.go +++ b/pkg/resourceinterpreter/customized/declarative/luavm/kube.go @@ -28,6 +28,9 @@ const ( // - function accuratePodRequirements(pod) requirements // accurate total resource requirements for pod. Example: // requirements = kube.accuratePodRequirements(pod) +// - function getPodDependencies(podTemplate, namespace) dependencies +// get total dependencies from podTemplate and namespace. Example: +// dependencies = kube.getPodDependencies(podTemplate, namespace) func KubeLoader(ls *lua.LState) int { mod := ls.SetFuncs(ls.NewTable(), kubeFuncs) ls.Push(mod) @@ -37,6 +40,7 @@ func KubeLoader(ls *lua.LState) int { var kubeFuncs = map[string]lua.LGFunction{ "resourceAdd": resourceAdd, "accuratePodRequirements": accuratePodRequirements, + "getPodDependencies": getPodDependencies, } func resourceAdd(ls *lua.LState) int { @@ -78,6 +82,35 @@ func accuratePodRequirements(ls *lua.LState) int { return 1 } +func getPodDependencies(ls *lua.LState) int { + n := ls.GetTop() + if n != 2 { + ls.RaiseError("getPodDependencies only accepts two argument") + return 0 + } + + podTemplate := ls.CheckTable(1) + namespace := checkNamespace(ls, 2) + + template := &corev1.PodTemplateSpec{} + err := ConvertLuaResultInto(podTemplate, template) + if err != nil { + ls.RaiseError("fail to convert lua value %#v to corev1.PodTemplateSpec: %v", podTemplate, err) + return 0 + } + + pod := helper.GeneratePodFromTemplateAndNamespace(template, namespace) + dependencies, _ := helper.GetDependenciesFromPodTemplate(pod) + retValue, err := decodeValue(ls, dependencies) + if err != nil { + ls.RaiseError("fail to convert %#v to Lua value: %v", dependencies, err) + return 0 + } + + ls.Push(retValue) + return 1 +} + func checkResourceQuantity(ls *lua.LState, n int) resource.Quantity { v := ls.Get(n) switch typ := v.Type(); typ { @@ -91,3 +124,16 @@ func checkResourceQuantity(ls *lua.LState, n int) resource.Quantity { return resource.Quantity{} } } + +func checkNamespace(ls *lua.LState, n int) string { + v := ls.Get(n) + switch typ := v.Type(); typ { + case lua.LTNil: + return "default" + case lua.LTString, lua.LTNumber: + return ls.CheckString(n) + default: + ls.TypeError(n, lua.LTString) + return "" + } +} diff --git a/pkg/resourceinterpreter/customized/declarative/luavm/lua_test.go b/pkg/resourceinterpreter/customized/declarative/luavm/lua_test.go index 7b29b0e12..92e9289e6 100644 --- a/pkg/resourceinterpreter/customized/declarative/luavm/lua_test.go +++ b/pkg/resourceinterpreter/customized/declarative/luavm/lua_test.go @@ -496,7 +496,7 @@ func TestStatusReflection(t *testing.T) { } func TestGetDeployPodDependencies(t *testing.T) { - newDeploy := appsv1.Deployment{ + newDeploy1 := appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Deployment", @@ -513,14 +513,24 @@ func TestGetDeployPodDependencies(t *testing.T) { }, }, } - newObj, _ := helper.ToUnstructured(&newDeploy) - expect := make([]configv1alpha1.DependentObjectReference, 1) - expect[0] = configv1alpha1.DependentObjectReference{ + newDeploy2 := newDeploy1.DeepCopy() + newDeploy2.Namespace = "" + newObj1, _ := helper.ToUnstructured(&newDeploy1) + expect1 := make([]configv1alpha1.DependentObjectReference, 1) + expect1[0] = configv1alpha1.DependentObjectReference{ APIVersion: "v1", Kind: "ServiceAccount", Namespace: "test", Name: "test", } + newObj2, _ := helper.ToUnstructured(&newDeploy2) + expect2 := make([]configv1alpha1.DependentObjectReference, 1) + expect2[0] = configv1alpha1.DependentObjectReference{ + APIVersion: "v1", + Kind: "ServiceAccount", + Namespace: "default", + Name: "test", + } tests := []struct { name string curObj *unstructured.Unstructured @@ -529,7 +539,7 @@ func TestGetDeployPodDependencies(t *testing.T) { }{ { name: "Get GetDeployPodDependencies", - curObj: newObj, + curObj: newObj1, luaScript: `function GetDependencies(desiredObj) dependentSas = {} refs = {} @@ -549,7 +559,27 @@ func TestGetDeployPodDependencies(t *testing.T) { end return refs end`, - want: expect, + want: expect1, + }, + { + name: "Test getPodDependencies", + curObj: newObj1, + luaScript: `local kube = require("kube") + function GetDependencies(desiredObj) + dependencies = kube.getPodDependencies(desiredObj.spec.template, desiredObj.metadata.namespace) + return dependencies + end`, + want: expect1, + }, + { + name: "Test getPodDependencies when desiredObj's namespace is empty", + curObj: newObj2, + luaScript: `local kube = require("kube") + function GetDependencies(desiredObj) + dependencies = kube.getPodDependencies(desiredObj.spec.template, desiredObj.metadata.namespace) + return dependencies + end`, + want: expect2, }, } diff --git a/pkg/resourceinterpreter/default/native/dependencies.go b/pkg/resourceinterpreter/default/native/dependencies.go index 9bd09d81e..5982ff876 100644 --- a/pkg/resourceinterpreter/default/native/dependencies.go +++ b/pkg/resourceinterpreter/default/native/dependencies.go @@ -8,7 +8,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/sets" configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" "github.com/karmada-io/karmada/pkg/util" @@ -40,7 +39,7 @@ func getDeploymentDependencies(object *unstructured.Unstructured) ([]configv1alp return nil, err } - return getDependenciesFromPodTemplate(podObj) + return helper.GetDependenciesFromPodTemplate(podObj) } func getJobDependencies(object *unstructured.Unstructured) ([]configv1alpha1.DependentObjectReference, error) { @@ -55,7 +54,7 @@ func getJobDependencies(object *unstructured.Unstructured) ([]configv1alpha1.Dep return nil, err } - return getDependenciesFromPodTemplate(podObj) + return helper.GetDependenciesFromPodTemplate(podObj) } func getCronJobDependencies(object *unstructured.Unstructured) ([]configv1alpha1.DependentObjectReference, error) { @@ -70,7 +69,7 @@ func getCronJobDependencies(object *unstructured.Unstructured) ([]configv1alpha1 return nil, err } - return getDependenciesFromPodTemplate(podObj) + return helper.GetDependenciesFromPodTemplate(podObj) } func getPodDependencies(object *unstructured.Unstructured) ([]configv1alpha1.DependentObjectReference, error) { @@ -80,7 +79,7 @@ func getPodDependencies(object *unstructured.Unstructured) ([]configv1alpha1.Dep return nil, fmt.Errorf("failed to convert Pod from unstructured object: %v", err) } - return getDependenciesFromPodTemplate(podObj) + return helper.GetDependenciesFromPodTemplate(podObj) } func getDaemonSetDependencies(object *unstructured.Unstructured) ([]configv1alpha1.DependentObjectReference, error) { @@ -95,7 +94,7 @@ func getDaemonSetDependencies(object *unstructured.Unstructured) ([]configv1alph return nil, err } - return getDependenciesFromPodTemplate(podObj) + return helper.GetDependenciesFromPodTemplate(podObj) } func getStatefulSetDependencies(object *unstructured.Unstructured) ([]configv1alpha1.DependentObjectReference, error) { @@ -110,89 +109,5 @@ func getStatefulSetDependencies(object *unstructured.Unstructured) ([]configv1al return nil, err } - return getDependenciesFromPodTemplate(podObj) -} - -func getDependenciesFromPodTemplate(podObj *corev1.Pod) ([]configv1alpha1.DependentObjectReference, error) { - dependentConfigMaps := getConfigMapNames(podObj) - dependentSecrets := getSecretNames(podObj) - dependentSas := getServiceAccountNames(podObj) - dependentPVCs := getPVCNames(podObj) - var dependentObjectRefs []configv1alpha1.DependentObjectReference - for cm := range dependentConfigMaps { - dependentObjectRefs = append(dependentObjectRefs, configv1alpha1.DependentObjectReference{ - APIVersion: "v1", - Kind: "ConfigMap", - Namespace: podObj.Namespace, - Name: cm, - }) - } - - for secret := range dependentSecrets { - dependentObjectRefs = append(dependentObjectRefs, configv1alpha1.DependentObjectReference{ - APIVersion: "v1", - Kind: "Secret", - Namespace: podObj.Namespace, - Name: secret, - }) - } - for sa := range dependentSas { - dependentObjectRefs = append(dependentObjectRefs, configv1alpha1.DependentObjectReference{ - APIVersion: "v1", - Kind: "ServiceAccount", - Namespace: podObj.Namespace, - Name: sa, - }) - } - - for pvc := range dependentPVCs { - dependentObjectRefs = append(dependentObjectRefs, configv1alpha1.DependentObjectReference{ - APIVersion: "v1", - Kind: "PersistentVolumeClaim", - Namespace: podObj.Namespace, - Name: pvc, - }) - } - - return dependentObjectRefs, nil -} - -func getSecretNames(pod *corev1.Pod) sets.Set[string] { - result := sets.New[string]() - lifted.VisitPodSecretNames(pod, func(name string) bool { - result.Insert(name) - return true - }) - return result -} - -func getServiceAccountNames(pod *corev1.Pod) sets.Set[string] { - result := sets.New[string]() - if pod.Spec.ServiceAccountName != "" && pod.Spec.ServiceAccountName != "default" { - result.Insert(pod.Spec.ServiceAccountName) - } - return result -} - -func getConfigMapNames(pod *corev1.Pod) sets.Set[string] { - result := sets.New[string]() - lifted.VisitPodConfigmapNames(pod, func(name string) bool { - result.Insert(name) - return true - }) - return result -} - -func getPVCNames(pod *corev1.Pod) sets.Set[string] { - result := sets.New[string]() - for i := range pod.Spec.Volumes { - volume := pod.Spec.Volumes[i] - if volume.PersistentVolumeClaim != nil { - claimName := volume.PersistentVolumeClaim.ClaimName - if len(claimName) != 0 { - result.Insert(claimName) - } - } - } - return result + return helper.GetDependenciesFromPodTemplate(podObj) } diff --git a/pkg/resourceinterpreter/default/native/dependencies_test.go b/pkg/resourceinterpreter/default/native/dependencies_test.go index c0caf0b24..1ee545304 100644 --- a/pkg/resourceinterpreter/default/native/dependencies_test.go +++ b/pkg/resourceinterpreter/default/native/dependencies_test.go @@ -1,545 +1 @@ package native - -import ( - "reflect" - "testing" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/sets" - - configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" - "github.com/karmada-io/karmada/test/helper" -) - -var ( - namespace = "karmada-test" - podName = "fake-pod" -) - -func TestGetSecretNames(t *testing.T) { - emptySecretsPod := helper.NewPod(namespace, podName) - - secretsPod := helper.NewPod(namespace, podName) - secretsPod.Spec.ImagePullSecrets = []corev1.LocalObjectReference{ - { - Name: "image-secret", - }, - } - secretsPod.Spec.InitContainers = []corev1.Container{ - { - EnvFrom: []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "initcontainer-envfrom-secret", - }, - }, - }, - }, - Env: []corev1.EnvVar{ - { - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "initcontainer-env-secret", - }, - }, - }, - }, - }, - }, - } - secretsPod.Spec.Containers = []corev1.Container{ - { - EnvFrom: []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "container-envfrom-secret", - }, - }, - }, - }, - Env: []corev1.EnvVar{ - { - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "container-env-secret", - }, - }, - }, - }, - }, - }, - } - secretsPod.Spec.EphemeralContainers = []corev1.EphemeralContainer{ - { - EphemeralContainerCommon: corev1.EphemeralContainerCommon{ - EnvFrom: []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "ephemeralcontainer-envfrom-secret", - }, - }, - }, - }, - Env: []corev1.EnvVar{ - { - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "ephemeralcontainer-env-secret", - }, - }, - }, - }, - }, - }, - }, - } - secretsPod.Spec.Volumes = []corev1.Volume{ - { - VolumeSource: corev1.VolumeSource{ - AzureFile: &corev1.AzureFileVolumeSource{ - SecretName: "azure-secret", - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - CephFS: &corev1.CephFSVolumeSource{ - SecretRef: &corev1.LocalObjectReference{ - Name: "cephfs-secret", - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - Cinder: &corev1.CinderVolumeSource{ - SecretRef: &corev1.LocalObjectReference{ - Name: "cinder-secret", - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - FlexVolume: &corev1.FlexVolumeSource{ - SecretRef: &corev1.LocalObjectReference{ - Name: "flex-secret", - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - Sources: []corev1.VolumeProjection{ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "projected-secret", - }, - }, - }, - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - RBD: &corev1.RBDVolumeSource{ - SecretRef: &corev1.LocalObjectReference{ - Name: "rbd-secret", - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "secret-config", - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - ScaleIO: &corev1.ScaleIOVolumeSource{ - SecretRef: &corev1.LocalObjectReference{ - Name: "scaleio-secret", - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - ISCSI: &corev1.ISCSIVolumeSource{ - SecretRef: &corev1.LocalObjectReference{ - Name: "iscsi-secret", - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - StorageOS: &corev1.StorageOSVolumeSource{ - SecretRef: &corev1.LocalObjectReference{ - Name: "storageos-secret", - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - CSI: &corev1.CSIVolumeSource{ - NodePublishSecretRef: &corev1.LocalObjectReference{ - Name: "csi-secret", - }, - }, - }, - }, - } - - tests := []struct { - name string - pod *corev1.Pod - expected sets.Set[string] - }{ - { - name: "get secret names from pod without secrets", - pod: emptySecretsPod, - expected: make(sets.Set[string], 0), - }, - { - name: "get secret names from pod with secrets", - pod: secretsPod, - expected: sets.New("image-secret", "initcontainer-envfrom-secret", "initcontainer-env-secret", "container-envfrom-secret", "container-env-secret", - "ephemeralcontainer-envfrom-secret", "ephemeralcontainer-env-secret", "azure-secret", "cephfs-secret", "cinder-secret", "flex-secret", "projected-secret", - "rbd-secret", "secret-config", "scaleio-secret", "iscsi-secret", "storageos-secret", "csi-secret"), - }, - } - - for i := range tests { - tt := tests[i] - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - res := getSecretNames(tt.pod) - if !res.Equal(tt.expected) { - t.Errorf("getSecretNames() = %v, want %v", res, tt.expected) - } - }) - } -} - -func TestGetConfigMapNames(t *testing.T) { - emptyConfigMapsPod := helper.NewPod(namespace, podName) - - configMapsPod := helper.NewPod(namespace, podName) - configMapsPod.Spec.InitContainers = []corev1.Container{ - { - EnvFrom: []corev1.EnvFromSource{ - { - ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "initcontainer-envfrom-configmap", - }, - }, - }, - }, - Env: []corev1.EnvVar{ - { - ValueFrom: &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "initcontainer-env-configmap", - }, - }, - }, - }, - }, - }, - } - configMapsPod.Spec.Containers = []corev1.Container{ - { - EnvFrom: []corev1.EnvFromSource{ - { - ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "container-envfrom-configmap", - }, - }, - }, - }, - Env: []corev1.EnvVar{ - { - ValueFrom: &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "container-env-configmap", - }, - }, - }, - }, - }, - }, - } - configMapsPod.Spec.EphemeralContainers = []corev1.EphemeralContainer{ - { - EphemeralContainerCommon: corev1.EphemeralContainerCommon{ - EnvFrom: []corev1.EnvFromSource{ - { - ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "ephemeralcontainer-envfrom-configmap", - }, - }, - }, - }, - Env: []corev1.EnvVar{ - { - ValueFrom: &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "ephemeralcontainer-env-configmap", - }, - }, - }, - }, - }, - }, - }, - } - configMapsPod.Spec.Volumes = []corev1.Volume{ - { - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - Sources: []corev1.VolumeProjection{ - { - ConfigMap: &corev1.ConfigMapProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "projected-configmap", - }, - }, - }, - }, - }, - }, - }, - { - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "configmap-config", - }, - }, - }, - }, - } - - tests := []struct { - name string - pod *corev1.Pod - expected sets.Set[string] - }{ - { - name: "get configMap names from pod without configMaps", - pod: emptyConfigMapsPod, - expected: make(sets.Set[string], 0), - }, - { - name: "get configMap names from pod with configMaps", - pod: configMapsPod, - expected: sets.New("initcontainer-envfrom-configmap", "initcontainer-env-configmap", "container-envfrom-configmap", "container-env-configmap", - "ephemeralcontainer-envfrom-configmap", "ephemeralcontainer-env-configmap", "projected-configmap", "configmap-config"), - }, - } - - for i := range tests { - tt := tests[i] - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - res := getConfigMapNames(tt.pod) - if !res.Equal(tt.expected) { - t.Errorf("getConfigMapNames() = %v, want %v", res, tt.expected) - } - }) - } -} - -func TestGetPVCNames(t *testing.T) { - emptyPvcsPod := helper.NewPod(namespace, podName) - pvcsPod := helper.NewPod(namespace, podName) - pvcsPod.Spec.Volumes = []corev1.Volume{ - { - Name: "foo-name", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "fake-foo", - ReadOnly: true, - }, - }, - }, - { - Name: "bar-name", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "fake-bar", - ReadOnly: true, - }, - }, - }, - } - - tests := []struct { - name string - pod *corev1.Pod - expected sets.Set[string] - }{ - { - name: "get pvc names from pod without pvcs", - pod: emptyPvcsPod, - expected: make(sets.Set[string], 0), - }, - { - name: "get pvc names from pod with pvcs", - pod: pvcsPod, - expected: sets.New("fake-foo", "fake-bar"), - }, - } - - for i := range tests { - tt := tests[i] - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - res := getPVCNames(tt.pod) - if !res.Equal(tt.expected) { - t.Errorf("getPVCNames() = %v, want %v", res, tt.expected) - } - }) - } -} - -func TestGetDependenciesFromPodTemplate(t *testing.T) { - emptyDependenciesPod := helper.NewPod(namespace, podName) - - dependenciesPod := helper.NewPod(namespace, podName) - dependenciesPod.Spec.Volumes = []corev1.Volume{ - { - Name: "foo-name", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "fake-foo", - }, - }, - }, - }, - { - Name: "bar-name", - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "fake-bar", - }, - }, - }, - { - Name: "pvc-name", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "fake-pvc", - }, - }, - }, - } - dependenciesPod.Spec.ServiceAccountName = "fake-sa" - - tests := []struct { - name string - pod *corev1.Pod - expected []configv1alpha1.DependentObjectReference - }{ - { - name: "get dependencies from PodTemplate without dependencies", - pod: emptyDependenciesPod, - expected: nil, - }, - { - name: "get dependencies from PodTemplate with dependencies", - pod: dependenciesPod, - expected: []configv1alpha1.DependentObjectReference{ - { - APIVersion: "v1", - Kind: "ConfigMap", - Namespace: namespace, - Name: "fake-foo", - }, - { - APIVersion: "v1", - Kind: "Secret", - Namespace: namespace, - Name: "fake-bar", - }, - { - APIVersion: "v1", - Kind: "ServiceAccount", - Namespace: namespace, - Name: "fake-sa", - }, - { - APIVersion: "v1", - Kind: "PersistentVolumeClaim", - Namespace: namespace, - Name: "fake-pvc", - }, - }, - }, - } - - for i := range tests { - tt := tests[i] - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - res, _ := getDependenciesFromPodTemplate(tt.pod) - if len(res) == 0 { - res = nil - } - if !reflect.DeepEqual(res, tt.expected) { - t.Errorf("getDependenciesFromPodTemplate() = %v, want %v", res, tt.expected) - } - }) - } -} - -func Test_getServiceAccountNames(t *testing.T) { - type args struct { - pod *corev1.Pod - } - tests := []struct { - name string - args args - want sets.Set[string] - }{ - { - name: "get ServiceAccountName from pod ", - args: args{pod: &corev1.Pod{Spec: corev1.PodSpec{ServiceAccountName: "test"}}}, - want: sets.New("test"), - }, - { - name: "get default ServiceAccountName from pod ", - args: args{pod: &corev1.Pod{Spec: corev1.PodSpec{ServiceAccountName: "default"}}}, - want: sets.New[string](), - }, - } - for i := range tests { - tt := tests[i] - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - if got := getServiceAccountNames(tt.args.pod); !got.Equal(tt.want) { - t.Errorf("getServiceAccountNames() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/util/helper/pod.go b/pkg/util/helper/pod.go index 0b5a73229..843e67b39 100644 --- a/pkg/util/helper/pod.go +++ b/pkg/util/helper/pod.go @@ -2,6 +2,11 @@ package helper import ( corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + + configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" + "github.com/karmada-io/karmada/pkg/util/lifted" ) // GetPodCondition extracts the provided condition from the given status and returns that. @@ -26,3 +31,103 @@ func GetPodConditionFromList(conditions []corev1.PodCondition, conditionType cor } return -1, nil } + +// GeneratePodFromTemplateAndNamespace generate a simple pod object from the given podTemplate and namespace, then +// returns the generated pod. +func GeneratePodFromTemplateAndNamespace(template *corev1.PodTemplateSpec, namespace string) *corev1.Pod { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + } + + pod.Spec = *template.Spec.DeepCopy() + return pod +} + +// GetDependenciesFromPodTemplate extracts the dependencies from the given pod and returns that. +// returns DependentObjectReferences according to the pod, including ConfigMap, Secret, ServiceAccount and PersistentVolumeClaim. +func GetDependenciesFromPodTemplate(podObj *corev1.Pod) ([]configv1alpha1.DependentObjectReference, error) { + dependentConfigMaps := getConfigMapNames(podObj) + dependentSecrets := getSecretNames(podObj) + dependentSas := getServiceAccountNames(podObj) + dependentPVCs := getPVCNames(podObj) + var dependentObjectRefs []configv1alpha1.DependentObjectReference + for cm := range dependentConfigMaps { + dependentObjectRefs = append(dependentObjectRefs, configv1alpha1.DependentObjectReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Namespace: podObj.Namespace, + Name: cm, + }) + } + + for secret := range dependentSecrets { + dependentObjectRefs = append(dependentObjectRefs, configv1alpha1.DependentObjectReference{ + APIVersion: "v1", + Kind: "Secret", + Namespace: podObj.Namespace, + Name: secret, + }) + } + + for sa := range dependentSas { + dependentObjectRefs = append(dependentObjectRefs, configv1alpha1.DependentObjectReference{ + APIVersion: "v1", + Kind: "ServiceAccount", + Namespace: podObj.Namespace, + Name: sa, + }) + } + + for pvc := range dependentPVCs { + dependentObjectRefs = append(dependentObjectRefs, configv1alpha1.DependentObjectReference{ + APIVersion: "v1", + Kind: "PersistentVolumeClaim", + Namespace: podObj.Namespace, + Name: pvc, + }) + } + + return dependentObjectRefs, nil +} + +func getSecretNames(pod *corev1.Pod) sets.Set[string] { + result := sets.New[string]() + lifted.VisitPodSecretNames(pod, func(name string) bool { + result.Insert(name) + return true + }) + return result +} + +func getServiceAccountNames(pod *corev1.Pod) sets.Set[string] { + result := sets.New[string]() + if pod.Spec.ServiceAccountName != "" && pod.Spec.ServiceAccountName != "default" { + result.Insert(pod.Spec.ServiceAccountName) + } + return result +} + +func getConfigMapNames(pod *corev1.Pod) sets.Set[string] { + result := sets.New[string]() + lifted.VisitPodConfigmapNames(pod, func(name string) bool { + result.Insert(name) + return true + }) + return result +} + +func getPVCNames(pod *corev1.Pod) sets.Set[string] { + result := sets.New[string]() + for i := range pod.Spec.Volumes { + volume := pod.Spec.Volumes[i] + if volume.PersistentVolumeClaim != nil { + claimName := volume.PersistentVolumeClaim.ClaimName + if len(claimName) != 0 { + result.Insert(claimName) + } + } + } + return result +} diff --git a/pkg/util/helper/pod_test.go b/pkg/util/helper/pod_test.go index 6d692b30d..b38de30f3 100644 --- a/pkg/util/helper/pod_test.go +++ b/pkg/util/helper/pod_test.go @@ -5,6 +5,16 @@ import ( "testing" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + + configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" + "github.com/karmada-io/karmada/test/helper" +) + +var ( + namespace = "karmada-test" + podName = "fake-pod" ) func TestGetPodCondition(t *testing.T) { @@ -165,3 +175,579 @@ func TestGetPodConditionFromList(t *testing.T) { }) } } + +func TestGeneratePodFromTemplateAndNamespace(t *testing.T) { + pod1 := helper.NewPod(namespace, podName) + pod2 := helper.NewPod(namespace, podName) + pod2.Spec.ServiceAccountName = "fake-sa" + type args struct { + template *corev1.PodTemplateSpec + namespace string + } + tests := []struct { + name string + args args + expected corev1.Pod + }{ + { + name: "generate a simple pod from template and namespace without dependency", + args: args{ + template: &corev1.PodTemplateSpec{Spec: pod1.Spec}, + namespace: "test1", + }, + expected: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test1"}, + Spec: *pod1.Spec.DeepCopy(), + }, + }, + { + name: "generate a simple pod from template and namespace", + args: args{ + template: &corev1.PodTemplateSpec{Spec: pod2.Spec}, + namespace: "test2", + }, + expected: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test2"}, + Spec: *pod2.Spec.DeepCopy(), + }, + }, + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := GeneratePodFromTemplateAndNamespace(tt.args.template, tt.args.namespace); !reflect.DeepEqual(*got, tt.expected) { + t.Errorf("GeneratePodFromTemplateAndNamespace() = %v,\n want %v", got, tt.expected) + } + }) + } +} + +func TestGetSecretNames(t *testing.T) { + emptySecretsPod := helper.NewPod(namespace, podName) + + secretsPod := helper.NewPod(namespace, podName) + secretsPod.Spec.ImagePullSecrets = []corev1.LocalObjectReference{ + { + Name: "image-secret", + }, + } + secretsPod.Spec.InitContainers = []corev1.Container{ + { + EnvFrom: []corev1.EnvFromSource{ + { + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "initcontainer-envfrom-secret", + }, + }, + }, + }, + Env: []corev1.EnvVar{ + { + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "initcontainer-env-secret", + }, + }, + }, + }, + }, + }, + } + secretsPod.Spec.Containers = []corev1.Container{ + { + EnvFrom: []corev1.EnvFromSource{ + { + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "container-envfrom-secret", + }, + }, + }, + }, + Env: []corev1.EnvVar{ + { + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "container-env-secret", + }, + }, + }, + }, + }, + }, + } + secretsPod.Spec.EphemeralContainers = []corev1.EphemeralContainer{ + { + EphemeralContainerCommon: corev1.EphemeralContainerCommon{ + EnvFrom: []corev1.EnvFromSource{ + { + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "ephemeralcontainer-envfrom-secret", + }, + }, + }, + }, + Env: []corev1.EnvVar{ + { + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "ephemeralcontainer-env-secret", + }, + }, + }, + }, + }, + }, + }, + } + secretsPod.Spec.Volumes = []corev1.Volume{ + { + VolumeSource: corev1.VolumeSource{ + AzureFile: &corev1.AzureFileVolumeSource{ + SecretName: "azure-secret", + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + CephFS: &corev1.CephFSVolumeSource{ + SecretRef: &corev1.LocalObjectReference{ + Name: "cephfs-secret", + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + Cinder: &corev1.CinderVolumeSource{ + SecretRef: &corev1.LocalObjectReference{ + Name: "cinder-secret", + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + FlexVolume: &corev1.FlexVolumeSource{ + SecretRef: &corev1.LocalObjectReference{ + Name: "flex-secret", + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "projected-secret", + }, + }, + }, + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + RBD: &corev1.RBDVolumeSource{ + SecretRef: &corev1.LocalObjectReference{ + Name: "rbd-secret", + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "secret-config", + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + ScaleIO: &corev1.ScaleIOVolumeSource{ + SecretRef: &corev1.LocalObjectReference{ + Name: "scaleio-secret", + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + ISCSI: &corev1.ISCSIVolumeSource{ + SecretRef: &corev1.LocalObjectReference{ + Name: "iscsi-secret", + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + StorageOS: &corev1.StorageOSVolumeSource{ + SecretRef: &corev1.LocalObjectReference{ + Name: "storageos-secret", + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + CSI: &corev1.CSIVolumeSource{ + NodePublishSecretRef: &corev1.LocalObjectReference{ + Name: "csi-secret", + }, + }, + }, + }, + } + + tests := []struct { + name string + pod *corev1.Pod + expected sets.Set[string] + }{ + { + name: "get secret names from pod without secrets", + pod: emptySecretsPod, + expected: make(sets.Set[string], 0), + }, + { + name: "get secret names from pod with secrets", + pod: secretsPod, + expected: sets.New("image-secret", "initcontainer-envfrom-secret", "initcontainer-env-secret", "container-envfrom-secret", "container-env-secret", + "ephemeralcontainer-envfrom-secret", "ephemeralcontainer-env-secret", "azure-secret", "cephfs-secret", "cinder-secret", "flex-secret", "projected-secret", + "rbd-secret", "secret-config", "scaleio-secret", "iscsi-secret", "storageos-secret", "csi-secret"), + }, + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + res := getSecretNames(tt.pod) + if !res.Equal(tt.expected) { + t.Errorf("getSecretNames() = %v, want %v", res, tt.expected) + } + }) + } +} + +func TestGetConfigMapNames(t *testing.T) { + emptyConfigMapsPod := helper.NewPod(namespace, podName) + + configMapsPod := helper.NewPod(namespace, podName) + configMapsPod.Spec.InitContainers = []corev1.Container{ + { + EnvFrom: []corev1.EnvFromSource{ + { + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "initcontainer-envfrom-configmap", + }, + }, + }, + }, + Env: []corev1.EnvVar{ + { + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "initcontainer-env-configmap", + }, + }, + }, + }, + }, + }, + } + configMapsPod.Spec.Containers = []corev1.Container{ + { + EnvFrom: []corev1.EnvFromSource{ + { + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "container-envfrom-configmap", + }, + }, + }, + }, + Env: []corev1.EnvVar{ + { + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "container-env-configmap", + }, + }, + }, + }, + }, + }, + } + configMapsPod.Spec.EphemeralContainers = []corev1.EphemeralContainer{ + { + EphemeralContainerCommon: corev1.EphemeralContainerCommon{ + EnvFrom: []corev1.EnvFromSource{ + { + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "ephemeralcontainer-envfrom-configmap", + }, + }, + }, + }, + Env: []corev1.EnvVar{ + { + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "ephemeralcontainer-env-configmap", + }, + }, + }, + }, + }, + }, + }, + } + configMapsPod.Spec.Volumes = []corev1.Volume{ + { + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + ConfigMap: &corev1.ConfigMapProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "projected-configmap", + }, + }, + }, + }, + }, + }, + }, + { + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "configmap-config", + }, + }, + }, + }, + } + + tests := []struct { + name string + pod *corev1.Pod + expected sets.Set[string] + }{ + { + name: "get configMap names from pod without configMaps", + pod: emptyConfigMapsPod, + expected: make(sets.Set[string], 0), + }, + { + name: "get configMap names from pod with configMaps", + pod: configMapsPod, + expected: sets.New("initcontainer-envfrom-configmap", "initcontainer-env-configmap", "container-envfrom-configmap", "container-env-configmap", + "ephemeralcontainer-envfrom-configmap", "ephemeralcontainer-env-configmap", "projected-configmap", "configmap-config"), + }, + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + res := getConfigMapNames(tt.pod) + if !res.Equal(tt.expected) { + t.Errorf("getConfigMapNames() = %v, want %v", res, tt.expected) + } + }) + } +} + +func TestGetPVCNames(t *testing.T) { + emptyPvcsPod := helper.NewPod(namespace, podName) + pvcsPod := helper.NewPod(namespace, podName) + pvcsPod.Spec.Volumes = []corev1.Volume{ + { + Name: "foo-name", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "fake-foo", + ReadOnly: true, + }, + }, + }, + { + Name: "bar-name", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "fake-bar", + ReadOnly: true, + }, + }, + }, + } + + tests := []struct { + name string + pod *corev1.Pod + expected sets.Set[string] + }{ + { + name: "get pvc names from pod without pvcs", + pod: emptyPvcsPod, + expected: make(sets.Set[string], 0), + }, + { + name: "get pvc names from pod with pvcs", + pod: pvcsPod, + expected: sets.New("fake-foo", "fake-bar"), + }, + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + res := getPVCNames(tt.pod) + if !res.Equal(tt.expected) { + t.Errorf("getPVCNames() = %v, want %v", res, tt.expected) + } + }) + } +} + +func TestGetDependenciesFromPodTemplate(t *testing.T) { + emptyDependenciesPod := helper.NewPod(namespace, podName) + + dependenciesPod := helper.NewPod(namespace, podName) + dependenciesPod.Spec.Volumes = []corev1.Volume{ + { + Name: "foo-name", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "fake-foo", + }, + }, + }, + }, + { + Name: "bar-name", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "fake-bar", + }, + }, + }, + { + Name: "pvc-name", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "fake-pvc", + }, + }, + }, + } + dependenciesPod.Spec.ServiceAccountName = "fake-sa" + + tests := []struct { + name string + pod *corev1.Pod + expected []configv1alpha1.DependentObjectReference + }{ + { + name: "get dependencies from PodTemplate without dependencies", + pod: emptyDependenciesPod, + expected: nil, + }, + { + name: "get dependencies from PodTemplate with dependencies", + pod: dependenciesPod, + expected: []configv1alpha1.DependentObjectReference{ + { + APIVersion: "v1", + Kind: "ConfigMap", + Namespace: namespace, + Name: "fake-foo", + }, + { + APIVersion: "v1", + Kind: "Secret", + Namespace: namespace, + Name: "fake-bar", + }, + { + APIVersion: "v1", + Kind: "ServiceAccount", + Namespace: namespace, + Name: "fake-sa", + }, + { + APIVersion: "v1", + Kind: "PersistentVolumeClaim", + Namespace: namespace, + Name: "fake-pvc", + }, + }, + }, + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + res, _ := GetDependenciesFromPodTemplate(tt.pod) + if len(res) == 0 { + res = nil + } + if !reflect.DeepEqual(res, tt.expected) { + t.Errorf("getDependenciesFromPodTemplate() = %v, want %v", res, tt.expected) + } + }) + } +} + +func Test_getServiceAccountNames(t *testing.T) { + type args struct { + pod *corev1.Pod + } + tests := []struct { + name string + args args + want sets.Set[string] + }{ + { + name: "get ServiceAccountName from pod ", + args: args{pod: &corev1.Pod{Spec: corev1.PodSpec{ServiceAccountName: "test"}}}, + want: sets.New("test"), + }, + { + name: "get default ServiceAccountName from pod ", + args: args{pod: &corev1.Pod{Spec: corev1.PodSpec{ServiceAccountName: "default"}}}, + want: sets.New[string](), + }, + } + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := getServiceAccountNames(tt.args.pod); !got.Equal(tt.want) { + t.Errorf("getServiceAccountNames() = %v, want %v", got, tt.want) + } + }) + } +}