Skip to content

Commit 3b62f2d

Browse files
authored
YDBOPS-9168 Storage finalizer waiting for existing Databases (ydb-platform#211)
1 parent 71402a7 commit 3b62f2d

File tree

7 files changed

+201
-9
lines changed

7 files changed

+201
-9
lines changed

deploy/ydb-operator/Chart.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ type: application
1515
# This is the chart version. This version number should be incremented each time you make changes
1616
# to the chart and its templates, including the app version.
1717
# Versions are expected to follow Semantic Versioning (https://semver.org/)
18-
version: 0.5.19
18+
version: 0.5.20
1919

2020
# This is the version number of the application being deployed. This version number should be
2121
# incremented each time you make changes to the application. Versions are not expected to
2222
# follow Semantic Versioning. They should reflect the version the application is using.
2323
# It is recommended to use it with quotes.
24-
appVersion: "0.5.19"
24+
appVersion: "0.5.20"

e2e/tests/smoke_test.go

+100-7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
. "github.com/onsi/gomega"
2323
v1 "k8s.io/api/apps/v1"
2424
corev1 "k8s.io/api/core/v1"
25+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2526
"k8s.io/apimachinery/pkg/api/meta"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/types"
@@ -31,6 +32,7 @@ import (
3132
testobjects "github.com/ydb-platform/ydb-kubernetes-operator/e2e/tests/test-objects"
3233
. "github.com/ydb-platform/ydb-kubernetes-operator/internal/controllers/constants"
3334
"github.com/ydb-platform/ydb-kubernetes-operator/internal/resources"
35+
"github.com/ydb-platform/ydb-kubernetes-operator/internal/test"
3436
)
3537

3638
const (
@@ -158,7 +160,7 @@ func bringYdbCliToPod(podName, podNamespace string) {
158160
}, Timeout, Interval).Should(BeNil())
159161
}
160162

