Add a function getPodDependencies for InterpretDependency operation

Signed-off-by: yike21 <yike21@qq.com>
This commit is contained in:
yike21 2023-03-29 16:58:51 +08:00
parent c62cc2b055
commit 9acd98faec
6 changed files with 779 additions and 641 deletions

View File

@ -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 ""
}
}

View File

@ -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,
},
}

View File

@ -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)
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}