From 212ca4be997373b7177d2a2071c3c2341dca89a5 Mon Sep 17 00:00:00 2001 From: panxiao81 Date: Thu, 10 Sep 2026 18:15:50 +0000 Subject: [PATCH] test: verify Instance readiness end to end --- docs/development.md | 7 +- test/e2e/e2e_test.go | 87 ++++++++++++++++- test/e2e/fixtures/instance-dependencies.yaml | 98 ++++++++++++++++++++ 3 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 test/e2e/fixtures/instance-dependencies.yaml diff --git a/docs/development.md b/docs/development.md index cd02f0b..2fb5eda 100644 --- a/docs/development.md +++ b/docs/development.md @@ -171,9 +171,10 @@ E2E fixture 必须把测试 PostgreSQL、OpenBao 和 ESO 部署进 Kind,并等 -> 删除 Kind ``` -当前 controller 只实现 PostgreSQL registry adapter,尚未实现完整 PostgreSQL/OpenBao -provisioning;现有 E2E 只能验证 CRD、manager Deployment 和 metrics endpoint。上述真实依赖 fixture 应与第一个完整 -reconcile 纵向切片一起实现,不能在文档中声称已经通过。 +当前 controller 已实现第一条 Instance Ready 纵向链路:E2E fixture 在 Kind 内启动 +PostgreSQL/OpenBao,配置 Kubernetes auth,验证管理凭据读取、PostgreSQL 登录、registry +migration 和 Instance Ready。Tenant provisioning、ESO、TLS DNS/IP SAN 与删除路径仍需 +后续纵向切片覆盖,不能从 Instance Ready 推断这些合同已经通过。 ## 测试数据与泄漏检查 diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index b0d359d..27a46e0 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -36,6 +36,8 @@ import ( // namespace where the project is deployed in const namespace = "postgresql-tenant-operator-system" +const dependencyNamespace = "postgresql-tenant-operator-e2e" + // serviceAccountName created for the project const serviceAccountName = "postgresql-tenant-operator-controller-manager" @@ -52,9 +54,19 @@ var _ = Describe("Manager", Ordered, func() { // enforce the restricted security policy to the namespace, installing CRDs, // and deploying the controller. BeforeAll(func() { - By("creating manager namespace") - cmd := exec.Command("kubectl", "create", "ns", namespace) + By("deploying disposable PostgreSQL and OpenBao dependencies") + cmd := exec.Command("kubectl", "apply", "-f", "test/e2e/fixtures/instance-dependencies.yaml") _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to deploy Instance dependencies") + cmd = exec.Command("kubectl", "wait", "deployment/postgres", "deployment/openbao", "--for=condition=Available", + "-n", dependencyNamespace, "--timeout=3m") + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Instance dependencies did not become available") + configureOpenBao() + + By("creating manager namespace") + cmd = exec.Command("kubectl", "create", "ns", namespace) + _, err = utils.Run(cmd) Expect(err).NotTo(HaveOccurred(), "Failed to create namespace") By("labeling the namespace to enforce the restricted security policy") @@ -72,6 +84,13 @@ var _ = Describe("Manager", Ordered, func() { cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", managerImage)) _, err = utils.Run(cmd) Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager") + + By("configuring the controller for disposable dependencies") + argsPatch := `{"spec":{"template":{"spec":{"containers":[{"name":"manager","args":["--leader-elect","--health-probe-bind-address=:8081","--openbao-address=http://openbao.postgresql-tenant-operator-e2e.svc:8200","--openbao-auth-role=postgresql-tenant-operator","--openbao-kv-mount=secret","--external-secret-store-name=openbao"]}]}}}}` + cmd = exec.Command("kubectl", "patch", "deployment", "postgresql-tenant-operator-controller-manager", + "-n", namespace, "--type=strategic", "-p", argsPatch) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to configure controller dependency flags") }) // After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs, @@ -92,6 +111,10 @@ var _ = Describe("Manager", Ordered, func() { By("removing manager namespace") cmd = exec.Command("kubectl", "delete", "ns", namespace) _, _ = utils.Run(cmd) + + By("removing disposable Instance dependencies") + cmd = exec.Command("kubectl", "delete", "-f", "test/e2e/fixtures/instance-dependencies.yaml", "--ignore-not-found") + _, _ = utils.Run(cmd) }) // After each test, check for failures and collect logs, events, @@ -268,6 +291,47 @@ var _ = Describe("Manager", Ordered, func() { Eventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed()) }) + It("should make an Instance Ready after validating OpenBao and initializing PostgreSQL", func() { + serviceIP, err := utils.Run(exec.Command("kubectl", "get", "service", "postgres", "-n", dependencyNamespace, + "-o", "jsonpath={.spec.clusterIP}")) + Expect(err).NotTo(HaveOccurred()) + manifest := fmt.Sprintf(`apiVersion: database.ddupan.top/v1alpha1 +kind: PostgreSQLInstance +metadata: + name: e2e +spec: + endpoint: + host: postgres.%s.svc + hostaddr: %s + port: 5432 + database: postgres + sslMode: disable + adminCredentialRef: + path: infrastructure/postgresql/admin +`, dependencyNamespace, serviceIP) + manifestPath := filepath.Join(GinkgoT().TempDir(), "instance.yaml") + Expect(os.WriteFile(manifestPath, []byte(manifest), 0o600)).To(Succeed()) + _, err = utils.Run(exec.Command("kubectl", "apply", "-f", manifestPath)) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func(g Gomega) { + phase, err := utils.Run(exec.Command("kubectl", "get", "postgresqlinstance", "e2e", + "-o", "jsonpath={.status.phase}")) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(phase).To(Equal("Ready")) + ready, err := utils.Run(exec.Command("kubectl", "get", "postgresqlinstance", "e2e", + "-o", `jsonpath={.status.conditions[?(@.type=="Ready")].status}`)) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(ready).To(Equal("True")) + }).Should(Succeed()) + + output, err := utils.Run(exec.Command("kubectl", "exec", "deployment/postgres", "-n", dependencyNamespace, + "--", "psql", "-U", "postgres", "-d", "postgres", "-Atc", + "SELECT to_regclass('postgresql_tenant_operator.tenant_ownership') IS NOT NULL")) + Expect(err).NotTo(HaveOccurred()) + Expect(output).To(Equal("t\n")) + }) + // +kubebuilder:scaffold:e2e-webhooks-checks // TODO: Customize the e2e test suite with scenarios specific to your project. @@ -282,6 +346,25 @@ var _ = Describe("Manager", Ordered, func() { }) }) +func configureOpenBao() { + By("configuring OpenBao Kubernetes auth and the PostgreSQL administrative credential") + script := `set -eu +bao auth enable kubernetes +bao write auth/kubernetes/config kubernetes_host=https://kubernetes.default.svc \ + token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ + kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt +printf 'path "secret/data/infrastructure/postgresql/admin" { capabilities = ["read"] }\n' | bao policy write postgresql-tenant-operator - +bao write auth/kubernetes/role/postgresql-tenant-operator \ + bound_service_account_names=postgresql-tenant-operator-controller-manager \ + bound_service_account_namespaces=postgresql-tenant-operator-system \ + policies=postgresql-tenant-operator ttl=5m +bao kv put secret/infrastructure/postgresql/admin username=postgres password=postgres-e2e-only +` + cmd := exec.Command("kubectl", "exec", "deployment/openbao", "-n", dependencyNamespace, "--", "sh", "-ec", script) + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to configure OpenBao") +} + // serviceAccountToken returns a token for the specified service account in the given namespace. // It uses the Kubernetes TokenRequest API to generate a token by directly sending a request // and parsing the resulting token from the API response. diff --git a/test/e2e/fixtures/instance-dependencies.yaml b/test/e2e/fixtures/instance-dependencies.yaml new file mode 100644 index 0000000..88a2fad --- /dev/null +++ b/test/e2e/fixtures/instance-dependencies.yaml @@ -0,0 +1,98 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: postgresql-tenant-operator-e2e +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: openbao + namespace: postgresql-tenant-operator-e2e +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: postgresql-tenant-operator-e2e-openbao-token-reviewer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:auth-delegator +subjects: + - kind: ServiceAccount + name: openbao + namespace: postgresql-tenant-operator-e2e +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: postgresql-tenant-operator-e2e +spec: + replicas: 1 + selector: + matchLabels: {app: postgres} + template: + metadata: + labels: {app: postgres} + spec: + containers: + - name: postgres + image: postgres:17-alpine + env: + - {name: POSTGRES_USER, value: postgres} + - {name: POSTGRES_PASSWORD, value: postgres-e2e-only} + ports: + - {name: postgresql, containerPort: 5432} + readinessProbe: + exec: {command: [pg_isready, -U, postgres]} + initialDelaySeconds: 2 + periodSeconds: 2 +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: postgresql-tenant-operator-e2e +spec: + selector: {app: postgres} + ports: + - {name: postgresql, port: 5432, targetPort: postgresql} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: openbao + namespace: postgresql-tenant-operator-e2e +spec: + replicas: 1 + selector: + matchLabels: {app: openbao} + template: + metadata: + labels: {app: openbao} + spec: + serviceAccountName: openbao + containers: + - name: openbao + image: openbao/openbao:2.6.1 + args: [server, -dev] + env: + - {name: BAO_DEV_LISTEN_ADDRESS, value: "0.0.0.0:8200"} + - {name: BAO_DEV_ROOT_TOKEN_ID, value: dev-only-root-token} + - {name: BAO_ADDR, value: "http://127.0.0.1:8200"} + ports: + - {name: http, containerPort: 8200} + readinessProbe: + exec: {command: [bao, status]} + initialDelaySeconds: 2 + periodSeconds: 2 +--- +apiVersion: v1 +kind: Service +metadata: + name: openbao + namespace: postgresql-tenant-operator-e2e +spec: + selector: {app: openbao} + ports: + - {name: http, port: 8200, targetPort: http}