Skip to content

Commit db886bc

Browse files
committed
Extend E2E utils with machinery for ScyllaDBDatacenters
1 parent 32a660a commit db886bc

File tree

7 files changed

+614
-95
lines changed

7 files changed

+614
-95
lines changed

test/e2e/utils/helpers.go

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/scylladb/scylla-operator/pkg/scyllaclient"
2929
"github.com/scylladb/scylla-operator/pkg/util/hash"
3030
"github.com/scylladb/scylla-operator/test/e2e/framework"
31+
utilsv1alpha1 "github.com/scylladb/scylla-operator/test/e2e/utils/v1alpha1"
3132
appsv1 "k8s.io/api/apps/v1"
3233
corev1 "k8s.io/api/core/v1"
3334
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -281,24 +282,7 @@ func GetStatefulSetsForScyllaCluster(ctx context.Context, client appv1client.App
281282
}
282283

283284
func GetPodsForStatefulSet(ctx context.Context, client corev1client.CoreV1Interface, sts *appsv1.StatefulSet) (map[string]*corev1.Pod, error) {
284-
selector, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
285-
if err != nil {
286-
return nil, fmt.Errorf("can't convert StatefulSet %q selector: %w", naming.ObjRef(sts), err)
287-
}
288-
289-
podList, err := client.Pods(sts.Namespace).List(ctx, metav1.ListOptions{
290-
LabelSelector: selector.String(),
291-
})
292-
if err != nil {
293-
return nil, fmt.Errorf("can't list Pods for StatefulSet %q: %w", naming.ObjRef(sts), err)
294-
}
295-
296-
res := map[string]*corev1.Pod{}
297-
for _, pod := range podList.Items {
298-
res[pod.Name] = &pod
299-
}
300-
301-
return res, nil
285+
return utilsv1alpha1.GetPodsForStatefulSet(ctx, client, sts)
302286
}
303287

