Browse code

Add PostgreSQL replication tests

Martin Nagy authored on 2015/11/12 23:11:35
Showing 3 changed files
... ...
@@ -32,18 +32,18 @@ func CreateMySQLReplicationHelpers(c kclient.PodInterface, masterDeployment, sla
32 32
 	o.Expect(err).NotTo(o.HaveOccurred())
33 33
 
34 34
 	// Create MySQL helper for master
35
-	master := exutil.NewMysql(c, masterPod, "")
35
+	master := exutil.NewMysql(masterPod, "")
36 36
 
37 37
 	// Create MySQL helpers for slaves
38 38
 	slaves := make([]exutil.Database, len(slavePods))
39 39
 	for i := range slavePods {
40
-		slave := exutil.NewMysql(c, slavePods[i], masterPod)
40
+		slave := exutil.NewMysql(slavePods[i], masterPod)
41 41
 		slaves[i] = slave
42 42
 	}
43 43
 
44 44
 	helperNames, err := exutil.WaitForPods(c, exutil.ParseLabelsOrDie(fmt.Sprintf("deployment=%s", helperDeployment)), exutil.CheckPodIsRunningFn, 1, 60*time.Second)
45 45
 	o.Expect(err).NotTo(o.HaveOccurred())
46
-	helper := exutil.NewMysql(c, helperNames[0], masterPod)
46
+	helper := exutil.NewMysql(helperNames[0], masterPod)
47 47
 
48 48
 	return master, slaves, helper
49 49
 }
