feat: 接入 Instance 原生管理观测与删除保护
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
databasev1alpha1 "git.ddupan.top/panxiao81/ayatori/api/database/v1alpha1"
|
||||
"git.ddupan.top/panxiao81/ayatori/internal/database/application"
|
||||
"git.ddupan.top/panxiao81/ayatori/internal/database/domain/instance"
|
||||
)
|
||||
|
||||
func instanceRecord(object *databasev1alpha1.PostgreSQLInstance) (*application.InstanceRecord, error) {
|
||||
identity, err := instance.NewIdentity(string(object.UID), object.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
revision, err := instance.NewRevision(object.Generation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec := object.Spec
|
||||
endpoint, err := instance.NewEndpoint(instance.EndpointValues{
|
||||
Host: spec.Endpoint.Host, HostAddr: spec.Endpoint.HostAddr,
|
||||
Port: int(spec.Endpoint.Port), ManagementDatabase: string(spec.Endpoint.Database),
|
||||
TLSMode: instance.TLSMode(spec.Endpoint.SSLMode),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
credential, err := instance.NewCredentialReference(instance.CredentialReferenceValues{
|
||||
Name: string(spec.AdminCredentialRef.Name),
|
||||
UsernameKey: spec.AdminCredentialRef.UsernameKey, PasswordKey: spec.AdminCredentialRef.PasswordKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
definition, err := instance.NewDefinition(endpoint, credential)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target, err := instance.NewObservationTarget(identity, revision, definition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &application.InstanceRecord{
|
||||
Target: target, Revision: object.ResourceVersion, Deleting: !object.DeletionTimestamp.IsZero(),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
databasev1alpha1 "git.ddupan.top/panxiao81/ayatori/api/database/v1alpha1"
|
||||
"git.ddupan.top/panxiao81/ayatori/internal/database/application"
|
||||
"git.ddupan.top/panxiao81/ayatori/internal/database/domain/instance"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
)
|
||||
|
||||
const InstanceFinalizer = "database.ayatori.ddupan.top/instance-protection"
|
||||
|
||||
type InstanceResources struct {
|
||||
Client client.Client
|
||||
Reader client.Reader
|
||||
}
|
||||
|
||||
func (r *InstanceResources) LoadInstance(ctx context.Context, name string) (*application.InstanceRecord, error) {
|
||||
object := &databasev1alpha1.PostgreSQLInstance{}
|
||||
if err := r.Reader.Get(ctx, client.ObjectKey{Name: name}, object); err != nil {
|
||||
return nil, client.IgnoreNotFound(err)
|
||||
}
|
||||
return instanceRecord(object)
|
||||
}
|
||||
|
||||
func (r *InstanceResources) ProtectInstance(ctx context.Context, record *application.InstanceRecord) (*application.InstanceRecord, error) {
|
||||
object, err := r.instanceAtVersion(ctx, record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if controllerutil.AddFinalizer(object, InstanceFinalizer) {
|
||||
if err := r.Client.Update(ctx, object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return instanceRecord(object)
|
||||
}
|
||||
|
||||
func (r *InstanceResources) InstanceReferences(ctx context.Context, name string) (string, error) {
|
||||
// 删除判断必须直读 API;包含 Released、删除中的 Database 和尚未绑定的申请。
|
||||
// 不按旧 Instance UID 忽略引用,也不依赖 informer 索引的及时性。
|
||||
databases := &databasev1alpha1.PostgreSQLDatabaseList{}
|
||||
if err := r.Reader.List(ctx, databases); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, database := range databases.Items {
|
||||
if string(database.Spec.InstanceRef.Name) == name {
|
||||
return "Database/" + database.Name, nil
|
||||
}
|
||||
}
|
||||
tenants := &databasev1alpha1.PostgreSQLTenantList{}
|
||||
if err := r.Reader.List(ctx, tenants); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, tenant := range tenants.Items {
|
||||
if tenant.Spec.Provision != nil && string(tenant.Spec.Provision.InstanceRef.Name) == name {
|
||||
return "Tenant/" + tenant.Namespace + "/" + tenant.Name, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (r *InstanceResources) PresentInstance(ctx context.Context, result application.InstanceResult) error {
|
||||
if result.Record == nil {
|
||||
return nil
|
||||
}
|
||||
object, err := r.instanceAtVersion(ctx, result.Record)
|
||||
if err != nil {
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
previous := object.Status.DeepCopy()
|
||||
object.Status.Phase = string(result.Snapshot.Phase)
|
||||
object.Status.ObservedGeneration = object.Generation
|
||||
object.Status.PostgreSQLVersion = result.Snapshot.ReportedVersion
|
||||
ready := metav1.ConditionFalse
|
||||
if result.Snapshot.Readiness == instance.Ready {
|
||||
ready = metav1.ConditionTrue
|
||||
}
|
||||
meta.SetStatusCondition(&object.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready", Status: ready, ObservedGeneration: object.Generation,
|
||||
Reason: result.Reason, Message: result.Message,
|
||||
})
|
||||
if !equality.Semantic.DeepEqual(*previous, object.Status) {
|
||||
if err := r.Client.Status().Update(ctx, object); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if result.RemoveProtection && controllerutil.RemoveFinalizer(object, InstanceFinalizer) {
|
||||
return r.Client.Update(ctx, object)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InstanceResources) instanceAtVersion(ctx context.Context, record *application.InstanceRecord) (*databasev1alpha1.PostgreSQLInstance, error) {
|
||||
object := &databasev1alpha1.PostgreSQLInstance{}
|
||||
name := record.Target.Identity().Name()
|
||||
if err := r.Reader.Get(ctx, client.ObjectKey{Name: name}, object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if string(object.UID) != record.Target.Identity().UID() || object.ResourceVersion != record.Revision {
|
||||
return nil, apierrors.NewConflict(databasev1alpha1.GroupVersion.WithResource("postgresqlinstances").GroupResource(),
|
||||
name, errors.New("Instance 快照已过期,请重新观察"))
|
||||
}
|
||||
return object, nil
|
||||
}
|
||||
Reference in New Issue
Block a user