Skip to content

Commit 47f16cb

Browse files
committed
Merge branch 'develop' into feature/CLD-502
2 parents 3533da3 + df75e11 commit 47f16cb

File tree

5 files changed

+280
-0
lines changed

5 files changed

+280
-0
lines changed

charts/templates/configmap.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ metadata:
66
labels:
77
{{- include "marklogic.labels" . | nindent 4 }}
88
data:
9+
{{- if ne .Values.bootstrapHostName "" }}
10+
MARKLOGIC_BOOTSTRAP_HOST: {{ .Values.bootstrapHostName }}
11+
{{- else }}
912
MARKLOGIC_BOOTSTRAP_HOST: {{ include "marklogic.fullname" . }}-0
13+
{{- end }}
1014
MARKLOGIC_FQDN_SUFFIX: {{ include "marklogic.headlessURL" . }}
1115
MARKLOGIC_INIT: "true"
1216
MARKLOGIC_JOIN_CLUSTER: "true"

charts/templates/statefulset.yaml

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,69 @@ spec:
2121
affinity: {{- toYaml . | nindent 8}}
2222
{{- end }}
2323
terminationGracePeriodSeconds: {{ .Values.terminationGracePeriod }}
24+
{{- if ne .Values.bootstrapHostName "" }}
25+
initContainers:
26+
- name: configure-group
27+
image: "{{ .Values.initContainerImage.repository }}:{{ .Values.initContainerImage.tag }}"
28+
command:
29+
- sh
30+
- '-c'
31+
- |
32+
log () {
33+
local TIMESTAMP=$(date +"%Y-%m-%d %T.%3N")
34+
echo "${TIMESTAMP} $@"
35+
}
36+
log "Info: [initContainer] Begin configure-group execution"
37+
if [[ $POD_NAME != *-0 ]]; then
38+
log "Info: [initContainer] Skipping creation of group $MARKLOGIC_GROUP as $POD_NAME is not the first node in the group."
39+
exit 0
40+
fi
41+
HOST_RESP_CODE=`curl --anyauth -m 20 -s -o /dev/null -w "%{http_code}" -X GET http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/hosts --anyauth --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD}`
42+
if [[ ${HOST_RESP_CODE} -ne 200 ]]; then
43+
log "Error: [initContainer] Bootstrap host $MARKLOGIC_BOOTSTRAP_HOST not found, exiting Init container."
44+
exit 1
45+
fi
46+
GROUP_CFG_TEMPLATE='{"group-name":"%s"}'
47+
GROUP_CFG=$(printf "$GROUP_CFG_TEMPLATE" "$MARKLOGIC_GROUP")
48+
GROUP_RESP_CODE=`curl --anyauth -m 20 -s -o /dev/null -w "%{http_code}" -X GET http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups/${MARKLOGIC_GROUP} --anyauth --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD}`
49+
if [[ ${GROUP_RESP_CODE} -eq 200 ]]; then
50+
log "Info: [initContainer] Skipping creation of group $MARKLOGIC_GROUP as it already exists on the MarkLogic cluster."
51+
exit 0
52+
fi
53+
log "Info: [initContainer] Group $MARKLOGIC_GROUP does not exist, configuring group $MARKLOGIC_GROUP on the MarkLogic cluster."
54+
for i in $(seq 1 5); do
55+
res_code=`curl --anyauth --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD} -m 20 -s -w '%{http_code}' -X POST -d "${GROUP_CFG}" -H "Content-type: application/json" http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups`
56+
if [[ ${res_code} -eq 201 ]]; then
57+
log "Info: [initContainer] Successfully configured group $MARKLOGIC_GROUP on the MarkLogic cluster."
58+
break
59+
else
60+
log "Info: [initContainer] Configure group $MARKLOGIC_GROUP retry attempt $i "
61+
log "Info: [initContainer] Expected response code 202, got $res_code"
62+
sleep 10s
63+
fi
64+
[ $i -eq 5 ] && exit 1
65+
done
66+
env:
67+
- name: MARKLOGIC_GROUP
68+
value: {{ .Values.group.name }}
69+
- name: MARKLOGIC_ADMIN_USERNAME
70+
valueFrom:
71+
secretKeyRef:
72+
name: {{ include "marklogic.fullname" . }}
73+
key: username
74+
- name: MARKLOGIC_ADMIN_PASSWORD
75+
valueFrom:
76+
secretKeyRef:
77+
name: {{ include "marklogic.fullname" . }}
78+
key: password
79+
- name: POD_NAME
80+
valueFrom:
81+
fieldRef:
82+
fieldPath: metadata.name
83+
envFrom:
84+
- configMapRef:
85+
name: {{ include "marklogic.fullname" . }}
86+
{{- end }}
2487
containers:
2588
- name: marklogic-server
2689
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
@@ -42,6 +105,12 @@ spec:
42105
secretKeyRef:
43106
name: {{ include "marklogic.fullname" . }}
44107
key: password
108+
- name: MARKLOGIC_GROUP
109+
value: {{ .Values.group.name }}
110+
- name: POD_NAME
111+
valueFrom:
112+
fieldRef:
113+
fieldPath: metadata.name
45114
envFrom:
46115
- configMapRef:
47116
name: {{ include "marklogic.fullname" . }}
@@ -62,6 +131,36 @@ spec:
62131
{{- toYaml .Values.extraContainerPorts | nindent 12 }}
63132
{{- end }}
64133
lifecycle:
134+
{{- if eq .Values.bootstrapHostName "" }}
135+
postStart:
136+
exec:
137+
command:
138+
- bash
139+
- '-c'
140+
- |
141+
pid=$(ps aux | grep -i '/bin/bas[h] /usr' | awk {'print $2'})
142+
143+
log () {
144+
local TIMESTAMP=$(date +"%Y-%m-%d %T.%3N")
145+
echo "${TIMESTAMP} $@" > /proc/$pid/fd/1
146+
}
147+
log "Info: [poststart] Begin Poststart Hook Execution"
148+
if [[ $MARKLOGIC_GROUP == "Default" || $POD_NAME != *-0 ]]; then
149+
log "Info: [poststart] This is not a bootstrap host or group specified is Default"
150+
else
151+
while [ ! -f /var/opt/MarkLogic/ready ]; do
152+
sleep 5s
153+
done
154+
155+
GROUP_CFG_TEMPLATE='{"group-name":"%s"}'
156+
GROUP_CFG=$(printf "$GROUP_CFG_TEMPLATE" "$MARKLOGIC_GROUP")
157+
158+
log "Info: [poststart] Updating Default group on cluster"
159+
curl --anyauth -m 20 -s -X PUT -H "Content-type: application/json" -d "${GROUP_CFG}" http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups/Default/properties --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD}
160+
sleep 10s
161+
fi
162+
log "Info: [poststart] Poststart Hook Execution Completed"
163+
{{- end }}
65164
preStop:
66165
exec:
67166
command:

charts/values.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,27 @@ replicaCount: 1
66
# Termination Grace Period
77
terminationGracePeriod: 120
88

9+
# Group related settings
10+
group:
11+
# the group name of the current Marklogic Helm Deployment
12+
name: Default
13+
14+
# The name of the host to join. If not provided, the deployment is a bootstrap host.
15+
bootstrapHostName: ""
16+
17+
918
# Marklogic image parameters
1019
image:
1120
repository: marklogicdb/marklogic-db
1221
tag: latest
1322
pullPolicy: IfNotPresent
1423

24+
# Init container image parameters
25+
initContainerImage:
26+
repository: curlimages/curl
27+
tag: 7.85.0
28+
pullPolicy: IfNotPresent
29+
1530
# Configure the imagePullSecret to pull the image from private repository that requires credential
1631
imagePullSecret: {}
1732
## docker hub registry: https://index.docker.io/v1/

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ require (
4343
github.com/pquerna/otp v1.2.0 // indirect
4444
github.com/russross/blackfriday/v2 v2.1.0 // indirect
4545
github.com/spf13/pflag v1.0.5 // indirect
46+
github.com/tidwall/gjson v1.14.3 // indirect
47+
github.com/tidwall/match v1.1.1 // indirect
48+
github.com/tidwall/pretty v1.2.0 // indirect
4649
github.com/urfave/cli v1.22.2 // indirect
4750
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
4851
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect

test/e2e/separate_nodes_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package e2e
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"net/http"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"github.com/gruntwork-io/terratest/modules/helm"
13+
"github.com/gruntwork-io/terratest/modules/k8s"
14+
"github.com/gruntwork-io/terratest/modules/random"
15+
digest_auth "github.com/xinsnake/go-http-digest-auth-client"
16+
"github.com/tidwall/gjson"
17+
)
18+
19+
func TestSeparateEDnode(t *testing.T) {
20+
// Path to the helm chart we will test
21+
helmChartPath, e := filepath.Abs("../../charts")
22+
if (e != nil) {
23+
t.Fatalf(e.Error())
24+
}
25+
username := "admin"
26+
password := "admin"
27+
var resp *http.Response
28+
var body []byte
29+
var err error
30+
31+
namespaceName := "marklogic-" + strings.ToLower(random.UniqueId())
32+
kubectlOptions := k8s.NewKubectlOptions("", "", namespaceName)
33+
options := &helm.Options{
34+
KubectlOptions: kubectlOptions,
35+
SetValues: map[string]string{
36+
"persistence.enabled": "false",
37+
"replicaCount": "1",
38+
"image.repository": "marklogic-centos/marklogic-server-centos",
39+
"image.tag": "10-internal",
40+
"auth.adminUsername": username,
41+
"auth.adminPassword": password,
42+
"group.name": "dnode",
43+
"logCollection.enabled": "false",
44+
},
45+
}
46+
47+
t.Logf("====Creating namespace: " + namespaceName)
48+
k8s.CreateNamespace(t, kubectlOptions, namespaceName)
49+
50+
defer t.Logf("====Deleting namespace: " + namespaceName)
51+
defer k8s.DeleteNamespace(t, kubectlOptions, namespaceName)
52+
53+
dnodeReleaseName := "test-dnode-group"
54+
t.Logf("====Installing Helm Chart" + dnodeReleaseName)
55+
helm.Install(t, options, helmChartPath, dnodeReleaseName)
56+
57+
podName := dnodeReleaseName + "-marklogic-0"
58+
59+
// wait until the pod is in Ready status
60+
k8s.WaitUntilPodAvailable(t, kubectlOptions, podName, 10, 20*time.Second)
61+
62+
time.Sleep(10 * time.Second)
63+
tunnel := k8s.NewTunnel(
64+
kubectlOptions, k8s.ResourceTypePod, podName, 8002, 8002)
65+
defer tunnel.Close()
66+
tunnel.ForwardPort(t)
67+
hosts_endpoint := fmt.Sprintf("http://%s/manage/v2/hosts?format=json", tunnel.Endpoint())
68+
t.Logf(`Endpoint: %s`, hosts_endpoint)
69+
70+
dr := digest_auth.NewRequest(username, password, "GET", hosts_endpoint, "")
71+
72+
if resp, err = dr.Execute(); err != nil {
73+
t.Fatalf(err.Error())
74+
}
75+
defer resp.Body.Close()
76+
77+
if body, err = ioutil.ReadAll(resp.Body); err != nil {
78+
t.Fatalf(err.Error())
79+
}
80+
t.Logf("Response:\n" + string(body))
81+
bootstrapHost := gjson.Get(string(body), `host-default-list.list-items.list-item.#(roleref="bootstrap").nameref`)
82+
t.Logf(`BootstrapHost: = %s` , bootstrapHost)
83+
84+
// verify bootstrap host exists on the cluster
85+
if bootstrapHost.String() == "" {
86+
t.Errorf("Bootstrap does not exists on cluster")
87+
}
88+
89+
enodeOptions := &helm.Options{
90+
KubectlOptions: kubectlOptions,
91+
SetValues: map[string]string{
92+
"persistence.enabled": "false",
93+
"replicaCount": "2",
94+
"image.repository": "marklogic-centos/marklogic-server-centos",
95+
"image.tag": "10-internal",
96+
"auth.adminUsername": username,
97+
"auth.adminPassword": password,
98+
"group.name": "enode",
99+
"bootstrapHostName": bootstrapHost.String(),
100+
"logCollection.enabled": "false",
101+
},
102+
}
103+
enodeReleaseName := "test-enode-group"
104+
t.Logf("====Installing Helm Chart " + enodeReleaseName)
105+
helm.Install(t, enodeOptions, helmChartPath, enodeReleaseName)
106+
107+
enodePodName-0 := enodeReleaseName + "-marklogic-0"
108+
109+
// wait until the first enode pod is in Ready status
110+
k8s.WaitUntilPodAvailable(t, kubectlOptions, enodePodName-0, 15, 20*time.Second)
111+
112+
group_endpoint := fmt.Sprintf("http://%s/manage/v2/groups", tunnel.Endpoint())
113+
t.Logf(`Endpoint: %s`, group_endpoint)
114+
115+
dr_groups := digest_auth.NewRequest(username, password, "GET", group_endpoint, "")
116+
117+
if resp, err = dr_groups.Execute(); err != nil {
118+
t.Fatalf(err.Error())
119+
}
120+
defer resp.Body.Close()
121+
122+
if body, err = ioutil.ReadAll(resp.Body); err != nil {
123+
t.Fatalf(err.Error())
124+
}
125+
t.Logf("Response:\n" + string(body))
126+
127+
// verify groups dnode, enode exists on the cluster
128+
if !strings.Contains(string(body), "<nameref>dnode</nameref>") && !strings.Contains(string(body), "<nameref>enode</nameref>") {
129+
t.Errorf("Groups does not exists on cluster")
130+
}
131+
132+
enodePodName-1 := enodeReleaseName + "-marklogic-1"
133+
134+
// wait until the second enode pod is in Ready status
135+
k8s.WaitUntilPodAvailable(t, kubectlOptions, enodePodName-1, 15, 20*time.Second)
136+
137+
enode_endpoint := fmt.Sprintf("http://%s/manage/v2/groups/enode?format=json", tunnel.Endpoint())
138+
t.Logf(`Endpoint: %s`, enode_endpoint)
139+
140+
dr_enode := digest_auth.NewRequest(username, password, "GET", enode_endpoint, "")
141+
142+
if resp, err = dr_enode.Execute(); err != nil {
143+
t.Fatalf(err.Error())
144+
}
145+
defer resp.Body.Close()
146+
147+
if body, err = ioutil.ReadAll(resp.Body); err != nil {
148+
t.Fatalf(err.Error())
149+
}
150+
t.Logf("Response:\n" + string(body))
151+
152+
enode_host_count := gjson.Get(string(body), `group-default.relations.relation-group.#(typeref="hosts").relation-count.value`)
153+
t.Logf(`enode_host_count: = %s` , enode_host_count)
154+
155+
// verify bootstrap host exists on the cluster
156+
if !strings.Contains(enode_host_count.String(),"2") {
157+
t.Errorf("enode hosts does not exists on cluster")
158+
}
159+
}

0 commit comments

Comments
 (0)