50 50
new file mode 100644
... ...
@@ -0,0 +1,160 @@
0
+package images
1
+
2
+import (
3
+	"fmt"
4
+	"time"
5
+
6
+	g "github.com/onsi/ginkgo"
7
+	o "github.com/onsi/gomega"
8
+
9
+	exutil "github.com/openshift/origin/test/extended/util"
10
+	testutil "github.com/openshift/origin/test/util"
11
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
12
+)
13
+
14
+var (
15
+	postgreSQLReplicationTemplate = "https://raw.githubusercontent.com/openshift/postgresql/master/examples/replica/postgresql_replica.json"
16
+	postgreSQLEphemeralTemplate   = exutil.FixturePath("..", "..", "examples", "db-templates", "postgresql-ephemeral-template.json")
17
+	postgreSQLHelperName          = "postgresql-helper"
18
+	postgreSQLImages              = []string{
19
+		"openshift/postgresql-92-centos7",
20
+		"centos/postgresql-94-centos7",
21
+		// TODO: Uncomment once upstream images are fixed
22
+		// "registry.access.redhat.com/openshift3/postgresql-92-rhel7",
23
+		// "registry.access.redhat.com/rhscl/postgresql-94-rhel7",
24
+	}
25
+)
26
+
27
+var _ = g.Describe("images: postgresql: replication", func() {
28
+	defer g.GinkgoRecover()
29
+
30
+	for i, image := range postgreSQLImages {
31
+		oc := exutil.NewCLI(fmt.Sprintf("postgresql-replication-%d", i), exutil.KubeConfigPath())
32
+		testFn := PostgreSQLReplicationTestFactory(oc, image)
33
+		g.It(fmt.Sprintf("postgresql replication works for %s", image), testFn)
34
+	}
35
+})
36
+
37
+// CreatePostgreSQLReplicationHelpers creates a set of PostgreSQL helpers for master,
38
+// slave an en extra helper that is used for remote login test.
39
+func CreatePostgreSQLReplicationHelpers(c kclient.PodInterface, masterDeployment, slaveDeployment, helperDeployment string, slaveCount int) (exutil.Database, []exutil.Database, exutil.Database) {
40
+	podNames, err := exutil.WaitForPods(c, exutil.ParseLabelsOrDie(fmt.Sprintf("deployment=%s", masterDeployment)), exutil.CheckPodIsRunningFn, 1, 2*time.Minute)
41
+	o.Expect(err).NotTo(o.HaveOccurred())
42
+	masterPod := podNames[0]
43
+
44
+	slavePods, err := exutil.WaitForPods(c, exutil.ParseLabelsOrDie(fmt.Sprintf("deployment=%s", slaveDeployment)), exutil.CheckPodIsRunningFn, slaveCount, 3*time.Minute)
45
+	o.Expect(err).NotTo(o.HaveOccurred())
46
+
47
+	// Create PostgreSQL helper for master
48
+	master := exutil.NewPostgreSQL(masterPod, "")
49
+
50
+	// Create PostgreSQL helpers for slaves
51
+	slaves := make([]exutil.Database, len(slavePods))
52
+	for i := range slavePods {
53
+		slave := exutil.NewPostgreSQL(slavePods[i], masterPod)
54
+		slaves[i] = slave
55
+	}
56
+
57
+	helperNames, err := exutil.WaitForPods(c, exutil.ParseLabelsOrDie(fmt.Sprintf("deployment=%s", helperDeployment)), exutil.CheckPodIsRunningFn, 1, 60*time.Second)
58
+	o.Expect(err).NotTo(o.HaveOccurred())
59
+	helper := exutil.NewPostgreSQL(helperNames[0], masterPod)
60
+
61
+	return master, slaves, helper
62
+}
63
+
64
+func PostgreSQLReplicationTestFactory(oc *exutil.CLI, image string) func() {
65
+	return func() {
66
+		oc.SetOutputDir(exutil.TestContext.OutputDir)
67
+		defer cleanup(oc)
68
+
69
+		_, err := exutil.SetupHostPathVolumes(oc.AdminKubeREST().PersistentVolumes(), oc.Namespace(), "512Mi", 1)
70
+		o.Expect(err).NotTo(o.HaveOccurred())
71
+
72
+		err = testutil.WaitForPolicyUpdate(oc.REST(), oc.Namespace(), "create", "templates", true)
73
+		o.Expect(err).NotTo(o.HaveOccurred())
74
+
75
+		err = oc.Run("new-app").Args("-f", postgreSQLReplicationTemplate, "-p", fmt.Sprintf("POSTGRESQL_IMAGE=%s", image)).Execute()
76
+		o.Expect(err).NotTo(o.HaveOccurred())
77
+
78
+		err = oc.Run("new-app").Args("-f", postgreSQLEphemeralTemplate, "-p", fmt.Sprintf("DATABASE_SERVICE_NAME=%s", postgreSQLHelperName)).Execute()
79
+		o.Expect(err).NotTo(o.HaveOccurred())
80
+		err = oc.KubeFramework().WaitForAnEndpoint(postgreSQLHelperName)
81
+		o.Expect(err).NotTo(o.HaveOccurred())
82
+
83
+		tableCounter := 0
84
+		assertReplicationIsWorking := func(masterDeployment, slaveDeployment string, slaveCount int) (exutil.Database, []exutil.Database, exutil.Database) {
85
+			tableCounter++
86
+			table := fmt.Sprintf("table_%0.2d", tableCounter)
87
+
88
+			master, slaves, helper := CreatePostgreSQLReplicationHelpers(oc.KubeREST().Pods(oc.Namespace()), masterDeployment, slaveDeployment, fmt.Sprintf("%s-1", postgreSQLHelperName), slaveCount)
89
+			o.Expect(exutil.WaitUntilAllHelpersAreUp(oc, []exutil.Database{master, helper})).NotTo(o.HaveOccurred())
90
+			o.Expect(exutil.WaitUntilAllHelpersAreUp(oc, slaves)).NotTo(o.HaveOccurred())
91
+
92
+			// Test if we can query as admin
93
+			oc.KubeFramework().WaitForAnEndpoint("postgresql-master")
94
+			err := helper.TestRemoteLogin(oc, "postgresql-master")
95
+			o.Expect(err).NotTo(o.HaveOccurred())
96
+
97
+			// Create a new table with random name
98
+			_, err = master.Query(oc, fmt.Sprintf("CREATE TABLE %s (col1 VARCHAR(20), col2 VARCHAR(20));", table))
99
+			o.Expect(err).NotTo(o.HaveOccurred())
100
+
101
+			// Write new data to the table through master
102
+			_, err = master.Query(oc, fmt.Sprintf("INSERT INTO %s (col1, col2) VALUES ('val1', 'val2');", table))
103
+			o.Expect(err).NotTo(o.HaveOccurred())
104
+
105
+			// Make sure data is present on master
106
+			err = exutil.WaitForQueryOutput(oc, master, 10*time.Second, false,
107
+				fmt.Sprintf("SELECT * FROM %s;", table),
108
+				"col1 | val1\ncol2 | val2")
109
+			o.Expect(err).NotTo(o.HaveOccurred())
110
+
111
+			// Make sure data was replicated to all slaves
112
+			for _, slave := range slaves {
113
+				err = exutil.WaitForQueryOutput(oc, slave, 90*time.Second, false,
114
+					fmt.Sprintf("SELECT * FROM %s;", table),
115
+					"col1 | val1\ncol2 | val2")
116
+				o.Expect(err).NotTo(o.HaveOccurred())
117
+			}
118
+
119
+			return master, slaves, helper
120
+		}
121
+
122
+		g.By("after initial deployment")
123
+		master, _, _ := assertReplicationIsWorking("postgresql-master-1", "postgresql-slave-1", 1)
124
+
125
+		g.By("after master is restarted by changing the Deployment Config")
126
+		err = oc.Run("env").Args("dc", "postgresql-master", "POSTGRESQL_ADMIN_PASSWORD=newpass").Execute()
127
+		o.Expect(err).NotTo(o.HaveOccurred())
128
+		o.Expect(err).NotTo(o.HaveOccurred())
129
+		err = exutil.WaitUntilPodIsGone(oc.KubeREST().Pods(oc.Namespace()), master.GetPodName(), 60*time.Second)
130
+		master, _, _ = assertReplicationIsWorking("postgresql-master-2", "postgresql-slave-1", 1)
131
+
132
+		g.By("after master is restarted by deleting the pod")
133
+		err = oc.Run("delete").Args("pod", "-l", "deployment=postgresql-master-2").Execute()
134
+		o.Expect(err).NotTo(o.HaveOccurred())
135
+		err = exutil.WaitUntilPodIsGone(oc.KubeREST().Pods(oc.Namespace()), master.GetPodName(), 60*time.Second)
136
+		o.Expect(err).NotTo(o.HaveOccurred())
137
+		_, slaves, _ := assertReplicationIsWorking("postgresql-master-2", "postgresql-slave-1", 1)
138
+
139
+		g.By("after slave is restarted by deleting the pod")
140
+		err = oc.Run("delete").Args("pod", "-l", "deployment=postgresql-slave-1").Execute()
141
+		o.Expect(err).NotTo(o.HaveOccurred())
142
+		err = exutil.WaitUntilPodIsGone(oc.KubeREST().Pods(oc.Namespace()), slaves[0].GetPodName(), 60*time.Second)
143
+		o.Expect(err).NotTo(o.HaveOccurred())
144
+		assertReplicationIsWorking("postgresql-master-2", "postgresql-slave-1", 1)
145
+
146
+		pods, err := oc.KubeREST().Pods(oc.Namespace()).List(exutil.ParseLabelsOrDie("deployment=postgresql-slave-1"), nil)
147
+		o.Expect(err).NotTo(o.HaveOccurred())
148
+		o.Expect(len(pods.Items)).To(o.Equal(1))
149
+
150
+		g.By("after slave is scaled to 0 and then back to 4 replicas")
151
+		err = oc.Run("scale").Args("dc", "postgresql-slave", "--replicas=0").Execute()
152
+		o.Expect(err).NotTo(o.HaveOccurred())
153
+		err = exutil.WaitUntilPodIsGone(oc.KubeREST().Pods(oc.Namespace()), pods.Items[0].Name, 60*time.Second)
154
+		o.Expect(err).NotTo(o.HaveOccurred())
155
+		err = oc.Run("scale").Args("dc", "postgresql-slave", "--replicas=4").Execute()
156
+		o.Expect(err).NotTo(o.HaveOccurred())
157
+		assertReplicationIsWorking("postgresql-master-2", "postgresql-slave-1", 4)
158
+	}
159
+}
... ...
@@ -34,6 +34,12 @@ type MySQL struct {
34 34
 	MasterPodName string
35 35
 }
