Skip to content

Commit 4eaab96

Browse files
Merge pull request #301 from lunarwhite/e2e-solver-flags
CM-607: Add e2es for customizing acme solver pods resources via controller flags
2 parents 71bfc87 + 5917fcf commit 4eaab96

3 files changed

Lines changed: 112 additions & 14 deletions

File tree

test/e2e/certificates_test.go

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
corev1 "k8s.io/api/core/v1"
1414
networkingv1 "k8s.io/api/networking/v1"
1515
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
16+
k8sresource "k8s.io/apimachinery/pkg/api/resource"
1617
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1718
"k8s.io/apimachinery/pkg/util/wait"
1819

@@ -30,6 +31,12 @@ const (
3031
// letsEncryptStagingServerURL is the address for the Let's Encrypt staging environment server.
3132
letsEncryptStagingServerURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
3233

34+
// acmeSolverPodLabel is the label that cert-manager uses to identify ACME solver pods.
35+
acmeSolverPodLabel = "acme.cert-manager.io/http01-solver"
36+
37+
// acmeSolverContainerName is the name of the container in the ACME solver pod.
38+
acmeSolverContainerName = "acmesolver"
39+
3340
// TARGET_PLATFORM is the environment variable for IBM Cloud CIS test.
3441
targetPlatformEnvironmentVar = "TARGET_PLATFORM"
3542

@@ -693,11 +700,15 @@ var _ = Describe("ACME Certificate", Ordered, func() {
693700
})
694701

695702
Context("http-01 challenge using ingress", func() {
696-
It("should obtain a valid LetsEncrypt certificate", func() {
703+
var ingressHost string
704+
var secretName string
697705

698-
By("creating a cluster issuer")
706+
BeforeEach(func() {
699707
clusterIssuerName := "letsencrypt-http01"
700708
ingressClassName := "openshift-default"
709+
secretName = "ingress-http01-secret"
710+
711+
By("creating a cluster issuer")
701712
clusterIssuer := &certmanagerv1.ClusterIssuer{
702713
ObjectMeta: metav1.ObjectMeta{
703714
Name: clusterIssuerName,
@@ -715,7 +726,7 @@ var _ = Describe("ACME Certificate", Ordered, func() {
715726
{
716727
HTTP01: &acmev1.ACMEChallengeSolverHTTP01{
717728
Ingress: &acmev1.ACMEChallengeSolverHTTP01Ingress{
718-
Class: &ingressClassName,
729+
IngressClassName: &ingressClassName,
719730
},
720731
},
721732
},
@@ -726,20 +737,16 @@ var _ = Describe("ACME Certificate", Ordered, func() {
726737
}
727738
_, err := certmanagerClient.CertmanagerV1().ClusterIssuers().Create(ctx, clusterIssuer, metav1.CreateOptions{})
728739
Expect(err).NotTo(HaveOccurred())
729-
defer certmanagerClient.CertmanagerV1().ClusterIssuers().Delete(ctx, clusterIssuerName, metav1.DeleteOptions{})
730740

731741
By("creating an hello-openshift deployment")
732742
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "acme", "deployment.yaml"), ns.Name)
733-
defer loader.DeleteFromFile(testassets.ReadFile, filepath.Join("testdata", "acme", "deployment.yaml"), ns.Name)
734743

735744
By("creating a service for the deployment hello-openshift")
736745
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "acme", "service.yaml"), ns.Name)
737-
defer loader.DeleteFromFile(testassets.ReadFile, filepath.Join("testdata", "acme", "service.yaml"), ns.Name)
738746

739747
By("creating an Ingress object")
740-
ingressHost := fmt.Sprintf("ahi-%s.%s", randomStr(3), appsDomain) // acronym for "ACME http-01 Ingress"
748+
ingressHost = fmt.Sprintf("ahi-%s.%s", randomStr(3), appsDomain) // acronym for "ACME http-01 Ingress"
741749
pathType := networkingv1.PathTypePrefix
742-
secretName := "ingress-http01-secret"
743750
ingress := &networkingv1.Ingress{
744751
TypeMeta: metav1.TypeMeta{
745752
APIVersion: "networking.k8s.io/v1",
@@ -749,11 +756,11 @@ var _ = Describe("ACME Certificate", Ordered, func() {
749756
Name: "ingress-http01",
750757
Namespace: ns.Name,
751758
Annotations: map[string]string{
752-
"cert-manager.io/cluster-issuer": clusterIssuerName,
753-
"acme.cert-manager.io/http01-ingress-class": ingressClassName,
759+
"cert-manager.io/cluster-issuer": clusterIssuerName,
754760
},
755761
},
756762
Spec: networkingv1.IngressSpec{
763+
IngressClassName: &ingressClassName,
757764
Rules: []networkingv1.IngressRule{
758765
{
759766
Host: ingressHost,
@@ -776,13 +783,18 @@ var _ = Describe("ACME Certificate", Ordered, func() {
776783
}},
777784
},
778785
}
779-
ingress, err = loader.KubeClient.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
786+
ingress, err = loader.KubeClient.NetworkingV1().Ingresses(ns.Name).Create(ctx, ingress, metav1.CreateOptions{})
780787
Expect(err).NotTo(HaveOccurred())
781-
defer loader.KubeClient.NetworkingV1().Ingresses(ingress.Namespace).Delete(ctx, ingress.Name, metav1.DeleteOptions{})
782788

789+
DeferCleanup(func() {
790+
certmanagerClient.CertmanagerV1().ClusterIssuers().Delete(ctx, clusterIssuerName, metav1.DeleteOptions{})
791+
})
792+
})
793+
794+
It("should obtain a valid LetsEncrypt certificate", func() {
783795
By("checking TLS certificate contents")
784-
err = wait.PollUntilContextTimeout(context.TODO(), slowPollInterval, highTimeout, true, func(context.Context) (bool, error) {
785-
secret, err := loader.KubeClient.CoreV1().Secrets(ingress.Namespace).Get(ctx, secretName, metav1.GetOptions{})
796+
err := wait.PollUntilContextTimeout(context.TODO(), slowPollInterval, highTimeout, true, func(context.Context) (bool, error) {
797+
secret, err := loader.KubeClient.CoreV1().Secrets(ns.Name).Get(ctx, secretName, metav1.GetOptions{})
786798
tlsConfig, isvalid := library.GetTLSConfig(secret)
787799
if !isvalid {
788800
return false, nil
@@ -800,6 +812,48 @@ var _ = Describe("ACME Certificate", Ordered, func() {
800812
})
801813
Expect(err).NotTo(HaveOccurred())
802814
})
815+
816+
It("should create HTTP01 solver pods with custom resource limits and requests", func() {
817+
By("monitoring for ACME HTTP01 solver pods with expected resource configuration")
818+
// These values match what's configured in BeforeAll
819+
expectedResources := corev1.ResourceRequirements{
820+
Limits: corev1.ResourceList{
821+
corev1.ResourceCPU: k8sresource.MustParse("150m"),
822+
corev1.ResourceMemory: k8sresource.MustParse("200Mi"),
823+
},
824+
Requests: corev1.ResourceList{
825+
corev1.ResourceCPU: k8sresource.MustParse("100m"),
826+
corev1.ResourceMemory: k8sresource.MustParse("100Mi"),
827+
},
828+
}
829+
830+
err := wait.PollUntilContextTimeout(ctx, fastPollInterval, lowTimeout, true, func(ctx context.Context) (bool, error) {
831+
pods, err := k8sClientSet.CoreV1().Pods("").List(ctx, metav1.ListOptions{
832+
LabelSelector: acmeSolverPodLabel,
833+
})
834+
if err != nil {
835+
return false, nil // Retry on transient errors
836+
}
837+
838+
if len(pods.Items) == 0 {
839+
return false, nil // Keep waiting for pods to appear
840+
}
841+
842+
// Check if any pod matches the expected resource configuration
843+
for _, pod := range pods.Items {
844+
if err := VerifyContainerResources(pod, acmeSolverContainerName, expectedResources); err == nil {
845+
return true, nil
846+
}
847+
}
848+
849+
return false, nil // No matching pods yet, keep waiting
850+
})
851+
Expect(err).NotTo(HaveOccurred(), "should find ACME HTTP01 solver pods with expected resource configuration")
852+
853+
By("waiting for certificate to get ready")
854+
err = waitForCertificateReadiness(ctx, secretName, ns.Name)
855+
Expect(err).NotTo(HaveOccurred())
856+
})
803857
})
804858
})
805859

test/e2e/suite_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ func TestAll(t *testing.T) {
7777
reportConfig.JUnitReport = filepath.Join(testDir, "junit.xml")
7878
reportConfig.NoColor = true
7979
reportConfig.VeryVerbose = true
80+
reportConfig.ShowNodeEvents = true
81+
reportConfig.FullTrace = true
82+
reportConfig.SilenceSkips = true
8083

8184
RunSpecs(t, "Cert Manager Suite", suiteConfig, reportConfig)
8285
}

test/e2e/utils_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,3 +764,44 @@ func pollTillDeploymentAvailable(ctx context.Context, clientSet *kubernetes.Clie
764764

765765
return err
766766
}
767+
768+
// VerifyContainerResources verifies that a container in a pod has resources matching the expected configuration.
769+
// If containerName is empty, it verifies the first container.
770+
func VerifyContainerResources(pod corev1.Pod, containerName string, expectedResources corev1.ResourceRequirements) error {
771+
var targetContainer *corev1.Container
772+
773+
if containerName == "" {
774+
// Default to first container when no specific container name is provided
775+
targetContainer = &pod.Spec.Containers[0]
776+
} else {
777+
// Find the specific container by name
778+
for i := range pod.Spec.Containers {
779+
if pod.Spec.Containers[i].Name == containerName {
780+
targetContainer = &pod.Spec.Containers[i]
781+
break
782+
}
783+
}
784+
if targetContainer == nil {
785+
return fmt.Errorf("container '%s' not found in pod '%s'", containerName, pod.Name)
786+
}
787+
}
788+
789+
// Verify limits
790+
if expectedResources.Limits != nil {
791+
for resourceType, expectedValue := range expectedResources.Limits {
792+
if actualValue := targetContainer.Resources.Limits[resourceType]; !actualValue.Equal(expectedValue) {
793+
return fmt.Errorf("%s limit for container '%s' in pod '%s' is '%v' but expected '%v'", resourceType, targetContainer.Name, pod.Name, actualValue, expectedValue)
794+
}
795+
}
796+
}
797+
798+
// Verify requests
799+
if expectedResources.Requests != nil {
800+
for resourceType, expectedValue := range expectedResources.Requests {
801+
if actualValue := targetContainer.Resources.Requests[resourceType]; !actualValue.Equal(expectedValue) {
802+
return fmt.Errorf("%s request for container '%s' in pod '%s' is '%v' but expected '%v'", resourceType, targetContainer.Name, pod.Name, actualValue, expectedValue)
803+
}
804+
}
805+
}
806+
return nil
807+
}

0 commit comments

Comments
 (0)