Skip to content

Commit 781be47

Browse files
authored
Merge pull request #347 from arangodb/feature/upgrade-tests
Feature/upgrade tests
2 parents 8c42676 + 1912157 commit 781be47

File tree

6 files changed

+373
-31
lines changed

6 files changed

+373
-31
lines changed

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ RELEASE := $(GOBUILDDIR)/bin/release
9292
GHRELEASE := $(GOBUILDDIR)/bin/github-release
9393

9494
TESTLENGTHOPTIONS := -test.short
95-
TESTTIMEOUT := 20m
95+
TESTTIMEOUT := 30m
9696
ifeq ($(LONG), 1)
9797
TESTLENGTHOPTIONS :=
9898
TESTTIMEOUT := 180m
@@ -294,6 +294,10 @@ $(TESTBIN): $(GOBUILDDIR) $(SOURCES)
294294
docker-test: $(TESTBIN)
295295
docker build --quiet -f $(DOCKERTESTFILE) -t $(TESTIMAGE) .
296296

297+
.PHONY: run-upgrade-tests
298+
run-upgrade-tests:
299+
TESTOPTIONS="-test.run=TestUpgrade" make run-tests
300+
297301
.PHONY: run-tests
298302
run-tests: docker-test
299303
ifdef PUSHIMAGES
@@ -311,7 +315,7 @@ endif
311315
kubectl apply -f $(MANIFESTPATHTEST)
312316
$(ROOTDIR)/scripts/kube_create_storage.sh $(DEPLOYMENTNAMESPACE)
313317
$(ROOTDIR)/scripts/kube_create_license_key_secret.sh "$(DEPLOYMENTNAMESPACE)" '$(ENTERPRISELICENSE)'
314-
$(ROOTDIR)/scripts/kube_run_tests.sh $(DEPLOYMENTNAMESPACE) $(TESTIMAGE) "$(ARANGODIMAGE)" '$(ENTERPRISEIMAGE)' $(TESTTIMEOUT) $(TESTLENGTHOPTIONS)
318+
$(ROOTDIR)/scripts/kube_run_tests.sh $(DEPLOYMENTNAMESPACE) $(TESTIMAGE) "$(ARANGODIMAGE)" '$(ENTERPRISEIMAGE)' $(TESTTIMEOUT) $(TESTLENGTHOPTIONS) $(TESTOPTIONS)
315319

316320
$(DURATIONTESTBIN): $(GOBUILDDIR) $(SOURCES)
317321
@mkdir -p $(BINDIR)

manifests/templates/test/rbac.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ rules:
1313
resources: ["pods", "services", "persistentvolumes", "persistentvolumeclaims", "secrets", "serviceaccounts"]
1414
verbs: ["*"]
1515
- apiGroups: ["apps"]
16-
resources: ["daemonsets"]
16+
resources: ["daemonsets", "deployments"]
1717
verbs: ["*"]
1818

1919
---