304288
func GetDaemonSetsForNodeConfig(ctx context.Context, client appv1client.AppsV1Interface, nc *scyllav1alpha1.NodeConfig) ([]*appsv1.DaemonSet, error) {

test/e2e/utils/v1alpha1/config.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (C) 2025 ScyllaDB
2+
3+
package v1alpha1
4+
5+
import "time"
6+
7+
const (
8+
// SyncTimeout is the maximum time the sync loop can last. In normal circumstances this is in the order
9+
// of seconds but there are special cases we need to account for. Like when the sync loop generates new keys
10+
// and signs certificates it can take several seconds. When the CI creates multiple clusters in parallel on
11+
// a constrained CPU, one cert can easily take over 30s.
12+
SyncTimeout = 2 * time.Minute
13+
imagePullTimeout = 4 * time.Minute
14+
joinClusterTimeout = 3 * time.Minute
15+
cleanupJobTimeout = 1 * time.Minute
16+
17+
// memberRolloutTimeout is the maximum amount of time it takes to start a scylla pod and become ready.
18+
// It includes the time to pull the images, copy the necessary files (sidecar), join the cluster and similar.
19+
memberRolloutTimeout = 30*time.Second + imagePullTimeout + joinClusterTimeout
20+
)

test/e2e/utils/v1alpha1/helpers.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,30 @@ package v1alpha1
44

55
import (
66
"context"
7+
"errors"
78
"fmt"
9+
"reflect"
10+
"sort"
11+
"strings"
12+
"time"
13+
14+
scyllav1 "github.com/scylladb/scylla-operator/pkg/api/scylla/v1"
815
scyllav1alpha1 "github.com/scylladb/scylla-operator/pkg/api/scylla/v1alpha1"
916
"github.com/scylladb/scylla-operator/pkg/controllerhelpers"
1017
"github.com/scylladb/scylla-operator/pkg/helpers"
1118
"github.com/scylladb/scylla-operator/pkg/helpers/slices"
1219
"github.com/scylladb/scylla-operator/pkg/naming"
1320
"github.com/scylladb/scylla-operator/pkg/pointer"
1421
"github.com/scylladb/scylla-operator/pkg/scyllaclient"
22+
"github.com/scylladb/scylla-operator/test/e2e/framework"
23+
appsv1 "k8s.io/api/apps/v1"
1524
corev1 "k8s.io/api/core/v1"
1625
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1726
"k8s.io/apimachinery/pkg/labels"
27+
"k8s.io/apimachinery/pkg/util/wait"
28+
appv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
1829
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
30+
"k8s.io/klog/v2"
1931
)
2032

2133
func GetScyllaClient(ctx context.Context, client corev1client.CoreV1Interface, sdc *scyllav1alpha1.ScyllaDBDatacenter) (*scyllaclient.Client, []string, error) {
@@ -160,4 +172,153 @@ func GetBroadcastAddress(ctx context.Context, client corev1client.CoreV1Interfac
160172
}
161173

162174
return broadcastAddress, nil
175+
176+
}
177+
178+
func ContextForRollout(parent context.Context, sdc *scyllav1alpha1.ScyllaDBDatacenter) (context.Context, context.CancelFunc) {
179+
return context.WithTimeout(parent, RolloutTimeoutForScyllaDBDatacenter(sdc))
180+
}
181+
182+
func RolloutTimeoutForScyllaDBDatacenter(sdc *scyllav1alpha1.ScyllaDBDatacenter) time.Duration {
183+
return SyncTimeout + time.Duration(GetNodeCount(sdc))*memberRolloutTimeout + cleanupJobTimeout
184+
}
185+
186+
func GetNodeCount(sdc *scyllav1alpha1.ScyllaDBDatacenter) int32 {
187+
nodes := int32(0)
188+
rackTemplateNodes := int32(0)
189+
190+
if sdc.Spec.RackTemplate != nil && sdc.Spec.RackTemplate.Nodes != nil {
191+
rackTemplateNodes = *sdc.Spec.RackTemplate.Nodes
192+
}
193+
194+
for _, r := range sdc.Spec.Racks {
195+
if r.Nodes != nil {
196+
nodes += *r.Nodes
197+
} else {
198+
nodes += rackTemplateNodes
199+
}
200+
}
201+
202+
return nodes
203+
}
204+
205+
func IsScyllaDBDatacenterRolledOut(sdc *scyllav1alpha1.ScyllaDBDatacenter) (bool, error) {
206+
if !helpers.IsStatusConditionPresentAndTrue(sdc.Status.Conditions, scyllav1.AvailableCondition, sdc.Generation) {
207+
return false, nil
208+
}
209+
210+
if !helpers.IsStatusConditionPresentAndFalse(sdc.Status.Conditions, scyllav1.ProgressingCondition, sdc.Generation) {
211+
return false, nil
212+
}
213+
214+
if !helpers.IsStatusConditionPresentAndFalse(sdc.Status.Conditions, scyllav1.DegradedCondition, sdc.Generation) {
215+
return false, nil
216+
}
217+
218+
framework.Infof("ScyllaDBDatacenter %s (RV=%s) is rolled out", klog.KObj(sdc), sdc.ResourceVersion)
219+
220+
return true, nil
221+
}
222+
223+
func IsScyllaDBManagerClusterRegistrationRolledOut(smcr *scyllav1alpha1.ScyllaDBManagerClusterRegistration) (bool, error) {
224+
if !helpers.IsStatusConditionPresentAndFalse(smcr.Status.Conditions, scyllav1.ProgressingCondition, smcr.Generation) {
225+
return false, nil
226+
}
227+
228+
if !helpers.IsStatusConditionPresentAndFalse(smcr.Status.Conditions, scyllav1.DegradedCondition, smcr.Generation) {
229+
return false, nil
230+
}
231+
232+
framework.Infof("ScyllaDBManagerClusterRegistration %s (RV=%s) is rolled out", klog.KObj(smcr), smcr.ResourceVersion)
233+
234+
return true, nil
235+
}
236+
237+
func GetStatefulSetsForScyllaDBDatacenter(ctx context.Context, client appv1client.AppsV1Interface, sdc *scyllav1alpha1.ScyllaDBDatacenter) (map[string]*appsv1.StatefulSet, error) {
238+
statefulsetList, err := client.StatefulSets(sdc.Namespace).List(ctx, metav1.ListOptions{
239+
LabelSelector: labels.Set{
240+
naming.ClusterNameLabel: sdc.Name,
241+
}.AsSelector().String(),
242+
})
243+
if err != nil {
244+
return nil, err
245+
}
246+
247+
res := map[string]*appsv1.StatefulSet{}
248+
for _, s := range statefulsetList.Items {
249+
rackName := s.Labels[naming.RackNameLabel]
250+
res[rackName] = &s
251+
}
252+
253+
return res, nil
254+
}
255+
256+
func GetPodsForStatefulSet(ctx context.Context, client corev1client.CoreV1Interface, sts *appsv1.StatefulSet) (map[string]*corev1.Pod, error) {
257+
selector, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
258+
if err != nil {
259+
return nil, fmt.Errorf("can't convert StatefulSet %q selector: %w", naming.ObjRef(sts), err)
260+
}
261+
262+
podList, err := client.Pods(sts.Namespace).List(ctx, metav1.ListOptions{
263+
LabelSelector: selector.String(),
264+
})
265+
if err != nil {
266+
return nil, fmt.Errorf("can't list Pods for StatefulSet %q: %w", naming.ObjRef(sts), err)
267+
}
268+
269+
res := map[string]*corev1.Pod{}
270+
for _, pod := range podList.Items {
271+
res[pod.Name] = &pod
272+
}
273+
274+
return res, nil
275+
}
276+
277+
// TODO: Should be unified with function coming from test/helpers once e2e's there starts using ScyllaDBDatacenter API.
278+
func WaitForFullQuorum(ctx context.Context, client corev1client.CoreV1Interface, sdc *scyllav1alpha1.ScyllaDBDatacenter, sortedExpectedHosts []string) error {
279+
scyllaClient, hosts, err := GetScyllaClient(ctx, client, sdc)
280+
if err != nil {
281+
return fmt.Errorf("can't get scylla client: %w", err)
282+
}
283+
defer scyllaClient.Close()
284+
285+
// Wait for node status to propagate and reach consistency.
286+
// This can take a while so let's set a large enough timeout to avoid flakes.
287+
return wait.PollImmediateWithContext(ctx, 1*time.Second, 5*time.Minute, func(ctx context.Context) (done bool, err error) {
288+
allSeeAllAsUN := true
289+
infoMessages := make([]string, 0, len(hosts))
290+
var errs []error
291+
for _, h := range hosts {
292+
var s scyllaclient.NodeStatusInfoSlice
293+
s, err = scyllaClient.Status(ctx, h)
294+
if err != nil {
295+
return true, fmt.Errorf("can't get scylla status on node %q: %w", h, err)
296+
}
297+
298+
sHosts := s.Hosts()
299+
sort.Strings(sHosts)
300+
if !reflect.DeepEqual(sHosts, sortedExpectedHosts) {
301+
errs = append(errs, fmt.Errorf("node %q thinks the cluster consists of different nodes: %s", h, sHosts))
302+
}
303+
304+
downHosts := s.DownHosts()
305+
infoMessages = append(infoMessages, fmt.Sprintf("Node %q, down: %q, up: %q", h, strings.Join(downHosts, "\n"), strings.Join(s.LiveHosts(), ",")))
306+
307+
if len(downHosts) != 0 {
308+
allSeeAllAsUN = false
309+
}
310+
}
311+
312+
if !allSeeAllAsUN {
313+
framework.Infof("ScyllaDB nodes have not reached status consistency yet. Statuses:\n%s", strings.Join(infoMessages, ","))
314+
}
315+
316+
err = errors.Join(errs...)
317+
if err != nil {
318+
framework.Infof("ScyllaDB nodes encountered an error. Statuses:\n%s", strings.Join(infoMessages, ","))
319+
return true, err
320+
}
321+
322+
return allSeeAllAsUN, nil
323+
})
163324
}

test/e2e/utils/verification/scyllacluster/cql.go

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/scylladb/scylla-operator/test/e2e/framework"
1010
"github.com/scylladb/scylla-operator/test/e2e/scheme"
1111
"github.com/scylladb/scylla-operator/test/e2e/utils"
12+
scylladbdatacenterverification "github.com/scylladb/scylla-operator/test/e2e/utils/verification/scylladbdatacenter"
1213
corev1 "k8s.io/api/core/v1"
1314
"k8s.io/apimachinery/pkg/runtime"
1415
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@@ -27,22 +28,11 @@ func WaitForFullMultiDCQuorum(ctx context.Context, dcClientMap map[string]corev1
2728
}
2829

2930
func VerifyCQLData(ctx context.Context, di *utils.DataInserter) {
30-
err := di.AwaitSchemaAgreement(ctx)
31-
o.Expect(err).NotTo(o.HaveOccurred())
32-
33-
framework.By("Verifying the data")
34-
data, err := di.Read()
35-
o.Expect(err).NotTo(o.HaveOccurred())
36-
o.Expect(data).To(o.Equal(di.GetExpected()))
31+
scylladbdatacenterverification.VerifyCQLData(ctx, di)
3732
}
3833

3934
func InsertAndVerifyCQLData(ctx context.Context, hosts []string, options ...utils.DataInserterOption) *utils.DataInserter {
40-
framework.By("Inserting data")
41-
di, err := utils.NewDataInserter(hosts, options...)
42-
o.Expect(err).NotTo(o.HaveOccurred())
43-
44-
InsertAndVerifyCQLDataUsingDataInserter(ctx, di)
45-
return di
35+
return scylladbdatacenterverification.InsertAndVerifyCQLData(ctx, hosts, options...)
4636
}
4737

4838
func InsertAndVerifyCQLDataByDC(ctx context.Context, hosts map[string][]string) *utils.DataInserter {
@@ -54,13 +44,7 @@ func InsertAndVerifyCQLDataByDC(ctx context.Context, hosts map[string][]string)
5444
}
5545

5646
func InsertAndVerifyCQLDataUsingDataInserter(ctx context.Context, di *utils.DataInserter) *utils.DataInserter {
57-
framework.By("Inserting data")
58-
err := di.Insert()
59-
o.Expect(err).NotTo(o.HaveOccurred())
60-
61-
VerifyCQLData(ctx, di)
62-
63-
return di
47+
return scylladbdatacenterverification.InsertAndVerifyCQLDataUsingDataInserter(ctx, di)
6448
}
6549

6650
type VerifyCQLConnectionConfigsOptions struct {

test/e2e/utils/verification/scylladbcluster/cql.go

Lines changed: 5 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,14 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"sort"
8+
79
"github.com/scylladb/scylla-operator/pkg/api/scylla/v1alpha1"
810
"github.com/scylladb/scylla-operator/pkg/helpers/slices"
911
"github.com/scylladb/scylla-operator/pkg/naming"
1012
"github.com/scylladb/scylla-operator/test/e2e/framework"
11-
v1alpha2 "github.com/scylladb/scylla-operator/test/e2e/utils/v1alpha1"
13+
utilsv1alpha1 "github.com/scylladb/scylla-operator/test/e2e/utils/v1alpha1"
1214
"k8s.io/apimachinery/pkg/apis/meta/v1"
13-
"k8s.io/apimachinery/pkg/util/wait"
14-
v2 "k8s.io/client-go/kubernetes/typed/core/v1"
15-
"reflect"
16-
"sort"
17-
"strings"
18-
"time"
1915
)
2016

2117
func WaitForFullQuorum(ctx context.Context, rkcClusterMap map[string]framework.ClusterInterface, sc *v1alpha1.ScyllaDBCluster) error {
@@ -46,7 +42,7 @@ func WaitForFullQuorum(ctx context.Context, rkcClusterMap map[string]framework.C
4642
continue
4743
}
4844

49-
hosts, err := v1alpha2.GetBroadcastAddresses(ctx, clusterClient.KubeAdminClient().CoreV1(), sdc)
45+
hosts, err := utilsv1alpha1.GetBroadcastAddresses(ctx, clusterClient.KubeAdminClient().CoreV1(), sdc)
5046
if err != nil {
5147
return fmt.Errorf("can't get broadcast addresses for ScyllaDBDatacenter %q: %w", naming.ObjRef(sdc), err)
5248
}
@@ -83,7 +79,7 @@ func WaitForFullQuorum(ctx context.Context, rkcClusterMap map[string]framework.C
8379
continue
8480
}
8581

86-
err = waitForFullQuorum(ctx, clusterClient.KubeAdminClient().CoreV1(), sdc, sortedAllBroadcastAddresses)
82+
err = utilsv1alpha1.WaitForFullQuorum(ctx, clusterClient.KubeAdminClient().CoreV1(), sdc, sortedAllBroadcastAddresses)
8783
if err != nil {
8884
errs = append(errs, err)
8985
}
@@ -98,51 +94,3 @@ func WaitForFullQuorum(ctx context.Context, rkcClusterMap map[string]framework.C
9894

9995
return nil
10096
}
101-
102-
// TODO: Should be unified with function coming from test/helpers once e2e's there starts using ScyllaDBDatacenter API.
103-
func waitForFullQuorum(ctx context.Context, client v2.CoreV1Interface, sdc *v1alpha1.ScyllaDBDatacenter, sortedExpectedHosts []string) error {
104-
scyllaClient, hosts, err := v1alpha2.GetScyllaClient(ctx, client, sdc)
105-
if err != nil {
106-
return fmt.Errorf("can't get scylla client: %w", err)
107-
}
108-
defer scyllaClient.Close()
109-
110-
// Wait for node status to propagate and reach consistency.
111-
// This can take a while so let's set a large enough timeout to avoid flakes.
112-
return wait.PollImmediateWithContext(ctx, 1*time.Second, 5*time.Minute, func(ctx context.Context) (done bool, err error) {
113-
allSeeAllAsUN := true
114-
infoMessages := make([]string, 0, len(hosts))
115-
var errs []error
116-
for _, h := range hosts {
117-
s, err := scyllaClient.Status(ctx, h)
118-
if err != nil {
119-
return true, fmt.Errorf("can't get scylla status on node %q: %w", h, err)
120-
}
121-
122-
sHosts := s.Hosts()
123-
sort.Strings(sHosts)
124-
if !reflect.DeepEqual(sHosts, sortedExpectedHosts) {
125-
errs = append(errs, fmt.Errorf("node %q thinks the cluster consists of different nodes: %s", h, sHosts))
126-
}
127-
128-
downHosts := s.DownHosts()
129-
infoMessages = append(infoMessages, fmt.Sprintf("Node %q, down: %q, up: %q", h, strings.Join(downHosts, "\n"), strings.Join(s.LiveHosts(), ",")))
130-
131-
if len(downHosts) != 0 {
132-
allSeeAllAsUN = false
133-
}
134-
}
135-
136-
if !allSeeAllAsUN {
137-
framework.Infof("ScyllaDB nodes have not reached status consistency yet. Statuses:\n%s", strings.Join(infoMessages, ","))
138-
}
139-
140-
err = errors.Join(errs...)
141-
if err != nil {
142-
framework.Infof("ScyllaDB nodes encountered an error. Statuses:\n%s", strings.Join(infoMessages, ","))
143-
return true, err
144-
}
145-
146-
return allSeeAllAsUN, nil
147-
})
148-
}

0 commit comments

Comments
 (0)