Files
ayatori/internal/database/adapter/postgresql/provisioning_watch_integration_test.go
T
panxiao81 e2016d3727
Verify / test (pull_request) Successful in 9m19s
Verify / lint (pull_request) Successful in 10m19s
Verify / database-integration (pull_request) Successful in 12m12s
feat: 接通 PostgreSQL 角色与数据库创建闭环
2026-09-29 16:04:52 +00:00

98 lines
3.6 KiB
Go

//go:build integration
package postgresql_test
import (
"context"
"sync/atomic"
"testing"
"time"
"git.ddupan.top/panxiao81/ayatori/internal/database/adapter/kubernetes"
"git.ddupan.top/panxiao81/ayatori/internal/database/application"
databasecontroller "git.ddupan.top/panxiao81/ayatori/internal/database/controller"
"git.ddupan.top/panxiao81/ayatori/internal/database/domain/instance"
"git.ddupan.top/panxiao81/ayatori/internal/database/domain/provisioning"
"k8s.io/apimachinery/pkg/api/meta"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
controllerconfig "sigs.k8s.io/controller-runtime/pkg/config"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
)
func testProvisioningManager(t *testing.T, f *provisioningFixture) {
t.Helper()
database := f.bound(t, "watch_supply")
// 首次 list/watch 自动供应;第二个新 manager 只观察确认记录,不重新建库。
var roleOID, databaseOID int64
for range 2 {
skipRepeatedName := true
manager, err := ctrl.NewManager(f.config, ctrl.Options{Scheme: f.scheme, Metrics: metricsserver.Options{BindAddress: "0"}, HealthProbeBindAddress: "0",
Controller: controllerconfig.Controller{SkipNameValidation: &skipRepeatedName}})
if err != nil {
t.Fatal(err)
}
resources := &kubernetes.ProvisioningResources{Client: manager.GetClient(), Reader: manager.GetAPIReader()}
backend := &countProvisioningObservations{ProvisioningBackend: f.service}
service := &application.DatabaseReconciliation{
Credentials: &application.CredentialPreparation{Resources: &kubernetes.CredentialResources{Client: manager.GetClient(), Reader: manager.GetAPIReader()}, Store: f.store},
Provisioning: &application.DatabaseProvisioning{Resources: resources, Credentials: f.store, Backend: backend},
}
if err := databasecontroller.NewProvisioningReconciler(manager.GetClient(), service).SetupWithManager(manager); err != nil {
t.Fatal(err)
}
ctx, stop := context.WithCancel(f.ctx)
done := make(chan error, 1)
go func() { done <- manager.Start(ctx) }()
// 使用 scope 确保任何失败都先停止 worker,再由 fixture 关闭共享连接。
func() {
defer func() {
stop()
select {
case err := <-done:
if err != nil {
t.Error(err)
}
case <-time.After(10 * time.Second):
t.Error("供应 manager 未停止")
}
}()
if !manager.GetCache().WaitForCacheSync(ctx) {
t.Fatal("供应 cache 未同步")
}
deadline := time.After(15 * time.Second)
for {
if err := f.api.Get(f.ctx, client.ObjectKeyFromObject(database), database); err != nil {
t.Fatal(err)
}
if meta.IsStatusConditionTrue(database.Status.Conditions, "ResourcesReady") && backend.reads.Load() > 0 {
break
}
select {
case <-deadline:
t.Fatal("watch 未推动资源创建")
case <-time.After(100 * time.Millisecond):
}
}
if roleOID != 0 && (roleOID != database.Status.RoleOID || databaseOID != database.Status.DatabaseOID) {
t.Fatal("manager 重启替换已确认资源")
}
roleOID, databaseOID = database.Status.RoleOID, database.Status.DatabaseOID
}()
}
f.status(t, database, provisioning.Available)
}
type countProvisioningObservations struct {
application.ProvisioningBackend
reads atomic.Int32
}
func (b *countProvisioningObservations) InspectResources(ctx context.Context, target instance.ObservationTarget, name, role string) (provisioning.Observation, error) {
observation, err := b.ProvisioningBackend.InspectResources(ctx, target, name, role)
if err == nil && name == "watch_supply" {
b.reads.Add(1)
}
return observation, err
}