... | ... |
@@ -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 |
|