Files
2026-05-07 14:39:58 +00:00

432 lines
12 KiB
Go

package templateagent
import (
"context"
"testing"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
"open-cluster-management.io/addon-framework/pkg/addonfactory"
addonapiv1beta1 "open-cluster-management.io/api/addon/v1beta1"
testingcommon "open-cluster-management.io/ocm/pkg/common/testing"
)
func TestNamespaceDecorator(t *testing.T) {
tests := []struct {
name string
namespace string
object *unstructured.Unstructured
validateObject func(t *testing.T, obj *unstructured.Unstructured)
}{
{
name: "no namespace set",
object: testingcommon.NewUnstructured("v1", "Pod", "default", "test"),
validateObject: func(t *testing.T, obj *unstructured.Unstructured) {
testingcommon.AssertEqualNameNamespace(t, obj.GetName(), obj.GetNamespace(), "test", "default")
},
},
{
name: "namespace set",
object: testingcommon.NewUnstructured("v1", "Pod", "default", "test"),
namespace: "newns",
validateObject: func(t *testing.T, obj *unstructured.Unstructured) {
testingcommon.AssertEqualNameNamespace(t, obj.GetName(), obj.GetNamespace(), "test", "newns")
},
},
{
name: "clusterRoleBinding",
object: func() *unstructured.Unstructured {
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ClusterRoleBinding",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Subjects: []rbacv1.Subject{
{
Name: "user1",
Namespace: "default",
},
{
Name: "user2",
Namespace: "default",
},
},
}
data, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(clusterRoleBinding)
return &unstructured.Unstructured{Object: data}
}(),
namespace: "newns",
validateObject: func(t *testing.T, obj *unstructured.Unstructured) {
binding := &rbacv1.ClusterRoleBinding{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, binding)
if err != nil {
t.Fatal(err)
}
for _, s := range binding.Subjects {
if s.Namespace != "newns" {
t.Errorf("namespace of subject is not correct, got %v", s)
}
}
},
},
{
name: "roleBinding",
object: func() *unstructured.Unstructured {
roleBinding := &rbacv1.RoleBinding{
TypeMeta: metav1.TypeMeta{
Kind: "RoleBinding",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Subjects: []rbacv1.Subject{
{
Name: "user1",
Namespace: "default",
},
{
Name: "user2",
Namespace: "default",
},
},
}
data, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(roleBinding)
return &unstructured.Unstructured{Object: data}
}(),
namespace: "newns",
validateObject: func(t *testing.T, obj *unstructured.Unstructured) {
testingcommon.AssertEqualNameNamespace(t, obj.GetName(), obj.GetNamespace(), "test", "newns")
binding := &rbacv1.RoleBinding{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, binding)
if err != nil {
t.Fatal(err)
}
for _, s := range binding.Subjects {
if s.Namespace != "newns" {
t.Errorf("namespace of subject is not correct, got %v", s)
}
}
},
},
{
name: "namespace",
object: func() *unstructured.Unstructured {
ns := &corev1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
}
data, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(ns)
return &unstructured.Unstructured{Object: data}
}(),
namespace: "newns",
validateObject: func(t *testing.T, obj *unstructured.Unstructured) {
ns := &corev1.Namespace{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ns)
if err != nil {
t.Fatal(err)
}
if ns.Name != "newns" {
t.Errorf("name of namespace is not correct, got %v", ns.Name)
}
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
values := addonfactory.Values{}
if len(tc.namespace) > 0 {
values[InstallNamespacePrivateValueKey] = tc.namespace
}
d := newNamespaceDecorator(values)
result, err := d.decorate(tc.object)
if err != nil {
t.Fatal(err)
}
tc.validateObject(t, result)
})
}
}
func TestProxyDecorator(t *testing.T) {
tests := []struct {
name string
config addonapiv1beta1.AddOnDeploymentConfig
pod *corev1.PodTemplateSpec
validateObject func(t *testing.T, pod *corev1.PodTemplateSpec)
}{
{
name: "no proxy set",
pod: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test",
Image: "test",
},
},
},
},
validateObject: func(t *testing.T, pod *corev1.PodTemplateSpec) {
for _, c := range pod.Spec.Containers {
if c.Env != nil {
for _, e := range c.Env {
if e.Name == "HTTP_PROXY" || e.Name == "HTTPS_PROXY" || e.Name == "NO_PROXY" ||
e.Name == "http_proxy" || e.Name == "https_proxy" || e.Name == "no_proxy" {
t.Errorf("proxy env is not expected, got %v", e)
}
}
}
}
},
},
{
name: "proxy set",
pod: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test",
Image: "test",
},
},
},
},
config: addonapiv1beta1.AddOnDeploymentConfig{
Spec: addonapiv1beta1.AddOnDeploymentConfigSpec{
ProxyConfig: addonapiv1beta1.ProxyConfig{
HTTPProxy: "http://proxy",
HTTPSProxy: "https://proxy",
NoProxy: "no-proxy",
},
},
},
validateObject: func(t *testing.T, pod *corev1.PodTemplateSpec) {
for _, c := range pod.Spec.Containers {
if c.Env != nil {
for _, e := range c.Env {
if e.Name == "HTTP_PROXY" || e.Name == "http_proxy" { //nolint:gocritic
if e.Value != "http://proxy" {
t.Errorf("http proxy env is not correct, got %v", e)
}
} else if e.Name == "HTTPS_PROXY" || e.Name == "https_proxy" {
if e.Value != "https://proxy" {
t.Errorf("https proxy env is not correct, got %v", e)
}
} else if e.Name == "NO_PROXY" || e.Name == "no_proxy" {
if e.Value != "no-proxy" {
t.Errorf("no proxy env is not correct, got %v", e)
}
}
}
}
}
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
values, err := ToAddOnProxyPrivateValues(tc.config)
if err != nil {
t.Fatal(err)
}
ctx := context.TODO()
logger := klog.FromContext(ctx)
d := newProxyHandler(logger, "addon1", values)
err = d.decorate("", tc.pod)
if err != nil {
t.Fatal(err)
}
tc.validateObject(t, tc.pod)
})
}
}
func TestResourceRequirementsDecorator(t *testing.T) {
tests := []struct {
name string
config addonapiv1beta1.AddOnDeploymentConfig
resourceName string
pod *corev1.PodTemplateSpec
supportResource supportResource
validateObject func(t *testing.T, pod *corev1.PodTemplateSpec)
}{
{
name: "deployment",
pod: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "c1",
Image: "test",
},
{
Name: "c2",
Image: "test",
},
},
},
},
resourceName: "d1",
supportResource: supportResourceDeployment,
config: addonapiv1beta1.AddOnDeploymentConfig{
Spec: addonapiv1beta1.AddOnDeploymentConfigSpec{
ResourceRequirements: []addonapiv1beta1.ContainerResourceRequirements{
{
ContainerID: "deployments:d1:c1",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("128Mi"),
},
},
},
},
},
},
validateObject: func(t *testing.T, pod *corev1.PodTemplateSpec) {
for _, c := range pod.Spec.Containers {
if c.Name == "c1" {
if c.Resources.Requests.Memory() == nil || c.Resources.Requests.Memory().String() != "128Mi" {
t.Errorf("memory request for c1 is not corrent, got %v", c.Resources)
}
} else {
if c.Resources.Requests.Memory() != nil && c.Resources.Requests.Memory().String() != "0" {
t.Errorf("memory request for other containers should not be set, got %v", c.Resources)
}
}
}
},
},
{
name: "daemonset",
pod: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "c1",
Image: "test",
},
{
Name: "c2",
Image: "test",
},
},
},
},
resourceName: "d1",
supportResource: supportResourceDaemonset,
config: addonapiv1beta1.AddOnDeploymentConfig{
Spec: addonapiv1beta1.AddOnDeploymentConfigSpec{
ResourceRequirements: []addonapiv1beta1.ContainerResourceRequirements{
{
ContainerID: "daemonsets:d1:c1",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("128Mi"),
},
},
},
},
},
},
validateObject: func(t *testing.T, pod *corev1.PodTemplateSpec) {
for _, c := range pod.Spec.Containers {
if c.Name == "c1" {
if c.Resources.Requests.Memory() == nil || c.Resources.Requests.Memory().String() != "128Mi" {
t.Errorf("memory request for c1 is not corrent, got %v", c.Resources)
}
} else {
if c.Resources.Requests.Memory() != nil && c.Resources.Requests.Memory().String() != "0" {
t.Errorf("memory request for other containers should not be set, got %v", c.Resources)
}
}
}
},
},
{
name: "regex match",
pod: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "c1",
Image: "test",
},
{
Name: "c2",
Image: "test",
},
},
},
},
resourceName: "d1",
supportResource: supportResourceDeployment,
config: addonapiv1beta1.AddOnDeploymentConfig{
Spec: addonapiv1beta1.AddOnDeploymentConfigSpec{
ResourceRequirements: []addonapiv1beta1.ContainerResourceRequirements{
{
ContainerID: "deployments:d1:c1",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("128Mi"),
},
},
},
{
ContainerID: "*:*:*",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("256Mi"),
},
},
},
},
},
},
validateObject: func(t *testing.T, pod *corev1.PodTemplateSpec) {
for _, c := range pod.Spec.Containers {
if c.Resources.Requests.Memory() == nil || c.Resources.Requests.Memory().String() != "256Mi" {
t.Errorf("memory request for c1 is not corrent, got %v", c.Resources)
}
}
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
values, err := ToAddOnResourceRequirementsPrivateValues(tc.config)
if err != nil {
t.Fatal(err)
}
ctx := context.TODO()
logger := klog.FromContext(ctx)
d := newResourceRequirementsDecorator(logger, tc.supportResource, values)
err = d.decorate(tc.resourceName, tc.pod)
if err != nil {
t.Fatal(err)
}
tc.validateObject(t, tc.pod)
})
}
}