Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions internal/controller/kruize_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -775,15 +775,24 @@ var _ = Describe("Kruize Controller", func() {
getContainer(kruizeDeployment, "kruize")
})

It("should generate Kruize-ui pod specification", func() {
generator := utils.NewKruizeResourceGenerator("test-namespace", "", "", constants.ClusterTypeOpenShift, &kruizev1alpha1.KruizeSpec{}, getTestContext())
It("should generate Kruize-ui deployment specification", func() {
generator := utils.NewKruizeResourceGenerator("test-namespace", "", "", constants.ClusterTypeOpenShift)

namespacedResources := generator.NamespacedResources()

// Find the Kruize UI pod
kruizeUIPod := findTypedResource[*corev1.Pod](namespacedResources, "kruize-ui-nginx-pod", "", "")
Expect(kruizeUIPod).NotTo(BeNil(), "Kruize UI pod should exist")
Expect(kruizeUIPod.Spec.Containers).NotTo(BeEmpty())

// Find the Kruize UI deployment
var kruizeUIDeployment *appsv1.Deployment
for _, resource := range namespacedResources {
if resource.GetObjectKind().GroupVersionKind().Kind == "Deployment" && resource.GetName() == "kruize-ui-nginx" {
var ok bool
kruizeUIDeployment, ok = resource.(*appsv1.Deployment)
Expect(ok).To(BeTrue(), "Resource should be a valid Deployment")
break
}
}

Expect(kruizeUIDeployment).NotTo(BeNil(), "Kruize UI deployment should exist")
Expect(kruizeUIDeployment.Spec.Template.Spec.Containers).NotTo(BeEmpty())
})

It("should generate Kruize-db pod specification", func() {
Expand Down Expand Up @@ -1258,17 +1267,17 @@ var _ = Describe("Kruize Controller", func() {
Expect(kruizeContainer).NotTo(BeNil(), "Kruize container should exist in deployment")
Expect(kruizeContainer.Image).To(Equal(constants.GetDefaultAutotuneImage()),
"Kruize deployment should use default Autotune image")

// Find and verify UI pod using helper
kruizeUIPod := findTypedResource[*corev1.Pod](namespacedResources, "kruize-ui-nginx-pod", "app", "kruize-ui-nginx")
Expect(kruizeUIPod).NotTo(BeNil(), "Kruize UI pod should be generated")
Expect(kruizeUIPod.Spec.Containers).NotTo(BeEmpty())
// Find and verify UI deployment using helper
kruizeUIDeployment := findTypedResource[*appsv1.Deployment](namespacedResources, "kruize-ui-nginx", "app", "kruize-ui-nginx")
Expect(kruizeUIDeployment).NotTo(BeNil(), "Kruize UI deployment should be generated")
Expect(kruizeUIDeployment.Spec.Template.Spec.Containers).NotTo(BeEmpty())

// Find the kruize-ui-nginx container by name using helper
uiContainer := findContainerByName(kruizeUIPod.Spec.Containers, "kruize-ui-nginx-container")
Expect(uiContainer).NotTo(BeNil(), "Kruize UI container should exist in pod")
uiContainer := findContainerByName(kruizeUIDeployment.Spec.Template.Spec.Containers, "kruize-ui-nginx-container")
Expect(uiContainer).NotTo(BeNil(), "Kruize UI container should exist in deployment")
Expect(uiContainer.Image).To(Equal(constants.GetDefaultUIImage()),
"Kruize UI pod should use default UI image")
"Kruize UI deployment should use default UI image")
})

It("should use custom images when specified", func() {
Expand Down
179 changes: 99 additions & 80 deletions internal/utils/kruize_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ func (g *KruizeResourceGenerator) NamespacedResources() []client.Object {
g.kruizeServiceMonitor(),
g.nginxConfigMap(),
g.kruizeUINginxService(),
g.kruizeUINginxPod(),
g.kruizeUINginxDeployment(),
g.deletePartitionCronJob(),
}

Expand Down Expand Up @@ -1049,81 +1049,93 @@ func (g *KruizeResourceGenerator) kruizeService() *corev1.Service {
}
}

func (g *KruizeResourceGenerator) kruizeUINginxPod() *corev1.Pod {
return &corev1.Pod{

func (g *KruizeResourceGenerator) kruizeUINginxDeployment() *appsv1.Deployment {
replicas := int32(1)

// Build pod security context based on cluster type
podSecurityContext := &corev1.PodSecurityContext{
RunAsNonRoot: boolPtr(true),
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
}

// Only set RunAsUser for non-OpenShift clusters
// OpenShift SCC will reject hardcoded UIDs and assign its own
if g.ClusterType != constants.ClusterTypeOpenShift {
podSecurityContext.RunAsUser = int64Ptr(101)
}

return &appsv1.Deployment{
// The TypeMeta tells the client which kind of object this is.
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
APIVersion: "apps/v1",
Kind: "Deployment",
},
// The ObjectMeta contains the name, namespace, and labels.
ObjectMeta: metav1.ObjectMeta{
Name: "kruize-ui-nginx-pod",
Namespace: g.Namespace, // We use the namespace from our generator struct.
Name: "kruize-ui-nginx",
Namespace: g.Namespace,
Labels: map[string]string{
"app": "kruize-ui-nginx",
},
},
// The Spec defines the desired state of the Pod.
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "kruize-ui-nginx-container",
Image: g.Autotune_ui_image,
ImagePullPolicy: corev1.PullAlways,
Env: []corev1.EnvVar{
{Name: "KRUIZE_API_URL", Value: "http://kruize:8080"},
{Name: "REACT_APP_KRUIZE_API_URL", Value: "http://kruize:8080"},
{Name: "KRUIZE_UI_API_URL", Value: "http://kruize:8080"},
{Name: "API_URL", Value: "http://kruize:8080"},
{Name: "KRUIZE_UI_ENV", Value: "production"},
// The Spec defines the desired state of the Deployment.
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "kruize-ui-nginx",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "kruize-ui-nginx",
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "nginx-config-volume",
MountPath: "/etc/nginx/nginx.conf",
SubPath: "nginx.conf",
},
},
Spec: corev1.PodSpec{
SecurityContext: podSecurityContext,
Containers: []corev1.Container{
{
Name: "nginx-cache",
MountPath: "/var/cache/nginx",
},
{Name: "nginx-pid", MountPath: "/var/run"},
{Name: "nginx-tmp", MountPath: "/tmp"},
},
SecurityContext: &corev1.SecurityContext{
AllowPrivilegeEscalation: boolPtr(false),
RunAsNonRoot: boolPtr(true),
RunAsUser: int64Ptr(101),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
Name: "kruize-ui-nginx-container",
Image: g.Autotune_ui_image,
ImagePullPolicy: corev1.PullAlways,
Env: []corev1.EnvVar{
{Name: "KRUIZE_UI_ENV", Value: "production"},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "nginx-config-volume",
MountPath: "/etc/nginx/nginx.conf",
SubPath: "nginx.conf",
},
},
SecurityContext: &corev1.SecurityContext{
AllowPrivilegeEscalation: boolPtr(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
},
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "nginx-config-volume",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "nginx-config",
Volumes: []corev1.Volume{
{
Name: "nginx-config-volume",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "nginx-config",
},
},
},
},
},
},
// Define the emptyDir volume that will be used for caching.
{
Name: "nginx-cache",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
{Name: "nginx-pid", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
{Name: "nginx-tmp", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
},
},
}
Expand All @@ -1133,27 +1145,34 @@ func (g *KruizeResourceGenerator) kruizeUINginxPod() *corev1.Pod {
func (g *KruizeResourceGenerator) nginxConfigMap() *corev1.ConfigMap {
nginxConf := `
events {}
pid /tmp/nginx.pid;
http {
upstream kruize-api {
server kruize:8080;
}

server {
listen 8080;
server_name localhost;

root /usr/share/nginx/html;

location ^~ /api/ {
rewrite ^/api(.*)$ $1 break;
proxy_pass http://kruize-api;
}

location / {
index index.html;
error_page 404 =200 /index.html;
}
}
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;

upstream kruize-api {
server kruize:8080;
}

server {
listen 8080;
server_name localhost;

root /usr/share/nginx/html;

location ^~ /api/ {
rewrite ^/api(.*)$ $1 break;
proxy_pass http://kruize-api;
}

location / {
index index.html;
error_page 404 =200 /index.html;
}
}
}
`
return &corev1.ConfigMap{
Expand Down Expand Up @@ -1869,7 +1888,7 @@ func (g *KruizeResourceGenerator) KubernetesNamespacedResources() []client.Objec
g.kruizeServiceMonitor(),
g.nginxConfigMap(),
g.kruizeUINginxService(),
g.kruizeUINginxPod(),
g.kruizeUINginxDeployment(),
g.deletePartitionCronJob(),
}
}
33 changes: 33 additions & 0 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,20 @@ var _ = Describe("controller", Ordered, func() {
return nil
}, 3*time.Minute, 10*time.Second).Should(Succeed())

By("checking that Kruize UI deployment is ready")
Eventually(func() error {
cmd := exec.Command("kubectl", "get", "deployment", "kruize-ui-nginx", "-n", namespace, "-o", "jsonpath={.status.readyReplicas}")
output, err := utils.Run(cmd)
if err != nil {
return err
}
if string(output) != "1" {
return fmt.Errorf("UI deployment not ready")
}
return nil
}, 3*time.Minute, 10*time.Second).Should(Succeed())


By("verifying deployed Kruize image")
cmd = exec.Command("kubectl", "get", "deployment", "kruize", "-n", namespace, "-o", "jsonpath={.spec.template.spec.containers[0].image}")
output, err := utils.Run(cmd)
Expand All @@ -292,6 +306,25 @@ var _ = Describe("controller", Ordered, func() {
fmt.Fprintf(GinkgoWriter, "Using default Kruize image from CR: %s\n", deployedImage)
}

By("verifying deployed Kruize UI image")
cmd = exec.Command("kubectl", "get", "deployment", "kruize-ui-nginx", "-n", namespace, "-o", "jsonpath={.spec.template.spec.containers[0].image}")
output, err = utils.Run(cmd)
ExpectWithOffset(1, err).NotTo(HaveOccurred())
deployedUIImage := strings.TrimSpace(string(output))
fmt.Fprintf(GinkgoWriter, "Deployed Kruize UI image: %s\n", deployedUIImage)

// If custom Kruize UI image was specified, verify it matches and fail on mismatch
if kruizeUIImage != "" {
fmt.Fprintf(GinkgoWriter, "Validating deployed UI image against KRUIZE_UI_IMAGE: %s\n", kruizeUIImage)
ExpectWithOffset(1, deployedUIImage).To(
Equal(kruizeUIImage),
fmt.Sprintf("Deployed UI image %s does not match KRUIZE_UI_IMAGE %s", deployedUIImage, kruizeUIImage),
)
fmt.Fprintf(GinkgoWriter, "✓ Deployed UI image matches specified KRUIZE_UI_IMAGE: %s\n", kruizeUIImage)
} else {
fmt.Fprintf(GinkgoWriter, "Using default Kruize UI image from CR: %s\n", deployedUIImage)
}

By("verifying Kruize API is responding")
Eventually(func() error {
cmd := exec.Command("kubectl", "exec", "deployment/kruize", "-n", namespace, "--", "curl", "-s", "localhost:8080/health")
Expand Down
Loading