tests/environments_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ func TestEnvironmentProduction(t *testing.T) {
6262
depl.Spec.Environment = api.NewEnvironment(api.EnvironmentProduction)
6363
depl.Spec.DBServers.Count = util.NewInt(numNodes + 1)
6464
depl.Spec.SetDefaults(depl.GetName()) // this must be last
65+
66+
// This test failes to validate the spec if no image is set explicitly because this is required in production mode
67+
if depl.Spec.Image == nil {
68+
depl.Spec.Image = util.NewString("arangodb/arangodb:latest")
69+
}
6570
assert.NoError(t, depl.Spec.Validate())
6671

6772
dbserverCount := depl.Spec.DBServers.GetCount()

tests/operator_upgrade_test.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package tests
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
appsv1 "k8s.io/api/apps/v1"
9+
"k8s.io/api/core/v1"
10+
"k8s.io/apimachinery/pkg/fields"
11+
12+
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha"
13+
kubeArangoClient "github.com/arangodb/kube-arangodb/pkg/client"
14+
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
15+
"github.com/arangodb/kube-arangodb/pkg/util/retry"
16+
"github.com/dchest/uniuri"
17+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
watch "k8s.io/apimachinery/pkg/watch"
19+
"k8s.io/client-go/kubernetes"
20+
)
21+
22+
const (
23+
operatorTestDeploymentName string = "arango-deployment-operator"
24+
oldOperatorTestImage string = "arangodb/kube-arangodb:0.3.7"
25+
)
26+
27+
func TestOperatorUpgradeFrom038(t *testing.T) {
28+
ns := getNamespace(t)
29+
kubecli := mustNewKubeClient(t)
30+
c := kubeArangoClient.MustNewInCluster()
31+
32+
if err := waitForArangoDBPodsGone(ns, kubecli); err != nil {
33+
t.Fatalf("Remaining arangodb pods did not vanish, can not start test: %v", err)
34+
}
35+
36+
currentimage, err := updateOperatorImage(t, ns, kubecli, oldOperatorTestImage)
37+
if err != nil {
38+
t.Fatalf("Could not replace operator with old image: %v", err)
39+
}
40+
defer updateOperatorImage(t, ns, kubecli, currentimage)
41+
42+
if err := waitForOperatorImage(ns, kubecli, oldOperatorTestImage); err != nil {
43+
t.Fatalf("Old Operator not ready in time: %v", err)
44+
}
45+
46+
depl := newDeployment(fmt.Sprintf("opup-%s", uniuri.NewLen(4)))
47+
depl.Spec.TLS = api.TLSSpec{} // should auto-generate cert
48+
depl.Spec.SetDefaults(depl.GetName()) // this must be last
49+
50+
// Create deployment
51+
if _, err := c.DatabaseV1alpha().ArangoDeployments(ns).Create(depl); err != nil {
52+
t.Fatalf("Create deployment failed: %v", err)
53+
}
54+
defer removeDeployment(c, depl.GetName(), ns)
55+
56+
// Wait for deployment to be ready
57+
_, err = waitUntilDeployment(c, depl.GetName(), ns, deploymentIsReady())
58+
if err != nil {
59+
t.Fatalf("Deployment not running in time: %v", err)
60+
}
61+
62+
podsWatcher, err := kubecli.CoreV1().Pods(ns).Watch(metav1.ListOptions{
63+
LabelSelector: fields.OneTermEqualSelector("app", "arangodb").String(),
64+
})
65+
if err != nil {
66+
t.Fatalf("Failed to watch pods: %v", err)
67+
}
68+
defer podsWatcher.Stop()
69+
70+
errorChannel := make(chan error)
71+
go func() {
72+
var addedPods []string
73+
for {
74+
select {
75+
case ev, ok := <-podsWatcher.ResultChan():
76+
if !ok {
77+
return // Abort
78+
}
79+
if pod, ok := ev.Object.(*v1.Pod); ok {
80+
if k8sutil.IsArangoDBImageIDAndVersionPod(*pod) {
81+
continue
82+
}
83+
84+
switch ev.Type {
85+
case watch.Modified:
86+
if !k8sutil.IsPodReady(pod) {
87+
errorChannel <- fmt.Errorf("Pod no longer ready: %s", pod.GetName())
88+
}
89+
break
90+
case watch.Deleted:
91+
errorChannel <- fmt.Errorf("Pod was deleted: %s", pod.GetName())
92+
break
93+
case watch.Added:
94+
if len(addedPods) >= 9 {
95+
errorChannel <- fmt.Errorf("New pod was created: %s", pod.GetName())
96+
}
97+
addedPods = append(addedPods, pod.GetName())
98+
break
99+
}
100+
}
101+
}
102+
}
103+
}()
104+
105+
if _, err := updateOperatorImage(t, ns, kubecli, currentimage); err != nil {
106+
t.Fatalf("Failed to replace new ")
107+
}
108+
109+
if err := waitForOperatorImage(ns, kubecli, currentimage); err != nil {
110+
t.Fatalf("New operator not ready in time: %v", err)
111+
}
112+
113+
select {
114+
case <-time.After(1 * time.Minute):
115+
break // cool
116+
case err := <-errorChannel:
117+
// not cool
118+
t.Errorf("Deployment had error: %v", err)
119+
}
120+
}
121+
122+
func updateOperatorImage(t *testing.T, ns string, kube kubernetes.Interface, newImage string) (string, error) {
123+
for {
124+
depl, err := kube.AppsV1().Deployments(ns).Get(operatorTestDeploymentName, metav1.GetOptions{})
125+
if err != nil {
126+
return "", err
127+
}
128+
old, err := getOperatorImage(depl)
129+
if err != nil {
130+
return "", err
131+
}
132+
setOperatorImage(depl, newImage)
133+
if _, err := kube.AppsV1().Deployments(ns).Update(depl); k8sutil.IsConflict(err) {
134+
continue
135+
} else if err != nil {
136+
return "", err
137+
}
138+
return old, nil
139+
}
140+
}
141+
142+
func updateOperatorDeployment(ns string, kube kubernetes.Interface) (*appsv1.Deployment, error) {
143+
return kube.AppsV1().Deployments(ns).Get(operatorTestDeploymentName, metav1.GetOptions{})
144+
}
145+
146+
func getOperatorImage(depl *appsv1.Deployment) (string, error) {
147+
for _, c := range depl.Spec.Template.Spec.Containers {
148+
if c.Name == "operator" {
149+
return c.Image, nil
150+
}
151+
}
152+
153+
return "", fmt.Errorf("Operator container not found")
154+
}
155+
156+
func setOperatorImage(depl *appsv1.Deployment, image string) {
157+
for i := range depl.Spec.Template.Spec.Containers {
158+
c := &depl.Spec.Template.Spec.Containers[i]
159+
if c.Name == "operator" {
160+
c.Image = image
161+
}
162+
}
163+
}
164+
165+
func waitForArangoDBPodsGone(ns string, kube kubernetes.Interface) error {
166+
return retry.Retry(func() error {
167+
_, err := kube.CoreV1().Pods(ns).List(metav1.ListOptions{
168+
LabelSelector: fields.OneTermEqualSelector("app", "arangodb").String(),
169+
})
170+
if k8sutil.IsNotFound(err) {
171+
return nil
172+
}
173+
return err
174+
}, deploymentReadyTimeout)
175+
}
176+
177+
func waitForOperatorImage(ns string, kube kubernetes.Interface, image string) error {
178+
return retry.Retry(func() error {
179+
pods, err := kube.CoreV1().Pods(ns).List(metav1.ListOptions{
180+
LabelSelector: fields.OneTermEqualSelector("app", operatorTestDeploymentName).String(),
181+
})
182+
if err != nil {
183+
return err
184+
}
185+
for _, pod := range pods.Items {
186+
for _, c := range pod.Spec.Containers {
187+
if c.Name == "operator" {
188+
if c.Image != image {
189+
return fmt.Errorf("in pod %s found image %s, expected %s", pod.Name, c.Image, image)
190+
}
191+
}
192+
}
193+
}
194+
return nil
195+
}, deploymentReadyTimeout)
196+
}

