139 lines
4.8 KiB
Go
139 lines
4.8 KiB
Go
//go:build integration
|
|
|
|
package openbao_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
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"
|
|
|
|
databasev1alpha1 "git.ddupan.top/panxiao81/ayatori/api/database/v1alpha1"
|
|
"git.ddupan.top/panxiao81/ayatori/internal/database/application"
|
|
databasecontroller "git.ddupan.top/panxiao81/ayatori/internal/database/controller"
|
|
)
|
|
|
|
func testPreparationWatch(t *testing.T, f *preparationFixture) {
|
|
database, _ := f.bound(t, "watch")
|
|
t.Cleanup(func() {
|
|
if !t.Failed() {
|
|
return
|
|
}
|
|
if err := f.api.Get(context.Background(), client.ObjectKeyFromObject(database), database); err == nil {
|
|
if condition := meta.FindStatusCondition(database.Status.Conditions, application.CredentialsReady); condition != nil {
|
|
t.Logf("失败时凭据条件: %s: %s", condition.Reason, condition.Message)
|
|
}
|
|
}
|
|
})
|
|
instance := &databasev1alpha1.PostgreSQLInstance{}
|
|
if err := f.api.Get(t.Context(), client.ObjectKey{Name: "watch"}, instance); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
setReady := func(ready metav1.ConditionStatus) {
|
|
t.Helper()
|
|
meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{
|
|
Type: "Ready", Status: ready, Reason: "FixtureObservation", Message: "隔离测试前置观察", ObservedGeneration: instance.Generation,
|
|
})
|
|
if err := f.api.Status().Update(t.Context(), instance); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
setReady(metav1.ConditionFalse)
|
|
stop := startPreparationManager(t, f)
|
|
waitForPreparation(t, func() bool {
|
|
if err := f.api.Get(t.Context(), client.ObjectKeyFromObject(database), database); err != nil {
|
|
return false
|
|
}
|
|
condition := meta.FindStatusCondition(database.Status.Conditions, application.CredentialsReady)
|
|
return condition != nil && condition.Reason == "DependencyUnavailable"
|
|
})
|
|
if database.Status.CredentialRef != nil {
|
|
t.Fatal("Instance 未 Ready 时不应固定或写入凭据")
|
|
}
|
|
setReady(metav1.ConditionTrue)
|
|
waitForPreparation(t, func() bool {
|
|
return f.api.Get(t.Context(), client.ObjectKeyFromObject(database), database) == nil && database.Status.CredentialVersion == 1
|
|
})
|
|
stop()
|
|
// 重启真实 manager 后继续观察;不得只依赖旧进程中的记忆。
|
|
setReady(metav1.ConditionFalse)
|
|
stop = startPreparationManager(t, f)
|
|
defer stop()
|
|
waitForPreparation(t, func() bool {
|
|
if err := f.api.Get(t.Context(), client.ObjectKeyFromObject(database), database); err != nil {
|
|
return false
|
|
}
|
|
condition := meta.FindStatusCondition(database.Status.Conditions, application.CredentialsReady)
|
|
return condition != nil && condition.Status == metav1.ConditionFalse && condition.Reason == "DependencyUnavailable"
|
|
})
|
|
setReady(metav1.ConditionTrue)
|
|
waitForPreparation(t, func() bool {
|
|
if err := f.api.Get(t.Context(), client.ObjectKeyFromObject(database), database); err != nil {
|
|
return false
|
|
}
|
|
condition := meta.FindStatusCondition(database.Status.Conditions, application.CredentialsReady)
|
|
return condition != nil && condition.Status == metav1.ConditionTrue && database.Status.CredentialVersion == 1
|
|
})
|
|
stored, err := f.bao.KVv2("secret").Get(t.Context(), database.Status.CredentialRef.Path)
|
|
if err != nil || stored.VersionMetadata.Version != 1 {
|
|
t.Fatal("manager 重启不得重建凭据")
|
|
}
|
|
}
|
|
|
|
func startPreparationManager(t *testing.T, f *preparationFixture) func() {
|
|
t.Helper()
|
|
manager, err := ctrl.NewManager(f.config, ctrl.Options{
|
|
Scheme: f.scheme, Metrics: metricsserver.Options{BindAddress: "0"}, HealthProbeBindAddress: "0",
|
|
// 测试在同一进程顺序重启 manager;旧 worker 已停止,但名称注册表仍是进程级。
|
|
Controller: controllerconfig.Controller{SkipNameValidation: new(true)},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := (&databasecontroller.BindingReconciler{}).SetupWithManager(t.Context(), manager); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := (&databasecontroller.CredentialReconciler{Store: fixtureStore(t, f.bao)}).SetupWithManager(manager); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
done := make(chan error, 1)
|
|
go func() { done <- manager.Start(ctx) }()
|
|
stopped := false
|
|
stop := func() {
|
|
if stopped {
|
|
return
|
|
}
|
|
stopped = true
|
|
cancel()
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
case <-time.After(20 * time.Second):
|
|
t.Error("凭据 manager 未停止")
|
|
}
|
|
}
|
|
t.Cleanup(stop)
|
|
return stop
|
|
}
|
|
|
|
func waitForPreparation(t *testing.T, check func() bool) {
|
|
t.Helper()
|
|
deadline := time.Now().Add(10 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if check() {
|
|
return
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
t.Fatal("凭据 watch 没有在低频重查之前驱动协调")
|
|
}
|