feat: reconcile PostgreSQLInstance dependencies
This commit is contained in:
@@ -18,7 +18,9 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -32,7 +34,15 @@ import (
|
||||
// PostgreSQLInstanceReconciler reconciles a PostgreSQLInstance object
|
||||
type PostgreSQLInstanceReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Scheme *runtime.Scheme
|
||||
Initializer PostgreSQLInstanceInitializer
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// PostgreSQLInstanceInitializer is the external dependency boundary used by the Instance state machine.
|
||||
type PostgreSQLInstanceInitializer interface {
|
||||
Validate(context.Context, *databasev1alpha1.PostgreSQLInstance) (string, error)
|
||||
InitializeRegistry(context.Context, *databasev1alpha1.PostgreSQLInstance) (string, error)
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=database.ddupan.top,resources=postgresqlinstances,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -62,6 +72,12 @@ func (r *PostgreSQLInstanceReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
setReconcilingCondition(&instance.Status.Conditions, instance.Generation, phaseResult.reconcilingMessage)
|
||||
}
|
||||
|
||||
var reconcileErr error
|
||||
if r.Initializer != nil && instance.DeletionTimestamp.IsZero() &&
|
||||
before.Status.Phase != "" && before.Status.Phase != databasev1alpha1.PostgreSQLInstancePhasePending {
|
||||
reconcileErr = r.reconcileDependencies(ctx, instance)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(before.Status, instance.Status) {
|
||||
if err := r.Status().Patch(ctx, instance, client.MergeFrom(before)); err != nil {
|
||||
if apierrors.IsConflict(err) {
|
||||
@@ -71,7 +87,56 @@ func (r *PostgreSQLInstanceReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
}
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
return ctrl.Result{}, reconcileErr
|
||||
}
|
||||
|
||||
func (r *PostgreSQLInstanceReconciler) reconcileDependencies(
|
||||
ctx context.Context,
|
||||
instance *databasev1alpha1.PostgreSQLInstance,
|
||||
) error {
|
||||
if r.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, r.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
if instance.Status.Phase == databasev1alpha1.PostgreSQLInstancePhaseReady &&
|
||||
instance.Status.ObservedGeneration != instance.Generation {
|
||||
instance.Status.Phase = databasev1alpha1.PostgreSQLInstancePhaseValidating
|
||||
setReconcilingCondition(&instance.Status.Conditions, instance.Generation, "instance dependencies are being validated")
|
||||
return nil
|
||||
}
|
||||
|
||||
var version string
|
||||
var err error
|
||||
switch instance.Status.Phase {
|
||||
case databasev1alpha1.PostgreSQLInstancePhaseValidating:
|
||||
version, err = r.Initializer.Validate(ctx, instance)
|
||||
if err == nil {
|
||||
instance.Status.PostgreSQLVersion = version
|
||||
instance.Status.Phase = databasev1alpha1.PostgreSQLInstancePhaseInitializingRegistry
|
||||
setReconcilingCondition(&instance.Status.Conditions, instance.Generation, "PostgreSQL registry is being initialized")
|
||||
}
|
||||
case databasev1alpha1.PostgreSQLInstancePhaseInitializingRegistry:
|
||||
version, err = r.Initializer.InitializeRegistry(ctx, instance)
|
||||
if err == nil {
|
||||
instance.Status.PostgreSQLVersion = version
|
||||
instance.Status.Phase = databasev1alpha1.PostgreSQLInstancePhaseReady
|
||||
instance.Status.ObservedGeneration = instance.Generation
|
||||
setReadyCondition(&instance.Status.Conditions, instance.Generation, "instance dependencies are ready")
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
instance.Status.ObservedGeneration = instance.Generation
|
||||
reason := databasev1alpha1.ReasonDependencyUnavailable
|
||||
var categorized interface{ ConditionReason() string }
|
||||
if errors.As(err, &categorized) {
|
||||
reason = categorized.ConditionReason()
|
||||
}
|
||||
setFailedCondition(&instance.Status.Conditions, instance.Generation, reason,
|
||||
"instance dependency validation failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
@@ -26,7 +28,53 @@ import (
|
||||
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)
|
||||
|
||||
@@ -32,3 +32,17 @@ func setReconcilingCondition(conditions *[]metav1.Condition, generation int64, m
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
func setReadyCondition(conditions *[]metav1.Condition, generation int64, message string) {
|
||||
apiMeta.SetStatusCondition(conditions, metav1.Condition{
|
||||
Type: databasev1alpha1.ConditionTypeReady, Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: generation, Reason: databasev1alpha1.ReasonReady, Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
func setFailedCondition(conditions *[]metav1.Condition, generation int64, reason, message string) {
|
||||
apiMeta.SetStatusCondition(conditions, metav1.Condition{
|
||||
Type: databasev1alpha1.ConditionTypeReady, Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: generation, Reason: reason, Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user