156 lines
5.8 KiB
Go
156 lines
5.8 KiB
Go
/*
|
|
Copyright 2026.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package controller
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
databasev1alpha1 "git.ddupan.top/panxiao81/postgresql-tenant-operator/api/v1alpha1"
|
|
)
|
|
|
|
type fakeInstanceInitializer struct {
|
|
validateVersion string
|
|
registryVersion string
|
|
err error
|
|
}
|
|
|
|
func (f fakeInstanceInitializer) Validate(context.Context, *databasev1alpha1.PostgreSQLInstance) (string, error) {
|
|
return f.validateVersion, f.err
|
|
}
|
|
|
|
func (f fakeInstanceInitializer) InitializeRegistry(context.Context, *databasev1alpha1.PostgreSQLInstance) (string, error) {
|
|
return f.registryVersion, f.err
|
|
}
|
|
|
|
var _ = Describe("phase handler state machines", func() {
|
|
It("advances an Instance through external validation and registry initialization", func() {
|
|
instance := &databasev1alpha1.PostgreSQLInstance{
|
|
ObjectMeta: metav1.ObjectMeta{Generation: 3},
|
|
Status: databasev1alpha1.PostgreSQLInstanceStatus{Phase: databasev1alpha1.PostgreSQLInstancePhaseValidating},
|
|
}
|
|
reconciler := &PostgreSQLInstanceReconciler{Initializer: fakeInstanceInitializer{
|
|
validateVersion: "17.6", registryVersion: "17.6",
|
|
}}
|
|
Expect(reconciler.reconcileDependencies(context.Background(), instance)).To(Succeed())
|
|
Expect(instance.Status.Phase).To(Equal(databasev1alpha1.PostgreSQLInstancePhaseInitializingRegistry))
|
|
Expect(reconciler.reconcileDependencies(context.Background(), instance)).To(Succeed())
|
|
Expect(instance.Status.Phase).To(Equal(databasev1alpha1.PostgreSQLInstancePhaseReady))
|
|
Expect(instance.Status.PostgreSQLVersion).To(Equal("17.6"))
|
|
Expect(instance.Status.ObservedGeneration).To(Equal(int64(3)))
|
|
Expect(instance.Status.Conditions).To(ConsistOf(And(
|
|
HaveField("Status", metav1.ConditionTrue), HaveField("Reason", databasev1alpha1.ReasonReady),
|
|
)))
|
|
})
|
|
|
|
It("keeps the safe phase when a dependency is unavailable", func() {
|
|
instance := &databasev1alpha1.PostgreSQLInstance{
|
|
ObjectMeta: metav1.ObjectMeta{Generation: 2},
|
|
Status: databasev1alpha1.PostgreSQLInstanceStatus{Phase: databasev1alpha1.PostgreSQLInstancePhaseValidating},
|
|
}
|
|
reconciler := &PostgreSQLInstanceReconciler{Initializer: fakeInstanceInitializer{err: errors.New("unavailable")}}
|
|
Expect(reconciler.reconcileDependencies(context.Background(), instance)).To(MatchError("unavailable"))
|
|
Expect(instance.Status.Phase).To(Equal(databasev1alpha1.PostgreSQLInstancePhaseValidating))
|
|
Expect(instance.Status.Conditions).To(ConsistOf(And(
|
|
HaveField("Status", metav1.ConditionFalse), HaveField("Reason", databasev1alpha1.ReasonDependencyUnavailable),
|
|
)))
|
|
})
|
|
|
|
DescribeTable("dispatches Instance phases",
|
|
func(instance *databasev1alpha1.PostgreSQLInstance, expected databasev1alpha1.PostgreSQLInstancePhase, hasMessage bool) {
|
|
result := newInstanceStateMachine().reconcile(instance)
|
|
Expect(result.phase).To(Equal(expected))
|
|
if hasMessage {
|
|
Expect(result.reconcilingMessage).NotTo(BeEmpty())
|
|
} else {
|
|
Expect(result.reconcilingMessage).To(BeEmpty())
|
|
}
|
|
},
|
|
Entry("starts validation from an empty checkpoint",
|
|
&databasev1alpha1.PostgreSQLInstance{},
|
|
databasev1alpha1.PostgreSQLInstancePhaseValidating,
|
|
true,
|
|
),
|
|
Entry("keeps an active phase until its handler can observe dependencies",
|
|
&databasev1alpha1.PostgreSQLInstance{Status: databasev1alpha1.PostgreSQLInstanceStatus{
|
|
Phase: databasev1alpha1.PostgreSQLInstancePhaseInitializingRegistry,
|
|
}},
|
|
databasev1alpha1.PostgreSQLInstancePhaseInitializingRegistry,
|
|
false,
|
|
),
|
|
Entry("routes deletion independently of the previous phase",
|
|
deletingInstance(),
|
|
databasev1alpha1.PostgreSQLInstancePhaseDeleting,
|
|
true,
|
|
),
|
|
)
|
|
|
|
DescribeTable("dispatches Tenant phases",
|
|
func(tenant *databasev1alpha1.PostgreSQLTenant, expected databasev1alpha1.PostgreSQLTenantPhase, hasMessage bool) {
|
|
result := newTenantStateMachine().reconcile(tenant)
|
|
Expect(result.phase).To(Equal(expected))
|
|
if hasMessage {
|
|
Expect(result.reconcilingMessage).NotTo(BeEmpty())
|
|
} else {
|
|
Expect(result.reconcilingMessage).To(BeEmpty())
|
|
}
|
|
},
|
|
Entry("starts pending from an empty checkpoint",
|
|
&databasev1alpha1.PostgreSQLTenant{},
|
|
databasev1alpha1.PostgreSQLTenantPhasePending,
|
|
true,
|
|
),
|
|
Entry("keeps an active phase until its handler can observe dependencies",
|
|
&databasev1alpha1.PostgreSQLTenant{Status: databasev1alpha1.PostgreSQLTenantStatus{
|
|
Phase: databasev1alpha1.PostgreSQLTenantPhaseCredentialCreated,
|
|
}},
|
|
databasev1alpha1.PostgreSQLTenantPhaseCredentialCreated,
|
|
false,
|
|
),
|
|
Entry("routes deletion independently of the previous phase",
|
|
deletingTenant(),
|
|
databasev1alpha1.PostgreSQLTenantPhaseDeleting,
|
|
true,
|
|
),
|
|
)
|
|
})
|
|
|
|
func deletingInstance() *databasev1alpha1.PostgreSQLInstance {
|
|
deletionTimestamp := metav1.NewTime(time.Now())
|
|
return &databasev1alpha1.PostgreSQLInstance{
|
|
ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &deletionTimestamp},
|
|
Status: databasev1alpha1.PostgreSQLInstanceStatus{
|
|
Phase: databasev1alpha1.PostgreSQLInstancePhaseReady,
|
|
},
|
|
}
|
|
}
|
|
|
|
func deletingTenant() *databasev1alpha1.PostgreSQLTenant {
|
|
deletionTimestamp := metav1.NewTime(time.Now())
|
|
return &databasev1alpha1.PostgreSQLTenant{
|
|
ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &deletionTimestamp},
|
|
Status: databasev1alpha1.PostgreSQLTenantStatus{
|
|
Phase: databasev1alpha1.PostgreSQLTenantPhaseReady,
|
|
},
|
|
}
|
|
}
|