tests/test_util.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ import (
5656
)
5757

5858
const (
59-
deploymentReadyTimeout = time.Minute * 4
59+
deploymentReadyTimeout = time.Minute * 4
60+
deploymentUpgradeTimeout = time.Minute * 20
6061
)
6162

6263
var (
@@ -344,7 +345,7 @@ func waitUntilSecretNotFound(cli kubernetes.Interface, secretName, ns string, ti
344345

345346
// waitUntilClusterHealth waits until an arango cluster
346347
// reached a state where the given predicate returns nil.
347-
func waitUntilClusterHealth(cli driver.Client, predicate func(driver.ClusterHealth) error) error {
348+
func waitUntilClusterHealth(cli driver.Client, predicate func(driver.ClusterHealth) error, timeout ...time.Duration) error {
348349
ctx := context.Background()
349350
op := func() error {
350351
cluster, err := cli.Cluster(ctx)
@@ -362,12 +363,30 @@ func waitUntilClusterHealth(cli driver.Client, predicate func(driver.ClusterHeal
362363
}
363364
return nil
364365
}
365-
if err := retry.Retry(op, deploymentReadyTimeout); err != nil {
366+
actualTimeout := deploymentReadyTimeout
367+
if len(timeout) > 0 {
368+
actualTimeout = timeout[0]
369+
}
370+
if err := retry.Retry(op, actualTimeout); err != nil {
366371
return maskAny(err)
367372
}
368373
return nil
369374
}
370375

376+
// waitUntilClusterVersionUp waits until an arango cluster is healthy and
377+
// all servers are running the given version.
378+
func waitUntilClusterVersionUp(cli driver.Client, version driver.Version) error {
379+
return waitUntilClusterHealth(cli, func(h driver.ClusterHealth) error {
380+
for s, r := range h.Health {
381+
if cmp := r.Version.CompareTo(version); cmp != 0 {
382+
return maskAny(fmt.Errorf("Member %s has version %s, expecting %s", s, r.Version, version))
383+
}
384+
}
385+
386+
return nil
387+
}, deploymentUpgradeTimeout)
388+
}
389+
371390
// waitUntilVersionUp waits until the arango database responds to
372391
// an `/_api/version` request without an error. An additional Predicate
373392
// can do a check on the VersionInfo object returned by the server.
@@ -552,16 +571,24 @@ func removeSecret(cli kubernetes.Interface, secretName, ns string) error {
552571

553572
// check if a deployment is up and has reached a state where it is able to answer to /_api/version requests.
554573
// Optionally the returned version can be checked against a user provided version
555-
func waitUntilArangoDeploymentHealthy(deployment *api.ArangoDeployment, DBClient driver.Client, k8sClient kubernetes.Interface, versionString string) error {
574+
func waitUntilArangoDeploymentHealthy(deployment *api.ArangoDeployment, DBClient driver.Client, k8sClient kubernetes.Interface, versionString driver.Version) error {
556575
// deployment checks
557576
var checkVersionPredicate func(driver.VersionInfo) error
558577
if len(versionString) > 0 {
559-
checkVersionPredicate = createEqualVersionsPredicate(driver.Version(versionString))
578+
checkVersionPredicate = createEqualVersionsPredicate(versionString)
560579
}
561580
switch mode := deployment.Spec.GetMode(); mode {
562581
case api.DeploymentModeCluster:
563582
// Wait for cluster to be completely ready
564583
if err := waitUntilClusterHealth(DBClient, func(h driver.ClusterHealth) error {
584+
if len(versionString) > 0 {
585+
for s, r := range h.Health {
586+
if cmp := r.Version.CompareTo(versionString); cmp != 0 {
587+
return maskAny(fmt.Errorf("Member %s has version %s, expecting %s", s, r.Version, versionString))
588+
}
589+
}
590+
}
591+
565592
return clusterHealthEqualsSpec(h, deployment.Spec)
566593
}); err != nil {
567594
return maskAny(fmt.Errorf("Cluster not running in expected health in time: %s", err))

0 commit comments

Comments
 (0)