161-
func executeSimpleQuery(ctx context.Context, podName, podNamespace, storageEndpoint string) {
163+
func executeSimpleQuery(podName, podNamespace, storageEndpoint string) {
162164
Eventually(func(g Gomega) string {
163165
args := []string{
164166
"-n",
@@ -345,7 +347,7 @@ var _ = Describe("Operator smoke test", func() {
345347
bringYdbCliToPod(podName, testobjects.YdbNamespace)
346348

347349
By("execute simple query inside ydb database pod...")
348-
executeSimpleQuery(ctx, podName, testobjects.YdbNamespace, storageEndpoint)
350+
executeSimpleQuery(podName, testobjects.YdbNamespace, storageEndpoint)
349351
})
350352

351353
It("pause and un-pause Storage, should destroy and bring up Pods", func() {
@@ -492,7 +494,6 @@ var _ = Describe("Operator smoke test", func() {
492494
It("create storage and database with nodeSets", func() {
493495
By("issuing create commands...")
494496
storageSample = testobjects.DefaultStorage(filepath.Join(".", "data", "storage-block-4-2-config-nodeSets.yaml"))
495-
databaseSample = testobjects.DefaultDatabase()
496497
testNodeSetName := "nodeset"
497498
for idx := 1; idx <= 2; idx++ {
498499
storageSample.Spec.NodeSets = append(storageSample.Spec.NodeSets, v1alpha1.StorageNodeSetSpecInline{
@@ -572,7 +573,7 @@ var _ = Describe("Operator smoke test", func() {
572573
bringYdbCliToPod(podName, testobjects.YdbNamespace)
573574

574575
By("execute simple query inside ydb database pod...")
575-
executeSimpleQuery(ctx, podName, testobjects.YdbNamespace, storageEndpoint)
576+
executeSimpleQuery(podName, testobjects.YdbNamespace, storageEndpoint)
576577
})
577578

578579
It("operatorConnection check, create storage with default staticCredentials", func() {
@@ -652,28 +653,115 @@ var _ = Describe("Operator smoke test", func() {
652653
LocalObjectReference: corev1.LocalObjectReference{Name: testobjects.CertificateSecretName},
653654
Key: "ca.crt",
654655
}
655-
656656
Expect(k8sClient.Create(ctx, storageSample)).Should(Succeed())
657657
defer func() {
658658
Expect(k8sClient.Delete(ctx, storageSample)).Should(Succeed())
659659
}()
660+
661+
By("waiting until Storage is ready...")
662+
waitUntilStorageReady(ctx, storageSample.Name, testobjects.YdbNamespace)
663+
664+
By("checking that all the storage pods are running and ready...")
665+
checkPodsRunningAndReady(ctx, "ydb-cluster", "kind-storage", storageSample.Spec.Nodes)
666+
660667
By("create database...")
668+
databaseSample.Spec.Service.GRPC.TLSConfiguration.Enabled = true
669+
databaseSample.Spec.Service.GRPC.TLSConfiguration.Certificate = corev1.SecretKeySelector{
670+
LocalObjectReference: corev1.LocalObjectReference{Name: testobjects.CertificateSecretName},
671+
Key: "tls.crt",
672+
}
673+
databaseSample.Spec.Service.GRPC.TLSConfiguration.Key = corev1.SecretKeySelector{
674+
LocalObjectReference: corev1.LocalObjectReference{Name: testobjects.CertificateSecretName},
675+
Key: "tls.key",
676+
}
677+
databaseSample.Spec.Service.GRPC.TLSConfiguration.CertificateAuthority = corev1.SecretKeySelector{
678+
LocalObjectReference: corev1.LocalObjectReference{Name: testobjects.CertificateSecretName},
679+
Key: "ca.crt",
680+
}
661681
Expect(k8sClient.Create(ctx, databaseSample)).Should(Succeed())
662682
defer func() {
663683
Expect(k8sClient.Delete(ctx, databaseSample)).Should(Succeed())
664684
}()
665685

686+
By("waiting until database is ready...")
687+
waitUntilDatabaseReady(ctx, databaseSample.Name, testobjects.YdbNamespace)
688+
689+
By("checking that all the database pods are running and ready...")
690+
checkPodsRunningAndReady(ctx, "ydb-cluster", "kind-database", databaseSample.Spec.Nodes)
691+
692+
storagePods := corev1.PodList{}
693+
Expect(k8sClient.List(ctx, &storagePods,
694+
client.InNamespace(testobjects.YdbNamespace),
695+
client.MatchingLabels{
696+
"ydb-cluster": "kind-database",
697+
})).Should(Succeed())
698+
podName := storagePods.Items[0].Name
699+
700+
By("bring YDB CLI inside ydb storage pod...")
701+
bringYdbCliToPod(podName, testobjects.YdbNamespace)
702+
703+
By("execute simple query inside ydb storage pod...")
704+
storageEndpoint := fmt.Sprintf("grpcs://%s:%d", testobjects.StorageGRPCService, testobjects.StorageGRPCPort)
705+
executeSimpleQuery(podName, testobjects.YdbNamespace, storageEndpoint)
706+
})
707+
708+
It("Check that Storage deleted after Database...", func() {
709+
By("create storage...")
710+
Expect(k8sClient.Create(ctx, storageSample)).Should(Succeed())
711+
712+
By("create database...")
713+
Expect(k8sClient.Create(ctx, databaseSample)).Should(Succeed())
714+
666715
By("waiting until Storage is ready...")
667716
waitUntilStorageReady(ctx, storageSample.Name, testobjects.YdbNamespace)
668717

669718
By("checking that all the storage pods are running and ready...")
670719
checkPodsRunningAndReady(ctx, "ydb-cluster", "kind-storage", storageSample.Spec.Nodes)
671720

672-
By("waiting until database is ready...")
721+
By("waiting until Database is ready...")
673722
waitUntilDatabaseReady(ctx, databaseSample.Name, testobjects.YdbNamespace)
674723

675724
By("checking that all the database pods are running and ready...")
676725
checkPodsRunningAndReady(ctx, "ydb-cluster", "kind-database", databaseSample.Spec.Nodes)
726+
727+
By("delete Storage...")
728+
Expect(k8sClient.Delete(ctx, storageSample)).Should(Succeed())
729+
730+
By("checking that Storage deletionTimestamp is not nil...")
731+
Eventually(func() bool {
732+
foundStorage := v1alpha1.Storage{}
733+
err := k8sClient.Get(ctx, types.NamespacedName{
734+
Name: storageSample.Name,
735+
Namespace: testobjects.YdbNamespace,
736+
}, &foundStorage)
737+
if err != nil {
738+
return false
739+
}
740+
return !foundStorage.DeletionTimestamp.IsZero()
741+
}, test.Timeout, test.Interval).Should(BeTrue())
742+
743+
By("checking that Storage is present in cluster...")
744+
Consistently(func() error {
745+
foundStorage := v1alpha1.Storage{}
746+
err := k8sClient.Get(ctx, types.NamespacedName{
747+
Name: storageSample.Name,
748+
Namespace: testobjects.YdbNamespace,
749+
}, &foundStorage)
750+
return err
751+
}, test.Timeout, test.Interval).ShouldNot(HaveOccurred())
752+
753+
By("delete Database...")
754+
Expect(k8sClient.Delete(ctx, databaseSample)).Should(Succeed())
755+
756+
By("checking that Storage deleted from cluster...")
757+
Eventually(func() bool {
758+
foundStorage := v1alpha1.Storage{}
759+
err := k8sClient.Get(ctx, types.NamespacedName{
760+
Name: storageSample.Name,
761+
Namespace: testobjects.YdbNamespace,
762+
}, &foundStorage)
763+
return apierrors.IsNotFound(err)
764+
}, test.Timeout, test.Interval).Should(BeTrue())
677765
})
678766

679767
It("TLS for status service", func() {
@@ -740,13 +828,18 @@ var _ = Describe("Operator smoke test", func() {
740828
Key: "ca.crt",
741829
},
742830
}
743-
744831
Expect(k8sClient.Create(ctx, storageSample)).Should(Succeed())
832+
defer func() {
833+
Expect(k8sClient.Delete(ctx, storageSample)).Should(Succeed())
834+
}()
745835

746836
By("create database...")
747837
databaseSample.Spec.Nodes = 1
748838
databaseSample.Spec.Service.Status = *storageSample.Spec.Service.Status.DeepCopy()
749839
Expect(k8sClient.Create(ctx, databaseSample)).Should(Succeed())
840+
defer func() {
841+
Expect(k8sClient.Delete(ctx, databaseSample)).Should(Succeed())
842+
}()
750843

751844
By("waiting until Storage is ready...")
752845
waitUntilStorageReady(ctx, storageSample.Name, testobjects.YdbNamespace)

e2e/tests/test-objects/objects.go

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const (
1818
CertificateSecretName = "storage-crt"
1919
DefaultDomain = "Root"
2020
ReadyStatus = "Ready"
21+
StorageGRPCService = "storage-grpc.ydb.svc.cluster.local"
22+
StorageGRPCPort = 2135
2123
)
2224

2325
func constructAntiAffinityFor(key, value string) *corev1.Affinity {

internal/annotations/annotations.go

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const (
55
PrimaryResourceDatabaseAnnotation = "ydb.tech/primary-resource-database"
66
RemoteResourceVersionAnnotation = "ydb.tech/remote-resource-version"
77
ConfigurationChecksum = "ydb.tech/configuration-checksum"
8+
StorageFinalizerKey = "ydb.tech/storage-finalizer"
89
RemoteFinalizerKey = "ydb.tech/remote-finalizer"
910
LastAppliedAnnotation = "ydb.tech/last-applied"
1011
)

internal/controllers/remotedatabasenodeset/controller_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ var _ = Describe("RemoteDatabaseNodeSet controller tests", func() {
347347
})
348348

349349
AfterEach(func() {
350+
Expect(localClient.Delete(ctx, databaseSample)).Should(Succeed())
351+
Expect(localClient.Delete(ctx, storageSample)).Should(Succeed())
350352
deleteAll(localEnv, localClient, &localNamespace)
351353
deleteAll(remoteEnv, remoteClient, &localNamespace)
352354
})

internal/controllers/remotestoragenodeset/controller_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ var _ = Describe("RemoteStorageNodeSet controller tests", func() {
279279
})
280280

281281
AfterEach(func() {
282+
Expect(localClient.Delete(ctx, storageSample)).Should(Succeed())
282283
deleteAll(localEnv, localClient, &localNamespace)
283284
deleteAll(remoteEnv, remoteClient, &localNamespace)
284285
})

internal/controllers/storage/controller.go

+93
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package storage
22

33
import (
44
"context"
5+
"errors"
6+
"fmt"
57

68
"github.com/go-logr/logr"
79
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
@@ -16,13 +18,15 @@ import (
1618
ctrl "sigs.k8s.io/controller-runtime"
1719
"sigs.k8s.io/controller-runtime/pkg/builder"
1820
"sigs.k8s.io/controller-runtime/pkg/client"
21+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1922
"sigs.k8s.io/controller-runtime/pkg/handler"
2023
"sigs.k8s.io/controller-runtime/pkg/log"
2124
"sigs.k8s.io/controller-runtime/pkg/predicate"
2225
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2326
"sigs.k8s.io/controller-runtime/pkg/source"
2427

2528
"github.com/ydb-platform/ydb-kubernetes-operator/api/v1alpha1"
29+
ydbannotations "github.com/ydb-platform/ydb-kubernetes-operator/internal/annotations"
2630
. "github.com/ydb-platform/ydb-kubernetes-operator/internal/controllers/constants" //nolint:revive,stylecheck
2731
"github.com/ydb-platform/ydb-kubernetes-operator/internal/resources"
2832
)
@@ -77,6 +81,40 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
7781
r.Log.Error(err, "unexpected Get error")
7882
return ctrl.Result{RequeueAfter: DefaultRequeueDelay}, err
7983
}
84+
85+
//nolint:nestif
86+
// examine DeletionTimestamp to determine if object is under deletion
87+
if resource.ObjectMeta.DeletionTimestamp.IsZero() {
88+
// The object is not being deleted, so if it does not have our finalizer,
89+
// then lets add the finalizer and update the object. This is equivalent
90+
// to registering our finalizer.
91+
if !controllerutil.ContainsFinalizer(resource, ydbannotations.StorageFinalizerKey) {
92+
controllerutil.AddFinalizer(resource, ydbannotations.StorageFinalizerKey)
93+
if err := r.Client.Update(ctx, resource); err != nil {
94+
return ctrl.Result{RequeueAfter: DefaultRequeueDelay}, err
95+
}
96+
}
97+
} else {
98+
// The object is being deleted
99+
if controllerutil.ContainsFinalizer(resource, ydbannotations.StorageFinalizerKey) {
100+
// our finalizer is present, so lets handle any external dependency
101+
if err := r.checkExistingDatabases(ctx, resource); err != nil {
102+
// if fail to check dependency existence, return with error
103+
// so that it can be retried.
104+
return ctrl.Result{RequeueAfter: DefaultRequeueDelay}, err
105+
}
106+
107+
// remove our finalizer from the list and update it.
108+
controllerutil.RemoveFinalizer(resource, ydbannotations.StorageFinalizerKey)
109+
if err := r.Client.Update(ctx, resource); err != nil {
110+
return ctrl.Result{RequeueAfter: DefaultRequeueDelay}, err
111+
}
112+
}
113+
114+
// Stop reconciliation as the item is being deleted
115+
return ctrl.Result{Requeue: false}, nil
116+
}
117+
80118
result, err := r.Sync(ctx, resource)
81119
if err != nil {
82120
r.Log.Error(err, "unexpected Sync error")
@@ -130,6 +168,18 @@ func createFieldIndexers(mgr ctrl.Manager) error {
130168
return err
131169
}
132170

171+
if err := mgr.GetFieldIndexer().IndexField(
172+
context.Background(),
173+
&v1alpha1.Database{},
174+
StorageRefField,
175+
func(obj client.Object) []string {
176+
// grab the Database object, extract the .spec.storageRef.name...
177+
database := obj.(*v1alpha1.Database)
178+
return []string{database.Spec.StorageClusterRef.Name}
179+
}); err != nil {
180+
return err
181+
}
182+
133183
return mgr.GetFieldIndexer().IndexField(
134184
context.Background(),
135185
&v1alpha1.Storage{},
@@ -212,3 +262,46 @@ func (r *Reconciler) findStoragesForSecret(secret client.Object) []reconcile.Req
212262
}
213263
return requests
214264
}
265+
266+
func (r *Reconciler) checkExistingDatabases(
267+
ctx context.Context,
268+
storage *v1alpha1.Storage,
269+
) error {
270+
databaseList := &v1alpha1.DatabaseList{}
271+
err := r.Client.List(
272+
ctx,
273+
databaseList,
274+
client.InNamespace(storage.Namespace),
275+
client.MatchingFields{
276+
StorageRefField: storage.Name,
277+
},
278+
)
279+
if err != nil {
280+
r.Log.Error(err, "failed to list Databases")
281+
r.Recorder.Event(
282+
storage,
283+
corev1.EventTypeWarning,
284+
"ControllerError",
285+
fmt.Sprintf("Failed to list Databases: %s", err),
286+
)
287+
return err
288+
}
289+
290+
if len(databaseList.Items) > 0 {
291+
var databases []string
292+
for _, database := range databaseList.Items {
293+
databases = append(databases, database.Name)
294+
}
295+
errMessage := fmt.Sprintf("Waiting for existing Databases to be deleted: %v", databases)
296+
r.Log.Info(errMessage)
297+
r.Recorder.Event(
298+
storage,
299+
corev1.EventTypeNormal,
300+
string(StorageProvisioning),
301+
fmt.Sprintf(errMessage, databases),
302+
)
303+
return errors.New(errMessage)
304+
}
305+
306+
return nil
307+
}

0 commit comments

Comments
 (0)