36 36
 
37
+// PostgreSQL is a PostgreSQL helper for executing commands
38
+type PostgreSQL struct {
39
+	PodName       string
40
+	MasterPodName string
41
+}
42
+
37 43
 // PodConfig holds configuration for a pod
38 44
 type PodConfig struct {
39 45
 	Container string
... ...
@@ -42,7 +48,7 @@ type PodConfig struct {
42 42
 
43 43
 // NewMysql queries OpenShift for a pod with given name, saving environment
44 44
 // variables like username and password for easier use.
45
-func NewMysql(c kclient.PodInterface, podName, masterPodName string) Database {
45
+func NewMysql(podName, masterPodName string) Database {
46 46
 	if masterPodName == "" {
47 47
 		masterPodName = podName
48 48
 	}
... ...
@@ -52,10 +58,26 @@ func NewMysql(c kclient.PodInterface, podName, masterPodName string) Database {
52 52
 	}
53 53
 }
54 54
 
55
+// NewPostgreSQL queries OpenShift for a pod with given name, saving environment
56
+// variables like username and password for easier use.
57
+func NewPostgreSQL(podName, masterPodName string) Database {
58
+	if masterPodName == "" {
59
+		masterPodName = podName
60
+	}
61
+	return &PostgreSQL{
62
+		PodName:       podName,
63
+		MasterPodName: masterPodName,
64
+	}
65
+}
66
+
55 67
 func (m MySQL) GetPodName() string {
56 68
 	return m.PodName
57 69
 }
58 70
 
71
+func (m PostgreSQL) GetPodName() string {
72
+	return m.PodName
73
+}
74
+
59 75
 func GetPodConfig(c kclient.PodInterface, podName string) (conf *PodConfig, err error) {
60 76
 	pod, err := c.Get(podName)
61 77
 	if err != nil {
... ...
@@ -118,14 +140,82 @@ func (m MySQL) TestRemoteLogin(oc *CLI, hostAddress string) error {
118 118
 	if err != nil {
119 119
 		return err
120 120
 	}
121
-	confi, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.MasterPodName)
121
+	masterConf, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.MasterPodName)
122 122
 	if err != nil {
123 123
 		return err
124 124
 	}
125
-	_, err = oc.Run("exec").Args(m.PodName, "-c", conf.Container, "--", "bash", "-c",
125
+	err = oc.Run("exec").Args(m.PodName, "-c", conf.Container, "--", "bash", "-c",
126 126
 		fmt.Sprintf("mysql -h %s -u%s -p%s -e \"SELECT 1;\" %s",
127
-			hostAddress, confi.Env["MYSQL_USER"], confi.Env["MYSQL_PASSWORD"],
128
-			confi.Env["MYSQL_DATABASE"])).Output()
127
+			hostAddress, masterConf.Env["MYSQL_USER"], masterConf.Env["MYSQL_PASSWORD"],
128
+			masterConf.Env["MYSQL_DATABASE"])).Execute()
129
+	return err
130
+}
131
+
132
+// IsReady pings the PostgreSQL server
133
+func (m PostgreSQL) IsReady(oc *CLI) (bool, error) {
134
+	conf, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.PodName)
135
+	if err != nil {
136
+		return false, err
137
+	}
138
+	out, err := oc.Run("exec").Args(m.PodName, "-c", conf.Container, "--", "bash", "-c",
139
+		"psql postgresql://postgres@127.0.0.1 -x -c \"SELECT 1;\"").Output()
140
+	if err != nil {
141
+		switch err.(type) {
142
+		case *exec.ExitError:
143
+			return false, nil
144
+		default:
145
+			return false, err
146
+		}
147
+	}
148
+	return out == "-[ RECORD 1 ]\n?column? | 1", nil
149
+}
150
+
151
+// Query executes an SQL query as an ordinary user and returns the result.
152
+func (m PostgreSQL) Query(oc *CLI, query string) (string, error) {
153
+	conf, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.PodName)
154
+	if err != nil {
155
+		return "", err
156
+	}
157
+	masterConf, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.MasterPodName)
158
+	if err != nil {
159
+		return "", err
160
+	}
161
+	return oc.Run("exec").Args(m.PodName, "-c", conf.Container, "--", "bash", "-c",
162
+		fmt.Sprintf("psql postgres://%s:%s@127.0.0.1/%s -x -c \"%s\"",
163
+			masterConf.Env["POSTGRESQL_USER"], masterConf.Env["POSTGRESQL_PASSWORD"],
164
+			masterConf.Env["POSTGRESQL_DATABASE"], query)).Output()
165
+}
166
+
167
+// QueryPrivileged executes an SQL query as a root user and returns the result.
168
+func (m PostgreSQL) QueryPrivileged(oc *CLI, query string) (string, error) {
169
+	conf, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.PodName)
170
+	if err != nil {
171
+		return "", err
172
+	}
173
+	masterConf, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.MasterPodName)
174
+	if err != nil {
175
+		return "", err
176
+	}
177
+	return oc.Run("exec").Args(m.PodName, "-c", conf.Container, "--", "bash", "-c",
178
+		fmt.Sprintf("psql postgres://postgres:%s@127.0.0.1/%s -x -c \"%s\"",
179
+			masterConf.Env["POSTGRESQL_ADMIN_PASSWORD"],
180
+			masterConf.Env["POSTGRESQL_DATABASE"], query)).Output()
181
+}
182
+
183
+// TestRemoteLogin will test whether we can login through to a remote database.
184
+func (m PostgreSQL) TestRemoteLogin(oc *CLI, hostAddress string) error {
185
+	conf, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.PodName)
186
+	if err != nil {
187
+		return err
188
+	}
189
+	masterConf, err := GetPodConfig(oc.KubeREST().Pods(oc.Namespace()), m.MasterPodName)
190
+	if err != nil {
191
+		return err
192
+	}
193
+	err = oc.Run("exec").Args(m.PodName, "-c", conf.Container, "--", "bash", "-c",
194
+		fmt.Sprintf("psql postgres://%s:%s@%s/%s -x -c \"SELECT 1;\"",
195
+			masterConf.Env["POSTGRESQL_USER"], masterConf.Env["POSTGRESQL_PASSWORD"],
196
+			hostAddress, masterConf.Env["POSTGRESQL_DATABASE"])).Execute()
129 197
 	return err
130 198
 }
131 199