Merge lp:~chad.smith/charms/precise/postgresql/postgresql-manual-tune-kernel-params-fix into lp:charms/postgresql
- Precise Pangolin (12.04)
- postgresql-manual-tune-kernel-params-fix
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 77 |
Proposed branch: | lp:~chad.smith/charms/precise/postgresql/postgresql-manual-tune-kernel-params-fix |
Merge into: | lp:charms/postgresql |
Diff against target: |
1001 lines (+545/-86) 7 files modified
Makefile (+9/-0) README.md (+14/-0) config.yaml (+23/-15) hooks/hooks.py (+143/-70) hooks/test_hooks.py (+354/-0) revision (+1/-0) templates/postgresql.conf.tmpl (+1/-1) |
To merge this branch: | bzr merge lp:~chad.smith/charms/precise/postgresql/postgresql-manual-tune-kernel-params-fix |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stuart Bishop (community) | Approve | ||
David Britton (community) | Approve | ||
Review via email: mp+200730@code.launchpad.net |
Commit message
Description of the change
Work in progress branch for initial review. Please let me know what you guys think before I go too far down this route. I didn't want to cause any issues by using local "trial" tests instead of juju integration tests, but it seems with a timeout of 15 minutes, the 5 existing integration tests were taking 1hr 15 mins to complete (during error conditions)
This branch does the following:
- adds basic framework for functional unit tests of hooks.
- these tests don't involve time-consuming juju unit deployment like the existing integration tests and provide
local testing of hook functions
- fixes some of the kernel_shmall, and kernel_shmmax config parameters, so ensure they properly written when
performance
- Some cleanup to reduce use of globals and reduce the use of local variable names that match global variable names to avoid confusion. Removing globals also better enabled the local unit testing framework by avoiding a global call to
hookenv.
If we think this is a good initial approach, it should be straight forward for us to add more unit tests to get coverage of most hook functions defined within. All thoughts appreciated.
- 87. By Chad Smith
-
uncomment integration tests
- 88. By Chad Smith
-
linting
- 89. By Chad Smith
-
revision bump
- 90. By Chad Smith
-
include python-psutil in install dependencies
David Britton (dpb) wrote : | # |
- 91. By Chad Smith
-
silly comma, I've got you this time
David Britton (dpb) wrote : | # |
[3] put in Makefile target for "devel" or "build-devel", where it will install appropriate packages
like: sudo apt-get install -y python-mocker python-twisted-core
[4] get rid of the revision file
I agree with the general approach. It's very light, but I think can be expanded as you go forward.
+1
Stuart Bishop (stub) wrote : | # |
This all looks good. The cleanups are much needed, as are the retrofitted unittests. And those globals... thanks for killing some more of them.
The checks for "wal_buffers = -1" are unnecessary, as this setting is not related to replication.
Stuart Bishop (stub) wrote : | # |
The file 'revision' needs to be removed before landing
Stuart Bishop (stub) wrote : | # |
Integration tests pass with the local provider (but still flaky due to the usual juju race conditions).
Merged.
Preview Diff
1 | === added file 'Makefile' | |||
2 | --- Makefile 1970-01-01 00:00:00 +0000 | |||
3 | +++ Makefile 2014-01-07 21:59:36 +0000 | |||
4 | @@ -0,0 +1,9 @@ | |||
5 | 1 | CHARM_DIR := $(shell pwd) | ||
6 | 2 | |||
7 | 3 | test: | ||
8 | 4 | cd hooks && CHARM_DIR=$(CHARM_DIR) trial test_hooks.py | ||
9 | 5 | echo "Integration tests using Juju deployed units" | ||
10 | 6 | TEST_TIMEOUT=900 ./test.py -v | ||
11 | 7 | |||
12 | 8 | lint: | ||
13 | 9 | bzr ls-lint | ||
14 | 0 | 10 | ||
15 | === modified file 'README.md' | |||
16 | --- README.md 2013-12-23 10:35:30 +0000 | |||
17 | +++ README.md 2014-01-07 21:59:36 +0000 | |||
18 | @@ -134,3 +134,17 @@ | |||
19 | 134 | - `state`: 'standalone', 'master' or 'hot standby' | 134 | - `state`: 'standalone', 'master' or 'hot standby' |
20 | 135 | - `allowed-units`: space separated list of allowed clients (unit name). | 135 | - `allowed-units`: space separated list of allowed clients (unit name). |
21 | 136 | You should check this to determine if you can connect to the database yet. | 136 | You should check this to determine if you can connect to the database yet. |
22 | 137 | |||
23 | 138 | ### For clustered support | ||
24 | 139 | In order for client charms to support replication the client will need to be | ||
25 | 140 | aware when relation-list reports > 1 unit of postgresql related: | ||
26 | 141 | - When > 1 postgresql units are related: | ||
27 | 142 | - if the client charm needs database write access, they will ignore | ||
28 | 143 | all "standalone", "hot standby" and "failover" states as those will | ||
29 | 144 | likely come from a standby unit (read-only) during standby install, | ||
30 | 145 | setup or teardown | ||
31 | 146 | - If read-only access is needed for a client, acting on | ||
32 | 147 | db-admin-relation-changed "hot standby" state will provide you with a | ||
33 | 148 | readonly replicated copy of the db | ||
34 | 149 | - When 1 postgresql unit is related: | ||
35 | 150 | - watch for updates to the db-admin-relation-changed with "standalone" state | ||
36 | 137 | 151 | ||
37 | === modified file 'config.yaml' | |||
38 | --- config.yaml 2013-11-13 10:07:41 +0000 | |||
39 | +++ config.yaml 2014-01-07 21:59:36 +0000 | |||
40 | @@ -160,7 +160,7 @@ | |||
41 | 160 | type: boolean | 160 | type: boolean |
42 | 161 | description: | | 161 | description: | |
43 | 162 | Hot standby or warm standby. When True, queries can be run against | 162 | Hot standby or warm standby. When True, queries can be run against |
45 | 163 | the database is in recovery or standby mode (ie. replicated). | 163 | the database when in recovery or standby mode (ie. replicated). |
46 | 164 | Overridden by juju when master/slave relations are used. | 164 | Overridden by juju when master/slave relations are used. |
47 | 165 | hot_standby_feedback: | 165 | hot_standby_feedback: |
48 | 166 | default: False | 166 | default: False |
49 | @@ -230,28 +230,40 @@ | |||
50 | 230 | type: string | 230 | type: string |
51 | 231 | description: | | 231 | description: | |
52 | 232 | Possible values here are "auto" or "manual". If we set "auto" then the | 232 | Possible values here are "auto" or "manual". If we set "auto" then the |
59 | 233 | charm will attempt to automatically tune all the performance paramaters | 233 | charm will attempt to automatically tune all the performance parameters |
60 | 234 | as below. If manual, then it will use the defaults below unless | 234 | for kernel_shmall, kernel_shmmax, shared_buffers and |
61 | 235 | overridden. "auto" gathers information about the node you're deployed | 235 | effective_cache_size below, unless those config values are explicitly |
62 | 236 | on and tries to make intelligent guesses about what tuning parameters | 236 | set. If manual, then it will use the defaults below unless set. |
63 | 237 | to set based on available RAM and CPU under the assumption that it's | 237 | "auto" gathers information about the node on which you are deployed and |
64 | 238 | the only significant service running on this node. | 238 | tries to make intelligent guesses about what tuning parameters to set |
65 | 239 | based on available RAM and CPU under the assumption that it's the only | ||
66 | 240 | significant service running on this node. | ||
67 | 239 | kernel_shmall: | 241 | kernel_shmall: |
68 | 240 | default: 0 | 242 | default: 0 |
69 | 241 | type: int | 243 | type: int |
71 | 242 | description: Kernel/shmall | 244 | description: Total amount of shared memory available, in bytes. |
72 | 243 | kernel_shmmax: | 245 | kernel_shmmax: |
73 | 244 | default: 0 | 246 | default: 0 |
74 | 245 | type: int | 247 | type: int |
76 | 246 | description: Kernel/shmmax | 248 | description: The maximum size, in bytes, of a shared memory segment. |
77 | 247 | shared_buffers: | 249 | shared_buffers: |
78 | 248 | default: "" | 250 | default: "" |
79 | 249 | type: string | 251 | type: string |
81 | 250 | description: Shared buffers | 252 | description: | |
82 | 253 | The amount of memory the database server uses for shared memory | ||
83 | 254 | buffers. This string should be of the format '###MB'. | ||
84 | 255 | effective_cache_size: | ||
85 | 256 | default: "" | ||
86 | 257 | type: string | ||
87 | 258 | description: | | ||
88 | 259 | Effective cache size is an estimate of how much memory is available for | ||
89 | 260 | disk caching within the database. (50% to 75% of system memory). This | ||
90 | 261 | string should be of the format '###MB'. | ||
91 | 251 | temp_buffers: | 262 | temp_buffers: |
92 | 252 | default: "1MB" | 263 | default: "1MB" |
93 | 253 | type: string | 264 | type: string |
95 | 254 | description: Temp buffers | 265 | description: | |
96 | 266 | The maximum number of temporary buffers used by each database session. | ||
97 | 255 | wal_buffers: | 267 | wal_buffers: |
98 | 256 | default: "-1" | 268 | default: "-1" |
99 | 257 | type: string | 269 | type: string |
100 | @@ -265,10 +277,6 @@ | |||
101 | 265 | default: 4.0 | 277 | default: 4.0 |
102 | 266 | type: float | 278 | type: float |
103 | 267 | description: Random page cost | 279 | description: Random page cost |
104 | 268 | effective_cache_size: | ||
105 | 269 | default: "" | ||
106 | 270 | type: string | ||
107 | 271 | description: Effective cache size | ||
108 | 272 | #------------------------------------------------------------------------ | 280 | #------------------------------------------------------------------------ |
109 | 273 | # Volume management | 281 | # Volume management |
110 | 274 | # volume-map, volume-dev_regexp are only used | 282 | # volume-map, volume-dev_regexp are only used |
111 | 275 | 283 | ||
112 | === modified file 'hooks/hooks.py' | |||
113 | --- hooks/hooks.py 2013-12-23 10:35:30 +0000 | |||
114 | +++ hooks/hooks.py 2014-01-07 21:59:36 +0000 | |||
115 | @@ -48,7 +48,7 @@ | |||
116 | 48 | ''' | 48 | ''' |
117 | 49 | myname = hookenv.local_unit().replace('/', '-') | 49 | myname = hookenv.local_unit().replace('/', '-') |
118 | 50 | ts = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) | 50 | ts = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) |
120 | 51 | with open('/var/log/juju/{}-debug.log'.format(myname), 'a') as f: | 51 | with open('{}/{}-debug.log'.format(juju_log_dir, myname), 'a') as f: |
121 | 52 | f.write('{} {}: {}\n'.format(ts, lvl, msg)) | 52 | f.write('{} {}: {}\n'.format(ts, lvl, msg)) |
122 | 53 | hookenv.log(msg, lvl) | 53 | hookenv.log(msg, lvl) |
123 | 54 | 54 | ||
124 | @@ -121,7 +121,7 @@ | |||
125 | 121 | def volume_get_volid_from_volume_map(): | 121 | def volume_get_volid_from_volume_map(): |
126 | 122 | volume_map = {} | 122 | volume_map = {} |
127 | 123 | try: | 123 | try: |
129 | 124 | volume_map = yaml.load(config_data['volume-map'].strip()) | 124 | volume_map = yaml.load(hookenv.config('volume-map').strip()) |
130 | 125 | if volume_map: | 125 | if volume_map: |
131 | 126 | return volume_map.get(os.environ['JUJU_UNIT_NAME']) | 126 | return volume_map.get(os.environ['JUJU_UNIT_NAME']) |
132 | 127 | except ConstructorError as e: | 127 | except ConstructorError as e: |
133 | @@ -154,7 +154,7 @@ | |||
134 | 154 | # @returns volid | 154 | # @returns volid |
135 | 155 | # None config state is invalid - we should not serve | 155 | # None config state is invalid - we should not serve |
136 | 156 | def volume_get_volume_id(): | 156 | def volume_get_volume_id(): |
138 | 157 | ephemeral_storage = config_data['volume-ephemeral-storage'] | 157 | ephemeral_storage = hookenv.config('volume-ephemeral-storage') |
139 | 158 | volid = volume_get_volid_from_volume_map() | 158 | volid = volume_get_volid_from_volume_map() |
140 | 159 | juju_unit_name = hookenv.local_unit() | 159 | juju_unit_name = hookenv.local_unit() |
141 | 160 | if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']: | 160 | if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']: |
142 | @@ -195,6 +195,7 @@ | |||
143 | 195 | 195 | ||
144 | 196 | 196 | ||
145 | 197 | def postgresql_autostart(enabled): | 197 | def postgresql_autostart(enabled): |
146 | 198 | postgresql_config_dir = _get_postgresql_config_dir() | ||
147 | 198 | startup_file = os.path.join(postgresql_config_dir, 'start.conf') | 199 | startup_file = os.path.join(postgresql_config_dir, 'start.conf') |
148 | 199 | if enabled: | 200 | if enabled: |
149 | 200 | log("Enabling PostgreSQL startup in {}".format(startup_file)) | 201 | log("Enabling PostgreSQL startup in {}".format(startup_file)) |
150 | @@ -202,8 +203,8 @@ | |||
151 | 202 | else: | 203 | else: |
152 | 203 | log("Disabling PostgreSQL startup in {}".format(startup_file)) | 204 | log("Disabling PostgreSQL startup in {}".format(startup_file)) |
153 | 204 | mode = 'manual' | 205 | mode = 'manual' |
156 | 205 | contents = Template(open("templates/start_conf.tmpl").read()).render( | 206 | template_file = "{}/templates/start_conf.tmpl".format(hookenv.charm_dir()) |
157 | 206 | {'mode': mode}) | 207 | contents = Template(open(template_file).read()).render({'mode': mode}) |
158 | 207 | host.write_file( | 208 | host.write_file( |
159 | 208 | startup_file, contents, 'postgres', 'postgres', perms=0o644) | 209 | startup_file, contents, 'postgres', 'postgres', perms=0o644) |
160 | 209 | 210 | ||
161 | @@ -229,7 +230,7 @@ | |||
162 | 229 | if status != 0: | 230 | if status != 0: |
163 | 230 | return False | 231 | return False |
164 | 231 | # e.g. output: "Running clusters: 9.1/main" | 232 | # e.g. output: "Running clusters: 9.1/main" |
166 | 232 | vc = "%s/%s" % (config_data["version"], config_data["cluster_name"]) | 233 | vc = "%s/%s" % (hookenv.config("version"), hookenv.config("cluster_name")) |
167 | 233 | return vc in output.decode('utf8').split() | 234 | return vc in output.decode('utf8').split() |
168 | 234 | 235 | ||
169 | 235 | 236 | ||
170 | @@ -253,7 +254,7 @@ | |||
171 | 253 | # success = host.service_restart('postgresql') | 254 | # success = host.service_restart('postgresql') |
172 | 254 | try: | 255 | try: |
173 | 255 | run('pg_ctlcluster -force {version} {cluster_name} ' | 256 | run('pg_ctlcluster -force {version} {cluster_name} ' |
175 | 256 | 'restart'.format(**config_data)) | 257 | 'restart'.format(**hookenv.config())) |
176 | 257 | success = True | 258 | success = True |
177 | 258 | except subprocess.CalledProcessError: | 259 | except subprocess.CalledProcessError: |
178 | 259 | success = False | 260 | success = False |
179 | @@ -327,11 +328,11 @@ | |||
180 | 327 | return success | 328 | return success |
181 | 328 | 329 | ||
182 | 329 | 330 | ||
184 | 330 | def get_service_port(postgresql_config): | 331 | def get_service_port(config_file): |
185 | 331 | '''Return the port PostgreSQL is listening on.''' | 332 | '''Return the port PostgreSQL is listening on.''' |
187 | 332 | if not os.path.exists(postgresql_config): | 333 | if not os.path.exists(config_file): |
188 | 333 | return None | 334 | return None |
190 | 334 | postgresql_config = open(postgresql_config, 'r').read() | 335 | postgresql_config = open(config_file, 'r').read() |
191 | 335 | port = re.search("port.*=(.*)", postgresql_config).group(1).strip() | 336 | port = re.search("port.*=(.*)", postgresql_config).group(1).strip() |
192 | 336 | try: | 337 | try: |
193 | 337 | return int(port) | 338 | return int(port) |
194 | @@ -339,14 +340,26 @@ | |||
195 | 339 | return None | 340 | return None |
196 | 340 | 341 | ||
197 | 341 | 342 | ||
199 | 342 | def create_postgresql_config(postgresql_config): | 343 | def _get_system_ram(): |
200 | 344 | """ Return the system ram in Megabytes """ | ||
201 | 345 | import psutil | ||
202 | 346 | return psutil.phymem_usage()[0] / (1024 ** 2) | ||
203 | 347 | |||
204 | 348 | |||
205 | 349 | def _get_page_size(): | ||
206 | 350 | """ Return the operating system's configured PAGE_SIZE """ | ||
207 | 351 | return int(run("getconf PAGE_SIZE")) # frequently 4096 | ||
208 | 352 | |||
209 | 353 | |||
210 | 354 | def create_postgresql_config(config_file): | ||
211 | 343 | '''Create the postgresql.conf file''' | 355 | '''Create the postgresql.conf file''' |
212 | 356 | config_data = hookenv.config() | ||
213 | 344 | if config_data["performance_tuning"] == "auto": | 357 | if config_data["performance_tuning"] == "auto": |
214 | 345 | # Taken from: | 358 | # Taken from: |
215 | 346 | # http://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server | 359 | # http://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server |
216 | 347 | # num_cpus is not being used ... commenting it out ... negronjl | 360 | # num_cpus is not being used ... commenting it out ... negronjl |
217 | 348 | #num_cpus = run("cat /proc/cpuinfo | grep processor | wc -l") | 361 | #num_cpus = run("cat /proc/cpuinfo | grep processor | wc -l") |
219 | 349 | total_ram = run("free -m | grep Mem | awk '{print $2}'") | 362 | total_ram = _get_system_ram() |
220 | 350 | if not config_data["effective_cache_size"]: | 363 | if not config_data["effective_cache_size"]: |
221 | 351 | config_data["effective_cache_size"] = \ | 364 | config_data["effective_cache_size"] = \ |
222 | 352 | "%sMB" % (int(int(total_ram) * 0.75),) | 365 | "%sMB" % (int(int(total_ram) * 0.75),) |
223 | @@ -357,15 +370,22 @@ | |||
224 | 357 | else: | 370 | else: |
225 | 358 | config_data["shared_buffers"] = \ | 371 | config_data["shared_buffers"] = \ |
226 | 359 | "%sMB" % (int(int(total_ram) * 0.15),) | 372 | "%sMB" % (int(int(total_ram) * 0.15),) |
236 | 360 | # XXX: This is very messy - should probably be a subordinate charm | 373 | config_data["kernel_shmmax"] = (int(total_ram) * 1024 * 1024) + 1024 |
237 | 361 | conf_file = open("/etc/sysctl.d/50-postgresql.conf", "w") | 374 | config_data["kernel_shmall"] = config_data["kernel_shmmax"] |
238 | 362 | conf_file.write("kernel.sem = 250 32000 100 1024\n") | 375 | |
239 | 363 | conf_file.write("kernel.shmall = %s\n" % | 376 | # XXX: This is very messy - should probably be a subordinate charm |
240 | 364 | ((int(total_ram) * 1024 * 1024) + 1024),) | 377 | lines = ["kernel.sem = 250 32000 100 1024\n"] |
241 | 365 | conf_file.write("kernel.shmmax = %s\n" % | 378 | if config_data["kernel_shmall"] > 0: |
242 | 366 | ((int(total_ram) * 1024 * 1024) + 1024),) | 379 | # Convert config kernel_shmall (bytes) to pages |
243 | 367 | conf_file.close() | 380 | page_size = _get_page_size() |
244 | 368 | run("sysctl -p /etc/sysctl.d/50-postgresql.conf") | 381 | num_pages = config_data["kernel_shmall"] / page_size |
245 | 382 | if (config_data["kernel_shmall"] % page_size) > 0: | ||
246 | 383 | num_pages += 1 | ||
247 | 384 | lines.append("kernel.shmall = %s\n" % num_pages) | ||
248 | 385 | if config_data["kernel_shmmax"] > 0: | ||
249 | 386 | lines.append("kernel.shmmax = %s\n" % config_data["kernel_shmmax"]) | ||
250 | 387 | host.write_file(postgresql_sysctl, ''.join(lines), perms=0600) | ||
251 | 388 | run("sysctl -p {}".format(postgresql_sysctl)) | ||
252 | 369 | 389 | ||
253 | 370 | # If we are replicating, some settings may need to be overridden to | 390 | # If we are replicating, some settings may need to be overridden to |
254 | 371 | # certain minimum levels. | 391 | # certain minimum levels. |
255 | @@ -383,28 +403,31 @@ | |||
256 | 383 | 403 | ||
257 | 384 | # Send config data to the template | 404 | # Send config data to the template |
258 | 385 | # Return it as pg_config | 405 | # Return it as pg_config |
259 | 406 | charm_dir = hookenv.charm_dir() | ||
260 | 407 | template_file = "{}/templates/postgresql.conf.tmpl".format(charm_dir) | ||
261 | 386 | pg_config = Template( | 408 | pg_config = Template( |
263 | 387 | open("templates/postgresql.conf.tmpl").read()).render(config_data) | 409 | open(template_file).read()).render(config_data) |
264 | 388 | host.write_file( | 410 | host.write_file( |
266 | 389 | postgresql_config, pg_config, | 411 | config_file, pg_config, |
267 | 390 | owner="postgres", group="postgres", perms=0600) | 412 | owner="postgres", group="postgres", perms=0600) |
268 | 391 | 413 | ||
269 | 392 | local_state['saved_config'] = config_data | 414 | local_state['saved_config'] = config_data |
270 | 393 | local_state.save() | 415 | local_state.save() |
271 | 394 | 416 | ||
272 | 395 | 417 | ||
274 | 396 | def create_postgresql_ident(postgresql_ident): | 418 | def create_postgresql_ident(output_file): |
275 | 397 | '''Create the pg_ident.conf file.''' | 419 | '''Create the pg_ident.conf file.''' |
276 | 398 | ident_data = {} | 420 | ident_data = {} |
279 | 399 | pg_ident_template = Template( | 421 | charm_dir = hookenv.charm_dir() |
280 | 400 | open("templates/pg_ident.conf.tmpl").read()) | 422 | template_file = "{}/templates/pg_ident.conf.tmpl".format(charm_dir) |
281 | 423 | pg_ident_template = Template(open(template_file).read()) | ||
282 | 401 | host.write_file( | 424 | host.write_file( |
284 | 402 | postgresql_ident, pg_ident_template.render(ident_data), | 425 | output_file, pg_ident_template.render(ident_data), |
285 | 403 | owner="postgres", group="postgres", perms=0600) | 426 | owner="postgres", group="postgres", perms=0600) |
286 | 404 | 427 | ||
287 | 405 | 428 | ||
288 | 406 | def generate_postgresql_hba( | 429 | def generate_postgresql_hba( |
290 | 407 | postgresql_hba, user=None, schema_user=None, database=None): | 430 | output_file, user=None, schema_user=None, database=None): |
291 | 408 | '''Create the pg_hba.conf file.''' | 431 | '''Create the pg_hba.conf file.''' |
292 | 409 | 432 | ||
293 | 410 | # Per Bug #1117542, when generating the postgresql_hba file we | 433 | # Per Bug #1117542, when generating the postgresql_hba file we |
294 | @@ -429,6 +452,7 @@ | |||
295 | 429 | return output.rstrip(".") # trailing dot | 452 | return output.rstrip(".") # trailing dot |
296 | 430 | return addr | 453 | return addr |
297 | 431 | 454 | ||
298 | 455 | config_data = hookenv.config() | ||
299 | 432 | allowed_units = set() | 456 | allowed_units = set() |
300 | 433 | relation_data = [] | 457 | relation_data = [] |
301 | 434 | relids = hookenv.relation_ids('db') + hookenv.relation_ids('db-admin') | 458 | relids = hookenv.relation_ids('db') + hookenv.relation_ids('db-admin') |
302 | @@ -525,9 +549,10 @@ | |||
303 | 525 | 'private-address': munge_address(admin_ip)} | 549 | 'private-address': munge_address(admin_ip)} |
304 | 526 | relation_data.append(admin_host) | 550 | relation_data.append(admin_host) |
305 | 527 | 551 | ||
307 | 528 | pg_hba_template = Template(open("templates/pg_hba.conf.tmpl").read()) | 552 | template_file = "{}/templates/pg_hba.conf.tmpl".format(hookenv.charm_dir()) |
308 | 553 | pg_hba_template = Template(open(template_file).read()) | ||
309 | 529 | host.write_file( | 554 | host.write_file( |
311 | 530 | postgresql_hba, pg_hba_template.render(access_list=relation_data), | 555 | output_file, pg_hba_template.render(access_list=relation_data), |
312 | 531 | owner="postgres", group="postgres", perms=0600) | 556 | owner="postgres", group="postgres", perms=0600) |
313 | 532 | postgresql_reload() | 557 | postgresql_reload() |
314 | 533 | 558 | ||
315 | @@ -539,27 +564,36 @@ | |||
316 | 539 | relid, {"allowed-units": " ".join(unit_sorted(allowed_units))}) | 564 | relid, {"allowed-units": " ".join(unit_sorted(allowed_units))}) |
317 | 540 | 565 | ||
318 | 541 | 566 | ||
320 | 542 | def install_postgresql_crontab(postgresql_ident): | 567 | def install_postgresql_crontab(output_file): |
321 | 543 | '''Create the postgres user's crontab''' | 568 | '''Create the postgres user's crontab''' |
322 | 569 | config_data = hookenv.config() | ||
323 | 544 | crontab_data = { | 570 | crontab_data = { |
324 | 545 | 'backup_schedule': config_data["backup_schedule"], | 571 | 'backup_schedule': config_data["backup_schedule"], |
325 | 546 | 'scripts_dir': postgresql_scripts_dir, | 572 | 'scripts_dir': postgresql_scripts_dir, |
326 | 547 | 'backup_days': config_data["backup_retention_count"], | 573 | 'backup_days': config_data["backup_retention_count"], |
327 | 548 | } | 574 | } |
328 | 575 | charm_dir = hookenv.charm_dir() | ||
329 | 576 | template_file = "{}/templates/postgres.cron.tmpl".format(charm_dir) | ||
330 | 549 | crontab_template = Template( | 577 | crontab_template = Template( |
333 | 550 | open("templates/postgres.cron.tmpl").read()).render(crontab_data) | 578 | open(template_file).read()).render(crontab_data) |
334 | 551 | host.write_file('/etc/cron.d/postgres', crontab_template, perms=0600) | 579 | host.write_file(output_file, crontab_template, perms=0600) |
335 | 552 | 580 | ||
336 | 553 | 581 | ||
337 | 554 | def create_recovery_conf(master_host, restart_on_change=False): | 582 | def create_recovery_conf(master_host, restart_on_change=False): |
338 | 583 | version = hookenv.config('version') | ||
339 | 584 | cluster_name = hookenv.config('cluster_name') | ||
340 | 585 | postgresql_cluster_dir = os.path.join( | ||
341 | 586 | postgresql_data_dir, version, cluster_name) | ||
342 | 587 | |||
343 | 555 | recovery_conf_path = os.path.join(postgresql_cluster_dir, 'recovery.conf') | 588 | recovery_conf_path = os.path.join(postgresql_cluster_dir, 'recovery.conf') |
344 | 556 | if os.path.exists(recovery_conf_path): | 589 | if os.path.exists(recovery_conf_path): |
345 | 557 | old_recovery_conf = open(recovery_conf_path, 'r').read() | 590 | old_recovery_conf = open(recovery_conf_path, 'r').read() |
346 | 558 | else: | 591 | else: |
347 | 559 | old_recovery_conf = None | 592 | old_recovery_conf = None |
348 | 560 | 593 | ||
351 | 561 | recovery_conf = Template( | 594 | charm_dir = hookenv.charm_dir() |
352 | 562 | open("templates/recovery.conf.tmpl").read()).render({ | 595 | template_file = "{}/templates/recovery.conf.tmpl".format(charm_dir) |
353 | 596 | recovery_conf = Template(open(template_file).read()).render({ | ||
354 | 563 | 'host': master_host, | 597 | 'host': master_host, |
355 | 564 | 'password': local_state['replication_password']}) | 598 | 'password': local_state['replication_password']}) |
356 | 565 | log(recovery_conf, DEBUG) | 599 | log(recovery_conf, DEBUG) |
357 | @@ -578,9 +612,9 @@ | |||
358 | 578 | # Returns a string containing the postgresql config or | 612 | # Returns a string containing the postgresql config or |
359 | 579 | # None | 613 | # None |
360 | 580 | #------------------------------------------------------------------------------ | 614 | #------------------------------------------------------------------------------ |
364 | 581 | def load_postgresql_config(postgresql_config): | 615 | def load_postgresql_config(config_file): |
365 | 582 | if os.path.isfile(postgresql_config): | 616 | if os.path.isfile(config_file): |
366 | 583 | return(open(postgresql_config).read()) | 617 | return(open(config_file).read()) |
367 | 584 | else: | 618 | else: |
368 | 585 | return(None) | 619 | return(None) |
369 | 586 | 620 | ||
370 | @@ -677,7 +711,11 @@ | |||
371 | 677 | # - manipulate /var/lib/postgresql/VERSION/CLUSTER symlink | 711 | # - manipulate /var/lib/postgresql/VERSION/CLUSTER symlink |
372 | 678 | #------------------------------------------------------------------------------ | 712 | #------------------------------------------------------------------------------ |
373 | 679 | def config_changed_volume_apply(): | 713 | def config_changed_volume_apply(): |
375 | 680 | data_directory_path = postgresql_cluster_dir | 714 | version = hookenv.config('version') |
376 | 715 | cluster_name = hookenv.config('cluster_name') | ||
377 | 716 | data_directory_path = os.path.join( | ||
378 | 717 | postgresql_data_dir, version, cluster_name) | ||
379 | 718 | |||
380 | 681 | assert(data_directory_path) | 719 | assert(data_directory_path) |
381 | 682 | volid = volume_get_volume_id() | 720 | volid = volume_get_volume_id() |
382 | 683 | if volid: | 721 | if volid: |
383 | @@ -698,7 +736,7 @@ | |||
384 | 698 | mount_point = volume_mount_point_from_volid(volid) | 736 | mount_point = volume_mount_point_from_volid(volid) |
385 | 699 | new_pg_dir = os.path.join(mount_point, "postgresql") | 737 | new_pg_dir = os.path.join(mount_point, "postgresql") |
386 | 700 | new_pg_version_cluster_dir = os.path.join( | 738 | new_pg_version_cluster_dir = os.path.join( |
388 | 701 | new_pg_dir, config_data["version"], config_data["cluster_name"]) | 739 | new_pg_dir, version, cluster_name) |
389 | 702 | if not mount_point: | 740 | if not mount_point: |
390 | 703 | log( | 741 | log( |
391 | 704 | "invalid mount point from volid = {}, " | 742 | "invalid mount point from volid = {}, " |
392 | @@ -724,7 +762,7 @@ | |||
393 | 724 | # /var/lib/postgresql/9.1/main | 762 | # /var/lib/postgresql/9.1/main |
394 | 725 | curr_dir_stat = os.stat(data_directory_path) | 763 | curr_dir_stat = os.stat(data_directory_path) |
395 | 726 | for new_dir in [new_pg_dir, | 764 | for new_dir in [new_pg_dir, |
397 | 727 | os.path.join(new_pg_dir, config_data["version"]), | 765 | os.path.join(new_pg_dir, version), |
398 | 728 | new_pg_version_cluster_dir]: | 766 | new_pg_version_cluster_dir]: |
399 | 729 | if not os.path.isdir(new_dir): | 767 | if not os.path.isdir(new_dir): |
400 | 730 | log("mkdir %s".format(new_dir)) | 768 | log("mkdir %s".format(new_dir)) |
401 | @@ -781,7 +819,8 @@ | |||
402 | 781 | 819 | ||
403 | 782 | @hooks.hook() | 820 | @hooks.hook() |
404 | 783 | def config_changed(force_restart=False): | 821 | def config_changed(force_restart=False): |
406 | 784 | update_repos_and_packages() | 822 | config_data = hookenv.config() |
407 | 823 | update_repos_and_packages(config_data["version"]) | ||
408 | 785 | 824 | ||
409 | 786 | # Trigger volume initialization logic for permanent storage | 825 | # Trigger volume initialization logic for permanent storage |
410 | 787 | volid = volume_get_volume_id() | 826 | volid = volume_get_volume_id() |
411 | @@ -813,10 +852,17 @@ | |||
412 | 813 | "Disabled and stopped postgresql service " | 852 | "Disabled and stopped postgresql service " |
413 | 814 | "(config_changed_volume_apply failure)", ERROR) | 853 | "(config_changed_volume_apply failure)", ERROR) |
414 | 815 | sys.exit(1) | 854 | sys.exit(1) |
415 | 855 | |||
416 | 856 | postgresql_config_dir = _get_postgresql_config_dir(config_data) | ||
417 | 857 | postgresql_config = os.path.join(postgresql_config_dir, "postgresql.conf") | ||
418 | 858 | postgresql_hba = os.path.join(postgresql_config_dir, "pg_hba.conf") | ||
419 | 859 | postgresql_ident = os.path.join(postgresql_config_dir, "pg_ident.conf") | ||
420 | 860 | |||
421 | 816 | current_service_port = get_service_port(postgresql_config) | 861 | current_service_port = get_service_port(postgresql_config) |
422 | 817 | create_postgresql_config(postgresql_config) | 862 | create_postgresql_config(postgresql_config) |
423 | 818 | generate_postgresql_hba(postgresql_hba) | 863 | generate_postgresql_hba(postgresql_hba) |
424 | 819 | create_postgresql_ident(postgresql_ident) | 864 | create_postgresql_ident(postgresql_ident) |
425 | 865 | |||
426 | 820 | updated_service_port = config_data["listen_port"] | 866 | updated_service_port = config_data["listen_port"] |
427 | 821 | update_service_port(current_service_port, updated_service_port) | 867 | update_service_port(current_service_port, updated_service_port) |
428 | 822 | update_nrpe_checks() | 868 | update_nrpe_checks() |
429 | @@ -832,8 +878,8 @@ | |||
430 | 832 | if os.path.isfile(f) and os.access(f, os.X_OK): | 878 | if os.path.isfile(f) and os.access(f, os.X_OK): |
431 | 833 | subprocess.check_call(['sh', '-c', f]) | 879 | subprocess.check_call(['sh', '-c', f]) |
432 | 834 | 880 | ||
435 | 835 | update_repos_and_packages() | 881 | config_data = hookenv.config() |
436 | 836 | 882 | update_repos_and_packages(config_data["version"]) | |
437 | 837 | if not 'state' in local_state: | 883 | if not 'state' in local_state: |
438 | 838 | # Fresh installation. Because this function is invoked by both | 884 | # Fresh installation. Because this function is invoked by both |
439 | 839 | # the install hook and the upgrade-charm hook, we need to guard | 885 | # the install hook and the upgrade-charm hook, we need to guard |
440 | @@ -848,6 +894,10 @@ | |||
441 | 848 | run("pg_createcluster --locale='{}' --encoding='{}' 9.1 main".format( | 894 | run("pg_createcluster --locale='{}' --encoding='{}' 9.1 main".format( |
442 | 849 | config_data['locale'], config_data['encoding'])) | 895 | config_data['locale'], config_data['encoding'])) |
443 | 850 | 896 | ||
444 | 897 | postgresql_backups_dir = ( | ||
445 | 898 | config_data['backup_dir'].strip() or | ||
446 | 899 | os.path.join(postgresql_data_dir, 'backups')) | ||
447 | 900 | |||
448 | 851 | host.mkdir(postgresql_backups_dir, owner="postgres", perms=0o755) | 901 | host.mkdir(postgresql_backups_dir, owner="postgres", perms=0o755) |
449 | 852 | host.mkdir(postgresql_scripts_dir, owner="postgres", perms=0o755) | 902 | host.mkdir(postgresql_scripts_dir, owner="postgres", perms=0o755) |
450 | 853 | host.mkdir(postgresql_logs_dir, owner="postgres", perms=0o755) | 903 | host.mkdir(postgresql_logs_dir, owner="postgres", perms=0o755) |
451 | @@ -857,10 +907,11 @@ | |||
452 | 857 | 'scripts_dir': postgresql_scripts_dir, | 907 | 'scripts_dir': postgresql_scripts_dir, |
453 | 858 | 'logs_dir': postgresql_logs_dir, | 908 | 'logs_dir': postgresql_logs_dir, |
454 | 859 | } | 909 | } |
459 | 860 | dump_script = Template( | 910 | charm_dir = hookenv.charm_dir() |
460 | 861 | open("templates/dump-pg-db.tmpl").read()).render(paths) | 911 | template_file = "{}/templates/dump-pg-db.tmpl".format(charm_dir) |
461 | 862 | backup_job = Template( | 912 | dump_script = Template(open(template_file).read()).render(paths) |
462 | 863 | open("templates/pg_backup_job.tmpl").read()).render(paths) | 913 | template_file = "{}/templates/pg_backup_job.tmpl".format(charm_dir) |
463 | 914 | backup_job = Template(open(template_file).read()).render(paths) | ||
464 | 864 | host.write_file( | 915 | host.write_file( |
465 | 865 | '{}/dump-pg-db'.format(postgresql_scripts_dir), | 916 | '{}/dump-pg-db'.format(postgresql_scripts_dir), |
466 | 866 | dump_script, perms=0755) | 917 | dump_script, perms=0755) |
467 | @@ -1176,6 +1227,7 @@ | |||
468 | 1176 | log("Client relations {}".format(local_state['client_relations'])) | 1227 | log("Client relations {}".format(local_state['client_relations'])) |
469 | 1177 | local_state.publish() | 1228 | local_state.publish() |
470 | 1178 | 1229 | ||
471 | 1230 | postgresql_hba = os.path.join(_get_postgresql_config_dir(), "pg_hba.conf") | ||
472 | 1179 | generate_postgresql_hba(postgresql_hba, user=user, | 1231 | generate_postgresql_hba(postgresql_hba, user=user, |
473 | 1180 | schema_user=schema_user, | 1232 | schema_user=schema_user, |
474 | 1181 | database=database) | 1233 | database=database) |
475 | @@ -1213,6 +1265,7 @@ | |||
476 | 1213 | log("Client relations {}".format(local_state['client_relations'])) | 1265 | log("Client relations {}".format(local_state['client_relations'])) |
477 | 1214 | local_state.publish() | 1266 | local_state.publish() |
478 | 1215 | 1267 | ||
479 | 1268 | postgresql_hba = os.path.join(_get_postgresql_config_dir(), "pg_hba.conf") | ||
480 | 1216 | generate_postgresql_hba(postgresql_hba) | 1269 | generate_postgresql_hba(postgresql_hba) |
481 | 1217 | 1270 | ||
482 | 1218 | snapshot_relations() | 1271 | snapshot_relations() |
483 | @@ -1245,6 +1298,7 @@ | |||
484 | 1245 | run_sql_as_postgres(sql, AsIs(quote_identifier(database)), | 1298 | run_sql_as_postgres(sql, AsIs(quote_identifier(database)), |
485 | 1246 | AsIs(quote_identifier(user + "_schema"))) | 1299 | AsIs(quote_identifier(user + "_schema"))) |
486 | 1247 | 1300 | ||
487 | 1301 | postgresql_hba = os.path.join(_get_postgresql_config_dir(), "pg_hba.conf") | ||
488 | 1248 | generate_postgresql_hba(postgresql_hba) | 1302 | generate_postgresql_hba(postgresql_hba) |
489 | 1249 | 1303 | ||
490 | 1250 | # Cleanup our local state. | 1304 | # Cleanup our local state. |
491 | @@ -1261,13 +1315,14 @@ | |||
492 | 1261 | sql = "ALTER USER %s NOSUPERUSER" | 1315 | sql = "ALTER USER %s NOSUPERUSER" |
493 | 1262 | run_sql_as_postgres(sql, AsIs(quote_identifier(user))) | 1316 | run_sql_as_postgres(sql, AsIs(quote_identifier(user))) |
494 | 1263 | 1317 | ||
495 | 1318 | postgresql_hba = os.path.join(_get_postgresql_config_dir(), "pg_hba.conf") | ||
496 | 1264 | generate_postgresql_hba(postgresql_hba) | 1319 | generate_postgresql_hba(postgresql_hba) |
497 | 1265 | 1320 | ||
498 | 1266 | # Cleanup our local state. | 1321 | # Cleanup our local state. |
499 | 1267 | snapshot_relations() | 1322 | snapshot_relations() |
500 | 1268 | 1323 | ||
501 | 1269 | 1324 | ||
503 | 1270 | def update_repos_and_packages(): | 1325 | def update_repos_and_packages(version): |
504 | 1271 | extra_repos = hookenv.config('extra_archives') | 1326 | extra_repos = hookenv.config('extra_archives') |
505 | 1272 | extra_repos_added = local_state.setdefault('extra_repos_added', set()) | 1327 | extra_repos_added = local_state.setdefault('extra_repos_added', set()) |
506 | 1273 | if extra_repos: | 1328 | if extra_repos: |
507 | @@ -1284,10 +1339,12 @@ | |||
508 | 1284 | # It might have been better for debversion and plpython to only get | 1339 | # It might have been better for debversion and plpython to only get |
509 | 1285 | # installed if they were listed in the extra-packages config item, | 1340 | # installed if they were listed in the extra-packages config item, |
510 | 1286 | # but they predate this feature. | 1341 | # but they predate this feature. |
515 | 1287 | packages = ["postgresql-%s" % config_data["version"], | 1342 | packages = ["python-psutil", # to obtain system RAM from python |
516 | 1288 | "postgresql-contrib-%s" % config_data["version"], | 1343 | "libc-bin", # for getconf |
517 | 1289 | "postgresql-plpython-%s" % config_data["version"], | 1344 | "postgresql-%s" % version, |
518 | 1290 | "postgresql-%s-debversion" % config_data["version"], | 1345 | "postgresql-contrib-%s" % version, |
519 | 1346 | "postgresql-plpython-%s" % version, | ||
520 | 1347 | "postgresql-%s-debversion" % version, | ||
521 | 1291 | "python-jinja2", "syslinux", "python-psycopg2"] | 1348 | "python-jinja2", "syslinux", "python-psycopg2"] |
522 | 1292 | packages.extend((hookenv.config('extra-packages') or '').split()) | 1349 | packages.extend((hookenv.config('extra-packages') or '').split()) |
523 | 1293 | packages = fetch.filter_installed_packages(packages) | 1350 | packages = fetch.filter_installed_packages(packages) |
524 | @@ -1351,6 +1408,11 @@ | |||
525 | 1351 | 1408 | ||
526 | 1352 | def promote_database(): | 1409 | def promote_database(): |
527 | 1353 | '''Take the database out of recovery mode.''' | 1410 | '''Take the database out of recovery mode.''' |
528 | 1411 | config_data = hookenv.config() | ||
529 | 1412 | version = config_data['version'] | ||
530 | 1413 | cluster_name = config_data['cluster_name'] | ||
531 | 1414 | postgresql_cluster_dir = os.path.join( | ||
532 | 1415 | postgresql_data_dir, version, cluster_name) | ||
533 | 1354 | recovery_conf = os.path.join(postgresql_cluster_dir, 'recovery.conf') | 1416 | recovery_conf = os.path.join(postgresql_cluster_dir, 'recovery.conf') |
534 | 1355 | if os.path.exists(recovery_conf): | 1417 | if os.path.exists(recovery_conf): |
535 | 1356 | # Rather than using 'pg_ctl promote', we do the promotion | 1418 | # Rather than using 'pg_ctl promote', we do the promotion |
536 | @@ -1554,6 +1616,8 @@ | |||
537 | 1554 | del local_state['paused_at_failover'] | 1616 | del local_state['paused_at_failover'] |
538 | 1555 | 1617 | ||
539 | 1556 | publish_hot_standby_credentials() | 1618 | publish_hot_standby_credentials() |
540 | 1619 | postgresql_hba = os.path.join( | ||
541 | 1620 | _get_postgresql_config_dir(), "pg_hba.conf") | ||
542 | 1557 | generate_postgresql_hba(postgresql_hba) | 1621 | generate_postgresql_hba(postgresql_hba) |
543 | 1558 | 1622 | ||
544 | 1559 | local_state.publish() | 1623 | local_state.publish() |
545 | @@ -1706,7 +1770,7 @@ | |||
546 | 1706 | lock. | 1770 | lock. |
547 | 1707 | ''' | 1771 | ''' |
548 | 1708 | import psycopg2 | 1772 | import psycopg2 |
550 | 1709 | key = long(config_data['advisory_lock_restart_key']) | 1773 | key = long(hookenv.config('advisory_lock_restart_key')) |
551 | 1710 | if exclusive: | 1774 | if exclusive: |
552 | 1711 | lock_function = 'pg_advisory_lock' | 1775 | lock_function = 'pg_advisory_lock' |
553 | 1712 | else: | 1776 | else: |
554 | @@ -1749,6 +1813,12 @@ | |||
555 | 1749 | postgresql_stop() | 1813 | postgresql_stop() |
556 | 1750 | log("Cloning master {}".format(master_unit)) | 1814 | log("Cloning master {}".format(master_unit)) |
557 | 1751 | 1815 | ||
558 | 1816 | config_data = hookenv.config() | ||
559 | 1817 | version = config_data['version'] | ||
560 | 1818 | cluster_name = config_data['cluster_name'] | ||
561 | 1819 | postgresql_cluster_dir = os.path.join( | ||
562 | 1820 | postgresql_data_dir, version, cluster_name) | ||
563 | 1821 | postgresql_config_dir = _get_postgresql_config_dir(config_data) | ||
564 | 1752 | cmd = [ | 1822 | cmd = [ |
565 | 1753 | 'sudo', '-E', # -E needed to locate pgpass file. | 1823 | 'sudo', '-E', # -E needed to locate pgpass file. |
566 | 1754 | '-u', 'postgres', 'pg_basebackup', '-D', postgresql_cluster_dir, | 1824 | '-u', 'postgres', 'pg_basebackup', '-D', postgresql_cluster_dir, |
567 | @@ -1802,6 +1872,11 @@ | |||
568 | 1802 | 1872 | ||
569 | 1803 | 1873 | ||
570 | 1804 | def postgresql_is_in_backup_mode(): | 1874 | def postgresql_is_in_backup_mode(): |
571 | 1875 | version = hookenv.config('version') | ||
572 | 1876 | cluster_name = hookenv.config('cluster_name') | ||
573 | 1877 | postgresql_cluster_dir = os.path.join( | ||
574 | 1878 | postgresql_data_dir, version, cluster_name) | ||
575 | 1879 | |||
576 | 1805 | return os.path.exists( | 1880 | return os.path.exists( |
577 | 1806 | os.path.join(postgresql_cluster_dir, 'backup_label')) | 1881 | os.path.join(postgresql_cluster_dir, 'backup_label')) |
578 | 1807 | 1882 | ||
579 | @@ -1919,30 +1994,28 @@ | |||
580 | 1919 | host.service_reload('nagios-nrpe-server') | 1994 | host.service_reload('nagios-nrpe-server') |
581 | 1920 | 1995 | ||
582 | 1921 | 1996 | ||
583 | 1997 | def _get_postgresql_config_dir(config_data=None): | ||
584 | 1998 | """ Return the directory path of the postgresql configuration files. """ | ||
585 | 1999 | if config_data == None: | ||
586 | 2000 | config_data = hookenv.config() | ||
587 | 2001 | version = config_data['version'] | ||
588 | 2002 | cluster_name = config_data['cluster_name'] | ||
589 | 2003 | return os.path.join("/etc/postgresql", version, cluster_name) | ||
590 | 2004 | |||
591 | 1922 | ############################################################################### | 2005 | ############################################################################### |
592 | 1923 | # Global variables | 2006 | # Global variables |
593 | 1924 | ############################################################################### | 2007 | ############################################################################### |
594 | 1925 | config_data = hookenv.config() | ||
595 | 1926 | version = config_data['version'] | ||
596 | 1927 | cluster_name = config_data['cluster_name'] | ||
597 | 1928 | postgresql_data_dir = "/var/lib/postgresql" | 2008 | postgresql_data_dir = "/var/lib/postgresql" |
605 | 1929 | postgresql_cluster_dir = os.path.join( | 2009 | postgresql_scripts_dir = os.path.join(postgresql_data_dir, 'scripts') |
606 | 1930 | postgresql_data_dir, version, cluster_name) | 2010 | postgresql_logs_dir = os.path.join(postgresql_data_dir, 'logs') |
607 | 1931 | postgresql_bin_dir = os.path.join('/usr/lib/postgresql', version, 'bin') | 2011 | |
608 | 1932 | postgresql_config_dir = os.path.join("/etc/postgresql", version, cluster_name) | 2012 | postgresql_sysctl = "/etc/sysctl.d/50-postgresql.conf" |
602 | 1933 | postgresql_config = os.path.join(postgresql_config_dir, "postgresql.conf") | ||
603 | 1934 | postgresql_ident = os.path.join(postgresql_config_dir, "pg_ident.conf") | ||
604 | 1935 | postgresql_hba = os.path.join(postgresql_config_dir, "pg_hba.conf") | ||
609 | 1936 | postgresql_crontab = "/etc/cron.d/postgresql" | 2013 | postgresql_crontab = "/etc/cron.d/postgresql" |
610 | 1937 | postgresql_service_config_dir = "/var/run/postgresql" | 2014 | postgresql_service_config_dir = "/var/run/postgresql" |
611 | 1938 | postgresql_scripts_dir = os.path.join(postgresql_data_dir, 'scripts') | ||
612 | 1939 | postgresql_backups_dir = ( | ||
613 | 1940 | config_data['backup_dir'].strip() or | ||
614 | 1941 | os.path.join(postgresql_data_dir, 'backups')) | ||
615 | 1942 | postgresql_logs_dir = os.path.join(postgresql_data_dir, 'logs') | ||
616 | 1943 | hook_name = os.path.basename(sys.argv[0]) | ||
617 | 1944 | replication_relation_types = ['master', 'slave', 'replication'] | 2015 | replication_relation_types = ['master', 'slave', 'replication'] |
618 | 1945 | local_state = State('local_state.pickle') | 2016 | local_state = State('local_state.pickle') |
619 | 2017 | hook_name = os.path.basename(sys.argv[0]) | ||
620 | 2018 | juju_log_dir = "/var/log/juju" | ||
621 | 1946 | 2019 | ||
622 | 1947 | 2020 | ||
623 | 1948 | if __name__ == '__main__': | 2021 | if __name__ == '__main__': |
624 | 1949 | 2022 | ||
625 | === added file 'hooks/test_hooks.py' | |||
626 | --- hooks/test_hooks.py 1970-01-01 00:00:00 +0000 | |||
627 | +++ hooks/test_hooks.py 2014-01-07 21:59:36 +0000 | |||
628 | @@ -0,0 +1,354 @@ | |||
629 | 1 | import mocker | ||
630 | 2 | import hooks | ||
631 | 3 | |||
632 | 4 | |||
633 | 5 | class TestJujuHost(object): | ||
634 | 6 | """ | ||
635 | 7 | Testing object to intercept charmhelper calls and inject data, or make sure | ||
636 | 8 | certain data is set. | ||
637 | 9 | """ | ||
638 | 10 | def write_file(self, file_path, contents, owner=None, group=None, | ||
639 | 11 | perms=None): | ||
640 | 12 | """ | ||
641 | 13 | Only write the file as requested. owner, group and perms untested. | ||
642 | 14 | """ | ||
643 | 15 | with open(file_path, 'w') as target: | ||
644 | 16 | target.write(contents) | ||
645 | 17 | |||
646 | 18 | def mkdir(self, dir_path, owner, group, perms): | ||
647 | 19 | """Not yet tested""" | ||
648 | 20 | pass | ||
649 | 21 | |||
650 | 22 | def service_start(self, service_name): | ||
651 | 23 | """Not yet tested""" | ||
652 | 24 | pass | ||
653 | 25 | |||
654 | 26 | def service_reload(self, service_name): | ||
655 | 27 | """Not yet tested""" | ||
656 | 28 | pass | ||
657 | 29 | |||
658 | 30 | def service_pwgen(self, service_name): | ||
659 | 31 | """Not yet tested""" | ||
660 | 32 | return "" | ||
661 | 33 | |||
662 | 34 | def service_stop(self, service_name): | ||
663 | 35 | """Not yet tested""" | ||
664 | 36 | pass | ||
665 | 37 | |||
666 | 38 | |||
667 | 39 | class TestJuju(object): | ||
668 | 40 | """ | ||
669 | 41 | Testing object to intercept juju calls and inject data, or make sure | ||
670 | 42 | certain data is set. | ||
671 | 43 | """ | ||
672 | 44 | |||
673 | 45 | _relation_data = {} | ||
674 | 46 | _relation_ids = {} | ||
675 | 47 | _relation_list = ("postgres/0",) | ||
676 | 48 | |||
677 | 49 | def __init__(self): | ||
678 | 50 | self._config = { | ||
679 | 51 | "admin_addresses": "", | ||
680 | 52 | "locale": "C", | ||
681 | 53 | "encoding": "UTF-8", | ||
682 | 54 | "extra_packages": "", | ||
683 | 55 | "dumpfile_location": "None", | ||
684 | 56 | "config_change_command": "reload", | ||
685 | 57 | "version": "9.1", | ||
686 | 58 | "cluster_name": "main", | ||
687 | 59 | "listen_ip": "*", | ||
688 | 60 | "listen_port": "5432", | ||
689 | 61 | "max_connections": "100", | ||
690 | 62 | "ssl": "True", | ||
691 | 63 | "log_min_duration_statement": -1, | ||
692 | 64 | "log_checkpoints": False, | ||
693 | 65 | "log_connections": False, | ||
694 | 66 | "log_disconnections": False, | ||
695 | 67 | "log_line_prefix": "%t ", | ||
696 | 68 | "log_lock_waits": False, | ||
697 | 69 | "log_timezone": "UTC", | ||
698 | 70 | "autovacuum": True, | ||
699 | 71 | "log_autovacuum_min_duration": -1, | ||
700 | 72 | "autovacuum_analyze_threshold": 50, | ||
701 | 73 | "autovacuum_vacuum_scale_factor": 0.2, | ||
702 | 74 | "autovacuum_analyze_scale_factor": 0.1, | ||
703 | 75 | "autovacuum_vacuum_cost_delay": "20ms", | ||
704 | 76 | "search_path": "\"$user\",public", | ||
705 | 77 | "standard_conforming_strings": True, | ||
706 | 78 | "hot_standby": False, | ||
707 | 79 | "hot_standby_feedback": False, | ||
708 | 80 | "wal_level": "minimal", | ||
709 | 81 | "max_wal_senders": 0, | ||
710 | 82 | "wal_keep_segments": 0, | ||
711 | 83 | "replicated_wal_keep_segments": 5000, | ||
712 | 84 | "archive_mode": False, | ||
713 | 85 | "archive_command": "", | ||
714 | 86 | "work_mem": "1MB", | ||
715 | 87 | "maintenance_work_mem": "1MB", | ||
716 | 88 | "performance_tuning": "auto", | ||
717 | 89 | "kernel_shmall": 0, | ||
718 | 90 | "kernel_shmmax": 0, | ||
719 | 91 | "shared_buffers": "", | ||
720 | 92 | "effective_cache_size": "", | ||
721 | 93 | "temp_buffers": "1MB", | ||
722 | 94 | "wal_buffers": "-1", | ||
723 | 95 | "checkpoint_segments": 3, | ||
724 | 96 | "random_page_cost": 4.0, | ||
725 | 97 | "volume_ephemeral_storage": True, | ||
726 | 98 | "volume_map": "", | ||
727 | 99 | "volume_dev_regexp": "/dev/db[b-z]", | ||
728 | 100 | "backup_dir": "/var/lib/postgresql/backups", | ||
729 | 101 | "backup_schedule": "13 4 * * *", | ||
730 | 102 | "backup_retention_count": 7, | ||
731 | 103 | "nagios_context": "juju", | ||
732 | 104 | "extra_archives": "", | ||
733 | 105 | "advisory_lock_restart_key": 765} | ||
734 | 106 | |||
735 | 107 | def relation_set(self, *args, **kwargs): | ||
736 | 108 | """ | ||
737 | 109 | Capture result of relation_set into _relation_data, which | ||
738 | 110 | can then be checked later. | ||
739 | 111 | """ | ||
740 | 112 | if "relation_id" in kwargs: | ||
741 | 113 | del kwargs["relation_id"] | ||
742 | 114 | self._relation_data = dict(self._relation_data, **kwargs) | ||
743 | 115 | for arg in args: | ||
744 | 116 | (key, value) = arg.split("=") | ||
745 | 117 | self._relation_data[key] = value | ||
746 | 118 | |||
747 | 119 | def relation_ids(self, relation_name="db-admin"): | ||
748 | 120 | """ | ||
749 | 121 | Return expected relation_ids for tests. Feel free to expand | ||
750 | 122 | as more tests are added. | ||
751 | 123 | """ | ||
752 | 124 | return [self._relation_ids[name] for name in self._relation_ids.keys() | ||
753 | 125 | if name.find(relation_name) == 0] | ||
754 | 126 | |||
755 | 127 | def related_units(self, relid="db-admin:5"): | ||
756 | 128 | """ | ||
757 | 129 | Return expected relation_ids for tests. Feel free to expand | ||
758 | 130 | as more tests are added. | ||
759 | 131 | """ | ||
760 | 132 | return [name for name, value in self._relation_ids.iteritems() | ||
761 | 133 | if value == relid] | ||
762 | 134 | |||
763 | 135 | def relation_list(self): | ||
764 | 136 | """ | ||
765 | 137 | Hardcode expected relation_list for tests. Feel free to expand | ||
766 | 138 | as more tests are added. | ||
767 | 139 | """ | ||
768 | 140 | return list(self._relation_list) | ||
769 | 141 | |||
770 | 142 | def unit_get(self, *args): | ||
771 | 143 | """ | ||
772 | 144 | for now the only thing this is called for is "public-address", | ||
773 | 145 | so it's a simplistic return. | ||
774 | 146 | """ | ||
775 | 147 | return "localhost" | ||
776 | 148 | |||
777 | 149 | def local_unit(self): | ||
778 | 150 | return hooks.os.environ["JUJU_UNIT_NAME"] | ||
779 | 151 | |||
780 | 152 | def charm_dir(self): | ||
781 | 153 | return hooks.os.environ["CHARM_DIR"] | ||
782 | 154 | |||
783 | 155 | def juju_log(self, *args, **kwargs): | ||
784 | 156 | pass | ||
785 | 157 | |||
786 | 158 | def log(self, *args, **kwargs): | ||
787 | 159 | pass | ||
788 | 160 | |||
789 | 161 | def config_get(self, scope=None): | ||
790 | 162 | if scope is None: | ||
791 | 163 | return self.config | ||
792 | 164 | else: | ||
793 | 165 | return self.config[scope] | ||
794 | 166 | |||
795 | 167 | def relation_get(self, scope=None, unit_name=None, relation_id=None): | ||
796 | 168 | pass | ||
797 | 169 | |||
798 | 170 | |||
799 | 171 | class TestHooks(mocker.MockerTestCase): | ||
800 | 172 | |||
801 | 173 | def setUp(self): | ||
802 | 174 | hooks.hookenv = TestJuju() | ||
803 | 175 | hooks.host = TestJujuHost() | ||
804 | 176 | hooks.juju_log_dir = self.makeDir() | ||
805 | 177 | hooks.hookenv.config = lambda: hooks.hookenv._config | ||
806 | 178 | #hooks.hookenv.localunit = lambda: "localhost" | ||
807 | 179 | hooks.os.environ["JUJU_UNIT_NAME"] = "landscape/1" | ||
808 | 180 | hooks.postgresql_sysctl = self.makeFile() | ||
809 | 181 | hooks._get_system_ram = lambda: 1024 # MB | ||
810 | 182 | hooks._get_page_size = lambda: 1024 * 1024 # bytes | ||
811 | 183 | self.maxDiff = None | ||
812 | 184 | |||
813 | 185 | def assertFileContains(self, filename, lines): | ||
814 | 186 | """Make sure strings exist in a file.""" | ||
815 | 187 | with open(filename, "r") as fp: | ||
816 | 188 | contents = fp.read() | ||
817 | 189 | for line in lines: | ||
818 | 190 | self.assertIn(line, contents) | ||
819 | 191 | |||
820 | 192 | def assertNotFileContains(self, filename, lines): | ||
821 | 193 | """Make sure strings do not exist in a file.""" | ||
822 | 194 | with open(filename, "r") as fp: | ||
823 | 195 | contents = fp.read() | ||
824 | 196 | for line in lines: | ||
825 | 197 | self.assertNotIn(line, contents) | ||
826 | 198 | |||
827 | 199 | def assertFilesEqual(self, file1, file2): | ||
828 | 200 | """Given two filenames, compare them.""" | ||
829 | 201 | with open(file1, "r") as fp1: | ||
830 | 202 | contents1 = fp1.read() | ||
831 | 203 | with open(file2, "r") as fp2: | ||
832 | 204 | contents2 = fp2.read() | ||
833 | 205 | self.assertEqual(contents1, contents2) | ||
834 | 206 | |||
835 | 207 | |||
836 | 208 | class TestHooksService(TestHooks): | ||
837 | 209 | |||
838 | 210 | def test_create_postgresql_config_wal_no_replication(self): | ||
839 | 211 | """ | ||
840 | 212 | When postgresql is in C{standalone} mode, and participates in no | ||
841 | 213 | C{replication} relations, default wal settings will be present. | ||
842 | 214 | """ | ||
843 | 215 | config_outfile = self.makeFile() | ||
844 | 216 | run = self.mocker.replace(hooks.run) | ||
845 | 217 | run("sysctl -p %s" % hooks.postgresql_sysctl) | ||
846 | 218 | self.mocker.result(True) | ||
847 | 219 | self.mocker.replay() | ||
848 | 220 | hooks.create_postgresql_config(config_outfile) | ||
849 | 221 | self.assertFileContains( | ||
850 | 222 | config_outfile, | ||
851 | 223 | ["wal_buffers = -1", "wal_level = minimal", "max_wal_senders = 0", | ||
852 | 224 | "wal_keep_segments = 0"]) | ||
853 | 225 | |||
854 | 226 | def test_create_postgresql_config_wal_with_replication(self): | ||
855 | 227 | """ | ||
856 | 228 | When postgresql is in C{replicated} mode, and participates in a | ||
857 | 229 | C{replication} relation, C{hot_standby} will be set to C{on}, | ||
858 | 230 | C{wal_level} will be enabled as C{hot_standby} and the | ||
859 | 231 | C{max_wall_senders} will match the count of replication relations. | ||
860 | 232 | The value of C{wal_keep_segments} will be the maximum of the configured | ||
861 | 233 | C{wal_keep_segments} and C{replicated_wal_keep_segments}. | ||
862 | 234 | """ | ||
863 | 235 | self.addCleanup( | ||
864 | 236 | setattr, hooks.hookenv, "_relation_ids", {}) | ||
865 | 237 | hooks.hookenv._relation_ids = { | ||
866 | 238 | "replication/0": "db-admin:5", "replication/1": "db-admin:6"} | ||
867 | 239 | config_outfile = self.makeFile() | ||
868 | 240 | run = self.mocker.replace(hooks.run) | ||
869 | 241 | run("sysctl -p %s" % hooks.postgresql_sysctl) | ||
870 | 242 | self.mocker.result(True) | ||
871 | 243 | self.mocker.replay() | ||
872 | 244 | hooks.create_postgresql_config(config_outfile) | ||
873 | 245 | self.assertFileContains( | ||
874 | 246 | config_outfile, | ||
875 | 247 | ["hot_standby = on", "wal_buffers = -1", "wal_level = hot_standby", | ||
876 | 248 | "max_wal_senders = 2", "wal_keep_segments = 5000"]) | ||
877 | 249 | |||
878 | 250 | def test_create_postgresql_config_wal_with_replication_max_override(self): | ||
879 | 251 | """ | ||
880 | 252 | When postgresql is in C{replicated} mode, and participates in a | ||
881 | 253 | C{replication} relation, C{hot_standby} will be set to C{on}, | ||
882 | 254 | C{wal_level} will be enabled as C{hot_standby}. The written value for | ||
883 | 255 | C{max_wal_senders} will be the maximum of replication slave count and | ||
884 | 256 | the configuration value for C{max_wal_senders}. | ||
885 | 257 | The written value of C{wal_keep_segments} will be | ||
886 | 258 | the maximum of the configuration C{wal_keep_segments} and | ||
887 | 259 | C{replicated_wal_keep_segments}. | ||
888 | 260 | """ | ||
889 | 261 | self.addCleanup( | ||
890 | 262 | setattr, hooks.hookenv, "_relation_ids", ()) | ||
891 | 263 | hooks.hookenv._relation_ids = { | ||
892 | 264 | "replication/0": "db-admin:5", "replication/1": "db-admin:6"} | ||
893 | 265 | hooks.hookenv._config["max_wal_senders"] = "3" | ||
894 | 266 | hooks.hookenv._config["wal_keep_segments"] = 1000 | ||
895 | 267 | hooks.hookenv._config["replicated_wal_keep_segments"] = 999 | ||
896 | 268 | config_outfile = self.makeFile() | ||
897 | 269 | run = self.mocker.replace(hooks.run) | ||
898 | 270 | run("sysctl -p %s" % hooks.postgresql_sysctl) | ||
899 | 271 | self.mocker.result(True) | ||
900 | 272 | self.mocker.replay() | ||
901 | 273 | hooks.create_postgresql_config(config_outfile) | ||
902 | 274 | self.assertFileContains( | ||
903 | 275 | config_outfile, | ||
904 | 276 | ["hot_standby = on", "wal_buffers = -1", "wal_level = hot_standby", | ||
905 | 277 | "max_wal_senders = 3", "wal_keep_segments = 1000"]) | ||
906 | 278 | |||
907 | 279 | def test_create_postgresql_config_performance_tune_auto_large_ram(self): | ||
908 | 280 | """ | ||
909 | 281 | When configuration attribute C{performance_tune} is set to C{auto} and | ||
910 | 282 | total RAM on a system is > 1023MB. It will automatically calculate | ||
911 | 283 | values for the following attributes if these attributes were left as | ||
912 | 284 | default values: | ||
913 | 285 | - C{effective_cache_size} set to 75% of total RAM in MegaBytes | ||
914 | 286 | - C{shared_buffers} set to 25% of total RAM in MegaBytes | ||
915 | 287 | - C{kernel_shmmax} set to total RAM in bytes | ||
916 | 288 | - C{kernel_shmall} equal to kernel_shmmax in pages | ||
917 | 289 | """ | ||
918 | 290 | config_outfile = self.makeFile() | ||
919 | 291 | run = self.mocker.replace(hooks.run) | ||
920 | 292 | run("sysctl -p %s" % hooks.postgresql_sysctl) | ||
921 | 293 | self.mocker.result(True) | ||
922 | 294 | self.mocker.replay() | ||
923 | 295 | hooks.create_postgresql_config(config_outfile) | ||
924 | 296 | self.assertFileContains( | ||
925 | 297 | config_outfile, | ||
926 | 298 | ["shared_buffers = 256MB", "effective_cache_size = 768MB"]) | ||
927 | 299 | self.assertFileContains( | ||
928 | 300 | hooks.postgresql_sysctl, | ||
929 | 301 | ["kernel.shmall = 1025\nkernel.shmmax = 1073742848"]) | ||
930 | 302 | |||
931 | 303 | def test_create_postgresql_config_performance_tune_auto_small_ram(self): | ||
932 | 304 | """ | ||
933 | 305 | When configuration attribute C{performance_tune} is set to C{auto} and | ||
934 | 306 | total RAM on a system is <= 1023MB. It will automatically calculate | ||
935 | 307 | values for the following attributes if these attributes were left as | ||
936 | 308 | default values: | ||
937 | 309 | - C{effective_cache_size} set to 75% of total RAM in MegaBytes | ||
938 | 310 | - C{shared_buffers} set to 15% of total RAM in MegaBytes | ||
939 | 311 | - C{kernel_shmmax} set to total RAM in bytes | ||
940 | 312 | - C{kernel_shmall} equal to kernel_shmmax in pages | ||
941 | 313 | """ | ||
942 | 314 | hooks._get_system_ram = lambda: 1023 # MB | ||
943 | 315 | config_outfile = self.makeFile() | ||
944 | 316 | run = self.mocker.replace(hooks.run) | ||
945 | 317 | run("sysctl -p %s" % hooks.postgresql_sysctl) | ||
946 | 318 | self.mocker.result(True) | ||
947 | 319 | self.mocker.replay() | ||
948 | 320 | hooks.create_postgresql_config(config_outfile) | ||
949 | 321 | self.assertFileContains( | ||
950 | 322 | config_outfile, | ||
951 | 323 | ["shared_buffers = 153MB", "effective_cache_size = 767MB"]) | ||
952 | 324 | self.assertFileContains( | ||
953 | 325 | hooks.postgresql_sysctl, | ||
954 | 326 | ["kernel.shmall = 1024\nkernel.shmmax = 1072694272"]) | ||
955 | 327 | |||
956 | 328 | def test_create_postgresql_config_performance_tune_auto_overridden(self): | ||
957 | 329 | """ | ||
958 | 330 | When configuration attribute C{performance_tune} is set to C{auto} any | ||
959 | 331 | non-default values for the configuration parameters below will be used | ||
960 | 332 | instead of the automatically calculated values. | ||
961 | 333 | - C{effective_cache_size} | ||
962 | 334 | - C{shared_buffers} | ||
963 | 335 | - C{kernel_shmmax} | ||
964 | 336 | - C{kernel_shmall} | ||
965 | 337 | """ | ||
966 | 338 | hooks.hookenv._config["effective_cache_size"] = "999MB" | ||
967 | 339 | hooks.hookenv._config["shared_buffers"] = "101MB" | ||
968 | 340 | hooks.hookenv._config["kernel_shmmax"] = 50000 | ||
969 | 341 | hooks.hookenv._config["kernel_shmall"] = 500 | ||
970 | 342 | hooks._get_system_ram = lambda: 1023 # MB | ||
971 | 343 | config_outfile = self.makeFile() | ||
972 | 344 | run = self.mocker.replace(hooks.run) | ||
973 | 345 | run("sysctl -p %s" % hooks.postgresql_sysctl) | ||
974 | 346 | self.mocker.result(True) | ||
975 | 347 | self.mocker.replay() | ||
976 | 348 | hooks.create_postgresql_config(config_outfile) | ||
977 | 349 | self.assertFileContains( | ||
978 | 350 | config_outfile, | ||
979 | 351 | ["shared_buffers = 101MB", "effective_cache_size = 999MB"]) | ||
980 | 352 | self.assertFileContains( | ||
981 | 353 | hooks.postgresql_sysctl, | ||
982 | 354 | ["kernel.shmall = 1024\nkernel.shmmax = 1072694272"]) | ||
983 | 0 | 355 | ||
984 | === added file 'revision' | |||
985 | --- revision 1970-01-01 00:00:00 +0000 | |||
986 | +++ revision 2014-01-07 21:59:36 +0000 | |||
987 | @@ -0,0 +1,1 @@ | |||
988 | 1 | 2 | ||
989 | 0 | 2 | ||
990 | === modified file 'templates/postgresql.conf.tmpl' | |||
991 | --- templates/postgresql.conf.tmpl 2013-01-24 11:28:39 +0000 | |||
992 | +++ templates/postgresql.conf.tmpl 2014-01-07 21:59:36 +0000 | |||
993 | @@ -40,7 +40,7 @@ | |||
994 | 40 | {% if shared_buffers != "" -%} | 40 | {% if shared_buffers != "" -%} |
995 | 41 | shared_buffers = {{shared_buffers}} | 41 | shared_buffers = {{shared_buffers}} |
996 | 42 | {% endif -%} | 42 | {% endif -%} |
998 | 43 | {% if temp_buffers!= "" -%} | 43 | {% if temp_buffers != "" -%} |
999 | 44 | temp_buffers = {{temp_buffers}} | 44 | temp_buffers = {{temp_buffers}} |
1000 | 45 | {% endif -%} | 45 | {% endif -%} |
1001 | 46 | {% if work_mem != "" -%} | 46 | {% if work_mem != "" -%} |
Makefile:
[0] make two targets, test and integration-test
[1] remove ls-lint, as that is a landscape specific bzr plugin