Skip to content

Commit 5da1317

Browse files
e2e tests for backup/restore (#220)
1 parent c0c2d57 commit 5da1317

File tree

3 files changed

+349
-0
lines changed

3 files changed

+349
-0
lines changed

test/e2e/backup_restore_test.go

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
package e2e
2+
3+
import (
4+
"crypto/tls"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"os"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
http_helper "github.com/gruntwork-io/terratest/modules/http-helper"
14+
"github.com/gruntwork-io/terratest/modules/k8s"
15+
"github.com/gruntwork-io/terratest/modules/random"
16+
"github.com/imroc/req/v3"
17+
"github.com/marklogic/marklogic-kubernetes/test/testUtil"
18+
"github.com/stretchr/testify/assert"
19+
"github.com/tidwall/gjson"
20+
)
21+
22+
type BackupRestoreReq struct {
23+
Operation string `json:"operation"`
24+
BackupDir string `json:"backup-dir"`
25+
IncludeReplicas string `json:"include-replicas"`
26+
Incremental string `json:"incremental,omitempty"`
27+
IncrementalDir string `json:"incremental-dir,omitempty"`
28+
}
29+
type BackupRestoreStatusReq struct {
30+
Operation string `json:"operation"`
31+
JobID string `json:"job-id"`
32+
HostName string `json:"host-name,omitempty"`
33+
}
34+
35+
func PutDocs(docPath string, docName string, client *req.Client, qConsoleEndpoint string) (string, error) {
36+
result := ""
37+
xmlData, err := os.ReadFile(docPath + docName)
38+
if err != nil {
39+
return result, err
40+
}
41+
strXMLData := string(xmlData)
42+
43+
resp, err := client.R().
44+
SetContentType("application/json").
45+
SetBodyString(strXMLData).
46+
Put(qConsoleEndpoint)
47+
if err != nil {
48+
return result, err
49+
}
50+
if resp.GetStatusCode() == 201 {
51+
result = "Created"
52+
}
53+
return result, err
54+
}
55+
func GetDocs(client *req.Client, getEndpoint string, acceptHeader string) (string, error) {
56+
resp, err := client.R().
57+
SetContentType("application/json").
58+
SetHeader("Accept", acceptHeader).
59+
Get(getEndpoint)
60+
if err != nil {
61+
return "", err
62+
}
63+
defer resp.Body.Close()
64+
65+
bodyXML, err := io.ReadAll(resp.Body)
66+
if err != nil {
67+
return "", err
68+
}
69+
return string(bodyXML), err
70+
}
71+
72+
func DeleteDocs(client *req.Client, deleteEndpoint string) (string, error) {
73+
result := ""
74+
resp, err := client.R().
75+
SetContentType("application/json").
76+
Delete(deleteEndpoint)
77+
if err != nil {
78+
return result, err
79+
}
80+
defer resp.Body.Close()
81+
82+
if resp.GetStatusCode() == 204 {
83+
result = "Deleted"
84+
}
85+
return result, err
86+
}
87+
88+
func RunRequests(client *req.Client, dbReq string, hostsEndpoint string) (string, error) {
89+
var err error
90+
var body []byte
91+
headerMap := map[string]string{
92+
"Content-Type": "application/json",
93+
"Accept": "application/json",
94+
}
95+
result := ""
96+
status := ""
97+
operation := (gjson.Get(dbReq, `operation`)).Str
98+
var retryFn = (func(resp *req.Response, err error) bool {
99+
if err != nil {
100+
fmt.Println(err.Error())
101+
}
102+
body, err = io.ReadAll(resp.Body)
103+
if err != nil {
104+
fmt.Println(err.Error())
105+
}
106+
result = (string(body))
107+
return true
108+
})
109+
110+
if operation == "backup-status" {
111+
retryFn = (func(resp *req.Response, err error) bool {
112+
if err != nil {
113+
fmt.Printf("error: %s", err.Error())
114+
}
115+
body, _ := io.ReadAll(resp.Body)
116+
status = (gjson.Get(string(body), `status`)).Str
117+
if status != "completed" {
118+
fmt.Println("Waiting for backup to be completed")
119+
}
120+
result = (string(body))
121+
return status != "completed"
122+
})
123+
}
124+
125+
resp, err := client.R().
126+
AddRetryCondition(retryFn).
127+
SetHeaders(headerMap).
128+
SetBodyString(dbReq).
129+
Post(hostsEndpoint)
130+
if err != nil {
131+
return "", err
132+
}
133+
defer resp.Body.Close()
134+
135+
return result, err
136+
}
137+
138+
func TestMlDbBackupRestore(t *testing.T) {
139+
// var resp *http.Response
140+
141+
var err error
142+
var podName string
143+
imageRepo, repoPres := os.LookupEnv("dockerRepository")
144+
imageTag, tagPres := os.LookupEnv("dockerVersion")
145+
146+
if !repoPres {
147+
imageRepo = "ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-centos"
148+
t.Logf("No imageRepo variable present, setting to default value: " + imageRepo)
149+
}
150+
151+
if !tagPres {
152+
imageTag = "11.1.0-centos-1.1.2"
153+
t.Logf("No imageTag variable present, setting to default value: " + imageTag)
154+
}
155+
156+
username := "admin"
157+
password := "admin"
158+
159+
options := map[string]string{
160+
"persistence.enabled": "true",
161+
"replicaCount": "1",
162+
"image.repository": imageRepo,
163+
"image.tag": imageTag,
164+
"auth.adminUsername": username,
165+
"auth.adminPassword": password,
166+
"logCollection.enabled": "false",
167+
}
168+
169+
t.Logf("====Installing Helm Chart")
170+
releaseName := "bkuprestore"
171+
172+
namespaceName := "ml-" + strings.ToLower(random.UniqueId())
173+
kubectlOptions := k8s.NewKubectlOptions("", "", namespaceName)
174+
175+
t.Logf("====Creating namespace: " + namespaceName)
176+
k8s.CreateNamespace(t, kubectlOptions, namespaceName)
177+
178+
defer t.Logf("====Deleting namespace: " + namespaceName)
179+
defer k8s.DeleteNamespace(t, kubectlOptions, namespaceName)
180+
181+
podName = testUtil.HelmInstall(t, options, releaseName, kubectlOptions)
182+
183+
t.Logf("====Describe pod for backup restore test")
184+
k8s.RunKubectl(t, kubectlOptions, "describe", "pod", podName)
185+
186+
tlsConfig := tls.Config{}
187+
// wait until the pod is in Ready status
188+
k8s.WaitUntilPodAvailable(t, kubectlOptions, podName, 10, 15*time.Second)
189+
190+
tunnel7997 := k8s.NewTunnel(kubectlOptions, k8s.ResourceTypePod, podName, 7997, 7997)
191+
defer tunnel7997.Close()
192+
tunnel7997.ForwardPort(t)
193+
endpoint7997 := fmt.Sprintf("http://%s", tunnel7997.Endpoint())
194+
195+
// verify if 7997 health check endpoint returns 200
196+
http_helper.HttpGetWithRetryWithCustomValidation(
197+
t,
198+
endpoint7997,
199+
&tlsConfig,
200+
10,
201+
15*time.Second,
202+
func(statusCode int, body string) bool {
203+
return statusCode == 200
204+
},
205+
)
206+
207+
//create backup directories and setup permissions
208+
k8s.RunKubectl(t, kubectlOptions, "exec", podName, "--", "/bin/bash", "-c", "cd /tmp && mkdir backup && chmod 777 backup && mkdir backup/incrBackup && chmod 777 backup/incrBackup")
209+
210+
//set test data path and documents for tests
211+
docPath := "../test_data/bkup_restore_reqs/"
212+
docs := []string{"testOne.xml", "testTwo.xml"}
213+
214+
tunnel8000 := k8s.NewTunnel(kubectlOptions, k8s.ResourceTypePod, podName, 8000, 8000)
215+
defer tunnel8000.Close()
216+
tunnel8000.ForwardPort(t)
217+
218+
client := req.C().
219+
SetCommonDigestAuth(username, password).
220+
SetCommonRetryCount(10).
221+
SetCommonRetryFixedInterval(10 * time.Second)
222+
223+
//creating documents in the Documents DB
224+
for _, doc := range docs {
225+
qConsoleEndpoint := fmt.Sprintf("http://%s/v1/documents?database=Documents&uri=%s", tunnel8000.Endpoint(), doc)
226+
fmt.Println(qConsoleEndpoint)
227+
result, err := PutDocs(docPath, doc, client, qConsoleEndpoint)
228+
if err != nil {
229+
t.Fatalf(err.Error())
230+
}
231+
assert.Equal(t, "Created", result)
232+
}
233+
234+
getEndpoint := fmt.Sprintf("http://%s/v1/documents?database=Documents&uri=%s&uri=%s", tunnel8000.Endpoint(), docs[0], docs[1])
235+
fmt.Println(getEndpoint)
236+
237+
// verify both docs are loaded in Documents DB
238+
result, err := GetDocs(client, getEndpoint, "multipart/mixed")
239+
if err != nil {
240+
t.Fatalf(err.Error())
241+
}
242+
if !strings.Contains(string(result), "<b>two</b>") && !strings.Contains(string(result), "<a>one</a>") {
243+
t.Errorf("Both docs are loaded")
244+
}
245+
246+
tunnel8002 := k8s.NewTunnel(kubectlOptions, k8s.ResourceTypePod, podName, 8002, 8002)
247+
defer tunnel8002.Close()
248+
tunnel8002.ForwardPort(t)
249+
manageEndpoint := fmt.Sprintf("http://%s/manage/v2/databases/Documents", tunnel8002.Endpoint())
250+
fmt.Println(manageEndpoint)
251+
252+
bkupReq := &BackupRestoreReq{
253+
Operation: "backup-database",
254+
BackupDir: "/tmp/backup",
255+
IncludeReplicas: "true"}
256+
bkupReqRes, _ := json.Marshal(bkupReq)
257+
258+
//full backup for Documents DB
259+
result, err = RunRequests(client, string(bkupReqRes), manageEndpoint)
260+
if err != nil {
261+
t.Fatalf(err.Error())
262+
}
263+
jobID := (gjson.Get(result, `job-id`))
264+
hostName := (gjson.Get(result, `host-name`))
265+
266+
bkupStatusReq := &BackupRestoreStatusReq{
267+
Operation: "backup-status",
268+
JobID: jobID.String(),
269+
HostName: hostName.String()}
270+
bkupStatusReqRes, _ := json.Marshal(bkupStatusReq)
271+
272+
//get status of full backup job
273+
result, err = RunRequests(client, string(bkupStatusReqRes), manageEndpoint)
274+
if err != nil {
275+
t.Fatalf(err.Error())
276+
}
277+
bkupStatus := (gjson.Get(result, `status`)).Str
278+
279+
//verify full backup is completed
280+
assert.Equal(t, "completed", bkupStatus)
281+
282+
deleteEndpoint := fmt.Sprintf("http://%s/v1/documents?database=Documents&uri=%s", tunnel8000.Endpoint(), docs[1])
283+
fmt.Println(deleteEndpoint)
284+
285+
//incremental backup
286+
incrBkupReq := &BackupRestoreReq{
287+
Operation: "backup-database",
288+
BackupDir: "/tmp/backup",
289+
IncludeReplicas: "true",
290+
Incremental: "true",
291+
IncrementalDir: "/tmp/backup/incrBackup"}
292+
incrBkupReqRes, _ := json.Marshal(incrBkupReq)
293+
294+
//incremnetal backup for Documents DB
295+
result, err = RunRequests(client, string(incrBkupReqRes), manageEndpoint)
296+
if err != nil {
297+
t.Fatalf(err.Error())
298+
}
299+
jobID = (gjson.Get(result, `job-id`))
300+
hostName = (gjson.Get(result, `host-name`))
301+
302+
incrBkupStatusReq := &BackupRestoreStatusReq{
303+
Operation: "backup-status",
304+
JobID: jobID.String(),
305+
HostName: hostName.String()}
306+
incrBkupStatusReqRes, _ := json.Marshal(incrBkupStatusReq)
307+
308+
//get status of backup job
309+
result, err = RunRequests(client, string(incrBkupStatusReqRes), manageEndpoint)
310+
if err != nil {
311+
t.Fatalf(err.Error())
312+
}
313+
incrBkupStatus := (gjson.Get(result, `status`)).Str
314+
315+
//verify backup is completed
316+
assert.Equal(t, "completed", incrBkupStatus)
317+
318+
//delete a document from Documents DB
319+
result, err = DeleteDocs(client, deleteEndpoint)
320+
if err != nil {
321+
t.Fatalf(err.Error())
322+
}
323+
assert.Equal(t, "Deleted", result)
324+
325+
rstrReq := &BackupRestoreReq{
326+
Operation: "restore-database",
327+
BackupDir: "/tmp/backup",
328+
IncludeReplicas: "true"}
329+
brstrReqRes, _ := json.Marshal(rstrReq)
330+
331+
//restore Documents DB from incremental backup
332+
result, err = RunRequests(client, string(brstrReqRes), manageEndpoint)
333+
if err != nil {
334+
t.Fatalf(err.Error())
335+
}
336+
restoreJobID := (gjson.Get(result, `job-id`)).Str
337+
assert.NotEqual(t, "", restoreJobID)
338+
339+
result, err = GetDocs(client, getEndpoint, "multipart/mixed")
340+
if err != nil {
341+
t.Fatalf(err.Error())
342+
}
343+
// verify both docs are restored
344+
if !strings.Contains(string(result), "<b>two</b>") && !strings.Contains(string(result), "<a>one</a>") {
345+
t.Errorf("Both docs are restored")
346+
}
347+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<a>one</a>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<b>two</b>

0 commit comments

Comments
 (0)