Skip to content

Commit f087d5e

Browse files
committed
Run periodic task in janitor
[#155438732] Signed-off-by: Steffen Uhlig <[email protected]>
1 parent 90e5a81 commit f087d5e

File tree

8 files changed

+129
-4
lines changed

8 files changed

+129
-4
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ databases.hooks.pre-start | Script to run before starting PostgreSQL.
100100
databases.hooks.post-start | Script to run after PostgreSQL has started.
101101
databases.hooks.pre-stop | Script to run before stopping PostgreSQL.
102102
databases.hooks.post-stop | Script to run after PostgreSQL has stopped.
103+
janitor.script | If specified, this script would be run periodically. This would be useful for running house-keeping tasks.
104+
janitor.interval | Interval in seconds between two invocations of the janitor script. By default it's set to `1` day.
105+
janitor.timeout | Time limit in seconds for the janitor script. By default it's set to `0` that means no time limit.
103106

104107
*Note*
105108
- Removing a database from `databases.databases` list and deploying again does not trigger a physical deletion of the database in PostgreSQL.
@@ -157,6 +160,8 @@ If you plan to use this feature, you have to take into consideration that:
157160
If for example you want to use psql in your hook, you can specify:
158161
`${PACKAGE_DIR}/bin/psql -p ${PORT} -U vcap postgres -c "\l"`
159162

163+
If you are interested in running something periodically, see the `janitor` configuration.
164+
160165
## Contributing
161166

162167
### Contributor License Agreement

jobs/postgres/spec

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ templates:
77
pre-start.sh.erb: bin/pre-start
88
postgres_ctl.sh.erb: bin/postgres_ctl
99
pg_janitor_ctl.sh.erb: bin/pg_janitor_ctl
10+
pg_janitor.sh.erb: bin/pg_janitor.sh
1011
postgres_start.sh.erb: bin/postgres_start.sh
1112
pgconfig.sh.erb: bin/pgconfig.sh
1213
utils.sh.erb: bin/utils.sh
@@ -19,6 +20,7 @@ templates:
1920
server.ca_cert.erb: config/certificates/server.ca_cert
2021
hooks/call-hooks.sh.erb: bin/hooks/call-hooks.sh
2122
hooks/postgres-pre-start.sh.erb: bin/hooks/postgres-pre-start.sh
23+
hooks/janitor.sh.erb: bin/hooks/janitor.sh
2224
hooks/postgres-pre-stop.sh.erb: bin/hooks/postgres-pre-stop.sh
2325
hooks/postgres-post-start.sh.erb: bin/hooks/postgres-post-start.sh
2426
hooks/postgres-post-stop.sh.erb: bin/hooks/postgres-post-stop.sh
@@ -112,3 +114,16 @@ properties:
112114
databases.hooks.post_stop:
113115
description: "Script to run after PostgreSQL has stopped"
114116
default: ''
117+
janitor.script:
118+
description: "If specified, janitor would periodically run this script"
119+
default: ''
120+
example: |
121+
#!/bin/bash
122+
echo "Run VACUUM"
123+
${PACKAGE_DIR}/bin/psql -p ${PORT} -U vcap sandbox -c "VACUUM ANALYZE"
124+
janitor.interval:
125+
description: "Interval in seconds between two invocations of the janitor script. By default it's set to 1 day."
126+
default: 86400
127+
janitor.timeout:
128+
description: "Time limit in seconds for the janitor script. By default it's set to 0 that means no time limit"
129+
default: 0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= p("janitor.script", "") %>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash -e
2+
3+
exec > >(tee -a >(logger -p user.info -t vcap.$(basename $0).stdout) | awk -W interactive '{ system("echo -n [$(date +\"%Y-%m-%d %H:%M:%S%z\")]"); print " " $0 }' >> /var/vcap/sys/log/postgres/janitor.log)
4+
exec 2> >(tee -a >(logger -p user.error -t vcap.$(basename $0).stderr) | awk -W interactive '{ system("echo -n [$(date +\"%Y-%m-%d %H:%M:%S%z\")]"); print " " $0 }' >> /var/vcap/sys/log/postgres/janitor.err.log)
5+
6+
source /var/vcap/jobs/postgres/bin/pgconfig.sh
7+
<% if p("janitor.script").empty? %>
8+
sleep infinity
9+
<% else %>
10+
while :
11+
do
12+
echo "Invoking janitor script"
13+
env -i PACKAGE_DIR=${PACKAGE_DIR} PORT=${PORT} DATA_DIR=${DATA_DIR} timeout <%=p("janitor.timeout") %> ${JOB_DIR}/bin/hooks/janitor.sh
14+
retcode=$?
15+
echo "Janitor script invoked with return code $retcode"
16+
sleep <%= p("janitor.interval") %>
17+
done
18+
<% end %>

jobs/postgres/templates/pg_janitor_ctl.sh.erb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,17 @@ function main() {
2525
fi
2626
sleep 1
2727
done"
28-
# update/create roles and databases
2928
if [ "$?" != "0" ]; then
3029
echo "Failed while waiting for postgres to start"
3130
exit 1
3231
fi
32+
33+
# update/create roles and databases
3334
create_roles
3435
create_databases
36+
3537
echo $$ > "${PGJANITOR_PIDFILE}"
36-
sleep infinity
38+
exec ${JOB_DIR}/bin/pg_janitor.sh
3739
;;
3840

3941
"stop")

src/acceptance-tests/deploy/deploy_single_node_test.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package deploy_test
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"os"
67
"os/exec"
@@ -204,7 +205,18 @@ var _ = Describe("Deploy single instance", func() {
204205
pre_stop_value := fmt.Sprintf(psql_command, pre_stop_role_name)
205206
post_stop_value := fmt.Sprintf("echo %s", post_stop_uuid)
206207

207-
err = createOrUpdateDeployment(version, manifestPath, envName, variables, helpers.DefineHooks("0", pre_start_value, post_start_value, pre_stop_value, post_stop_value))
208+
jan := helpers.Janitor{
209+
Script: `${PACKAGE_DIR}/bin/psql -U vcap -p ${PORT} -d postgres << EOF
210+
CREATE TABLE IF NOT EXISTS test_hook(name VARCHAR(10) NOT NULL UNIQUE,total INTEGER NOT NULL);
211+
INSERT INTO test_hook (name, total) VALUES ('test', 1) ON CONFLICT (name) DO NOTHING;
212+
UPDATE test_hook SET total = total + 1 WHERE name = 'test';
213+
EOF
214+
`,
215+
Timeout: 60,
216+
Interval: 1,
217+
}
218+
219+
err = createOrUpdateDeployment(version, manifestPath, envName, variables, append(jan.GetOpDefinitions(), helpers.DefineHooks("0", pre_start_value, post_start_value, pre_stop_value, post_stop_value)...))
208220
Expect(err).NotTo(HaveOccurred())
209221

210222
sshKeyFile, err := writeSSHKey(envName)
@@ -235,6 +247,52 @@ var _ = Describe("Deploy single instance", func() {
235247
cmd = exec.Command("ssh", "-i", sshKeyFile, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", fmt.Sprintf("%s@%s", variables["testuser_name"], pgHost), fmt.Sprintf(bosh_ssh_command, post_stop_uuid))
236248
err = cmd.Run()
237249
Expect(err).NotTo(HaveOccurred())
250+
err = os.Remove(sshKeyFile)
251+
252+
By("Testing the frequency-based hook")
253+
conn, err := DB.GetSuperUserConnection()
254+
Eventually(func() int {
255+
rows, err := conn.Run("select total from test_hook where name = 'test'")
256+
var counter struct {
257+
Total int `json:"total"`
258+
}
259+
Expect(err).NotTo(HaveOccurred())
260+
err = json.Unmarshal([]byte(rows[0]), &counter)
261+
Expect(err).NotTo(HaveOccurred())
262+
return counter.Total
263+
}, "15s", "2s").Should(BeNumerically(">", 10))
264+
265+
By("Verifying that janitor script failure causes monit to restart janitor")
266+
jan = helpers.Janitor{
267+
Script: `#!/bin/bash
268+
STATEFILE=/tmp/statefile
269+
if [ -f $STATEFILE ]; then
270+
echo second start >> $STATEFILE
271+
else
272+
touch $STATEFILE
273+
chmod 777 $STATEFILE
274+
exit 1
275+
fi`,
276+
Timeout: 60,
277+
Interval: 86400,
278+
}
279+
280+
err = createOrUpdateDeployment(version, manifestPath, envName, variables, jan.GetOpDefinitions())
281+
Expect(err).NotTo(HaveOccurred())
282+
283+
sshKeyFile, err = writeSSHKey(envName)
284+
Expect(err).NotTo(HaveOccurred())
285+
286+
Eventually(func() string {
287+
bosh_ssh_command = "grep second /tmp/statefile"
288+
cmd = exec.Command("ssh", "-i", sshKeyFile, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", fmt.Sprintf("%s@%s", variables["testuser_name"], pgHost), bosh_ssh_command)
289+
err = cmd.Run()
290+
if err != nil {
291+
return err.Error()
292+
}
293+
return ""
294+
}, "10s", "2s").Should(BeEmpty())
295+
err = os.Remove(sshKeyFile)
238296

239297
By("Verifying that hooks failure does not prevent postgres to start")
240298
pre_start_uuid = helpers.GetUUID()
@@ -248,6 +306,7 @@ var _ = Describe("Deploy single instance", func() {
248306
Expect(err).NotTo(HaveOccurred())
249307
_, err = DB.GetPostgreSQLVersion()
250308
Expect(err).NotTo(HaveOccurred())
309+
err = os.Remove(sshKeyFile)
251310
})
252311

253312
It("Successfully deploys a fresh env", func() {
@@ -457,6 +516,7 @@ var _ = Describe("Deploy single instance", func() {
457516
variables = make(map[string]interface{})
458517
variables["defuser_name"] = "pgadmin"
459518
variables["defuser_password"] = "admin"
519+
opDefs = nil
460520
})
461521
It("Successfully upgrades from older", AssertUpgradeSuccessful())
462522
})
@@ -467,6 +527,7 @@ var _ = Describe("Deploy single instance", func() {
467527
variables = make(map[string]interface{})
468528
variables["defuser_name"] = "pgadmin"
469529
variables["defuser_password"] = "admin"
530+
opDefs = nil
470531
})
471532
It("Successfully upgrades from old", AssertUpgradeSuccessful())
472533
})
@@ -488,6 +549,7 @@ var _ = Describe("Deploy single instance", func() {
488549
variables = make(map[string]interface{})
489550
variables["defuser_name"] = "pgadmin"
490551
variables["defuser_password"] = "admin"
552+
opDefs = nil
491553
})
492554
It("Successfully upgrades from master", AssertUpgradeSuccessful())
493555
})

src/acceptance-tests/testing/helpers/op_defs_utilities.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package helpers
22

3+
type Janitor struct {
4+
Timeout int
5+
Interval int
6+
Script string
7+
}
8+
39
func AddOpDefinition(ods *[]OpDefinition, defType string, defPath string, defValue interface{}) {
410
od := OpDefinition{
511
Type: defType,
@@ -182,3 +188,19 @@ func DefineHooks(hooks_timeout string, pre_start string, post_start string, pre_
182188

183189
return ops
184190
}
191+
192+
func (j Janitor) GetOpDefinitions() []OpDefinition {
193+
var ops []OpDefinition
194+
var path string
195+
196+
path = "/instance_groups/name=postgres/jobs/name=postgres/properties/janitor?/timeout?"
197+
AddOpDefinition(&ops, "replace", path, j.Timeout)
198+
199+
path = "/instance_groups/name=postgres/jobs/name=postgres/properties/janitor?/interval?"
200+
AddOpDefinition(&ops, "replace", path, j.Interval)
201+
202+
path = "/instance_groups/name=postgres/jobs/name=postgres/properties/janitor?/script?"
203+
AddOpDefinition(&ops, "replace", path, j.Script)
204+
205+
return ops
206+
}

src/acceptance-tests/testing/templates/postgres_simple.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,4 @@ update:
6363
canary_watch_time: 30000-600000
6464
max_in_flight: 1
6565
serial: true
66-
update_watch_time: 5000-600000
66+
update_watch_time: 15000-300000

0 commit comments

Comments
 (0)