49
49
myname = hookenv.local_unit().replace('/', '-')
50
50
ts = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())
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:
52
52
f.write('{} {}: {}\n'.format(ts, lvl, msg))
53
53
hookenv.log(msg, lvl)
155
155
# None config state is invalid - we should not serve
156
156
def volume_get_volume_id():
157
ephemeral_storage = config_data['volume-ephemeral-storage']
157
ephemeral_storage = hookenv.config('volume-ephemeral-storage')
158
158
volid = volume_get_volid_from_volume_map()
159
159
juju_unit_name = hookenv.local_unit()
160
160
if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']:
203
204
log("Disabling PostgreSQL startup in {}".format(startup_file))
205
contents = Template(open("templates/start_conf.tmpl").read()).render(
206
template_file = "{}/templates/start_conf.tmpl".format(hookenv.charm_dir())
207
contents = Template(open(template_file).read()).render({'mode': mode})
208
209
startup_file, contents, 'postgres', 'postgres', perms=0o644)
330
def get_service_port(postgresql_config):
331
def get_service_port(config_file):
331
332
'''Return the port PostgreSQL is listening on.'''
332
if not os.path.exists(postgresql_config):
333
if not os.path.exists(config_file):
334
postgresql_config = open(postgresql_config, 'r').read()
335
postgresql_config = open(config_file, 'r').read()
335
336
port = re.search("port.*=(.*)", postgresql_config).group(1).strip()
342
def create_postgresql_config(postgresql_config):
343
def _get_system_ram():
344
""" Return the system ram in Megabytes """
346
return psutil.phymem_usage()[0] / (1024 ** 2)
349
def _get_page_size():
350
""" Return the operating system's configured PAGE_SIZE """
351
return int(run("getconf PAGE_SIZE")) # frequently 4096
354
def create_postgresql_config(config_file):
343
355
'''Create the postgresql.conf file'''
356
config_data = hookenv.config()
344
357
if config_data["performance_tuning"] == "auto":
346
359
# http://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server
347
360
# num_cpus is not being used ... commenting it out ... negronjl
348
361
#num_cpus = run("cat /proc/cpuinfo | grep processor | wc -l")
349
total_ram = run("free -m | grep Mem | awk '{print $2}'")
362
total_ram = _get_system_ram()
350
363
if not config_data["effective_cache_size"]:
351
364
config_data["effective_cache_size"] = \
352
365
"%sMB" % (int(int(total_ram) * 0.75),)
358
371
config_data["shared_buffers"] = \
359
372
"%sMB" % (int(int(total_ram) * 0.15),)
360
# XXX: This is very messy - should probably be a subordinate charm
361
conf_file = open("/etc/sysctl.d/50-postgresql.conf", "w")
362
conf_file.write("kernel.sem = 250 32000 100 1024\n")
363
conf_file.write("kernel.shmall = %s\n" %
364
((int(total_ram) * 1024 * 1024) + 1024),)
365
conf_file.write("kernel.shmmax = %s\n" %
366
((int(total_ram) * 1024 * 1024) + 1024),)
368
run("sysctl -p /etc/sysctl.d/50-postgresql.conf")
373
config_data["kernel_shmmax"] = (int(total_ram) * 1024 * 1024) + 1024
374
config_data["kernel_shmall"] = config_data["kernel_shmmax"]
376
# XXX: This is very messy - should probably be a subordinate charm
377
lines = ["kernel.sem = 250 32000 100 1024\n"]
378
if config_data["kernel_shmall"] > 0:
379
# Convert config kernel_shmall (bytes) to pages
380
page_size = _get_page_size()
381
num_pages = config_data["kernel_shmall"] / page_size
382
if (config_data["kernel_shmall"] % page_size) > 0:
384
lines.append("kernel.shmall = %s\n" % num_pages)
385
if config_data["kernel_shmmax"] > 0:
386
lines.append("kernel.shmmax = %s\n" % config_data["kernel_shmmax"])
387
host.write_file(postgresql_sysctl, ''.join(lines), perms=0600)
388
run("sysctl -p {}".format(postgresql_sysctl))
370
390
# If we are replicating, some settings may need to be overridden to
371
391
# certain minimum levels.
384
404
# Send config data to the template
385
405
# Return it as pg_config
406
charm_dir = hookenv.charm_dir()
407
template_file = "{}/templates/postgresql.conf.tmpl".format(charm_dir)
386
408
pg_config = Template(
387
open("templates/postgresql.conf.tmpl").read()).render(config_data)
409
open(template_file).read()).render(config_data)
389
postgresql_config, pg_config,
411
config_file, pg_config,
390
412
owner="postgres", group="postgres", perms=0600)
392
414
local_state['saved_config'] = config_data
393
415
local_state.save()
396
def create_postgresql_ident(postgresql_ident):
418
def create_postgresql_ident(output_file):
397
419
'''Create the pg_ident.conf file.'''
399
pg_ident_template = Template(
400
open("templates/pg_ident.conf.tmpl").read())
421
charm_dir = hookenv.charm_dir()
422
template_file = "{}/templates/pg_ident.conf.tmpl".format(charm_dir)
423
pg_ident_template = Template(open(template_file).read())
402
postgresql_ident, pg_ident_template.render(ident_data),
425
output_file, pg_ident_template.render(ident_data),
403
426
owner="postgres", group="postgres", perms=0600)
406
429
def generate_postgresql_hba(
407
postgresql_hba, user=None, schema_user=None, database=None):
430
output_file, user=None, schema_user=None, database=None):
408
431
'''Create the pg_hba.conf file.'''
410
433
# Per Bug #1117542, when generating the postgresql_hba file we
525
549
'private-address': munge_address(admin_ip)}
526
550
relation_data.append(admin_host)
528
pg_hba_template = Template(open("templates/pg_hba.conf.tmpl").read())
552
template_file = "{}/templates/pg_hba.conf.tmpl".format(hookenv.charm_dir())
553
pg_hba_template = Template(open(template_file).read())
530
postgresql_hba, pg_hba_template.render(access_list=relation_data),
555
output_file, pg_hba_template.render(access_list=relation_data),
531
556
owner="postgres", group="postgres", perms=0600)
532
557
postgresql_reload()
539
564
relid, {"allowed-units": " ".join(unit_sorted(allowed_units))})
542
def install_postgresql_crontab(postgresql_ident):
567
def install_postgresql_crontab(output_file):
543
568
'''Create the postgres user's crontab'''
569
config_data = hookenv.config()
545
571
'backup_schedule': config_data["backup_schedule"],
546
572
'scripts_dir': postgresql_scripts_dir,
547
573
'backup_days': config_data["backup_retention_count"],
575
charm_dir = hookenv.charm_dir()
576
template_file = "{}/templates/postgres.cron.tmpl".format(charm_dir)
549
577
crontab_template = Template(
550
open("templates/postgres.cron.tmpl").read()).render(crontab_data)
551
host.write_file('/etc/cron.d/postgres', crontab_template, perms=0600)
578
open(template_file).read()).render(crontab_data)
579
host.write_file(output_file, crontab_template, perms=0600)
554
582
def create_recovery_conf(master_host, restart_on_change=False):
583
version = hookenv.config('version')
584
cluster_name = hookenv.config('cluster_name')
585
postgresql_cluster_dir = os.path.join(
586
postgresql_data_dir, version, cluster_name)
555
588
recovery_conf_path = os.path.join(postgresql_cluster_dir, 'recovery.conf')
556
589
if os.path.exists(recovery_conf_path):
557
590
old_recovery_conf = open(recovery_conf_path, 'r').read()
559
592
old_recovery_conf = None
561
recovery_conf = Template(
562
open("templates/recovery.conf.tmpl").read()).render({
594
charm_dir = hookenv.charm_dir()
595
template_file = "{}/templates/recovery.conf.tmpl".format(charm_dir)
596
recovery_conf = Template(open(template_file).read()).render({
563
597
'host': master_host,
564
598
'password': local_state['replication_password']})
565
599
log(recovery_conf, DEBUG)
578
612
# Returns a string containing the postgresql config or
580
614
#------------------------------------------------------------------------------
581
def load_postgresql_config(postgresql_config):
582
if os.path.isfile(postgresql_config):
583
return(open(postgresql_config).read())
615
def load_postgresql_config(config_file):
616
if os.path.isfile(config_file):
617
return(open(config_file).read())
677
711
# - manipulate /var/lib/postgresql/VERSION/CLUSTER symlink
678
712
#------------------------------------------------------------------------------
679
713
def config_changed_volume_apply():
680
data_directory_path = postgresql_cluster_dir
714
version = hookenv.config('version')
715
cluster_name = hookenv.config('cluster_name')
716
data_directory_path = os.path.join(
717
postgresql_data_dir, version, cluster_name)
681
719
assert(data_directory_path)
682
720
volid = volume_get_volume_id()
698
736
mount_point = volume_mount_point_from_volid(volid)
699
737
new_pg_dir = os.path.join(mount_point, "postgresql")
700
738
new_pg_version_cluster_dir = os.path.join(
701
new_pg_dir, config_data["version"], config_data["cluster_name"])
739
new_pg_dir, version, cluster_name)
702
740
if not mount_point:
704
742
"invalid mount point from volid = {}, "
724
762
# /var/lib/postgresql/9.1/main
725
763
curr_dir_stat = os.stat(data_directory_path)
726
764
for new_dir in [new_pg_dir,
727
os.path.join(new_pg_dir, config_data["version"]),
765
os.path.join(new_pg_dir, version),
728
766
new_pg_version_cluster_dir]:
729
767
if not os.path.isdir(new_dir):
730
768
log("mkdir %s".format(new_dir))
783
821
def config_changed(force_restart=False):
784
update_repos_and_packages()
822
config_data = hookenv.config()
823
update_repos_and_packages(config_data["version"])
786
825
# Trigger volume initialization logic for permanent storage
787
826
volid = volume_get_volume_id()
813
852
"Disabled and stopped postgresql service "
814
853
"(config_changed_volume_apply failure)", ERROR)
856
postgresql_config_dir = _get_postgresql_config_dir(config_data)
857
postgresql_config = os.path.join(postgresql_config_dir, "postgresql.conf")
858
postgresql_hba = os.path.join(postgresql_config_dir, "pg_hba.conf")
859
postgresql_ident = os.path.join(postgresql_config_dir, "pg_ident.conf")
816
861
current_service_port = get_service_port(postgresql_config)
817
862
create_postgresql_config(postgresql_config)
818
863
generate_postgresql_hba(postgresql_hba)
819
864
create_postgresql_ident(postgresql_ident)
820
866
updated_service_port = config_data["listen_port"]
821
867
update_service_port(current_service_port, updated_service_port)
822
868
update_nrpe_checks()
832
878
if os.path.isfile(f) and os.access(f, os.X_OK):
833
879
subprocess.check_call(['sh', '-c', f])
835
update_repos_and_packages()
881
config_data = hookenv.config()
882
update_repos_and_packages(config_data["version"])
837
883
if not 'state' in local_state:
838
884
# Fresh installation. Because this function is invoked by both
839
885
# the install hook and the upgrade-charm hook, we need to guard
848
894
run("pg_createcluster --locale='{}' --encoding='{}' 9.1 main".format(
849
895
config_data['locale'], config_data['encoding']))
897
postgresql_backups_dir = (
898
config_data['backup_dir'].strip() or
899
os.path.join(postgresql_data_dir, 'backups'))
851
901
host.mkdir(postgresql_backups_dir, owner="postgres", perms=0o755)
852
902
host.mkdir(postgresql_scripts_dir, owner="postgres", perms=0o755)
853
903
host.mkdir(postgresql_logs_dir, owner="postgres", perms=0o755)
857
907
'scripts_dir': postgresql_scripts_dir,
858
908
'logs_dir': postgresql_logs_dir,
860
dump_script = Template(
861
open("templates/dump-pg-db.tmpl").read()).render(paths)
862
backup_job = Template(
863
open("templates/pg_backup_job.tmpl").read()).render(paths)
910
charm_dir = hookenv.charm_dir()
911
template_file = "{}/templates/dump-pg-db.tmpl".format(charm_dir)
912
dump_script = Template(open(template_file).read()).render(paths)
913
template_file = "{}/templates/pg_backup_job.tmpl".format(charm_dir)
914
backup_job = Template(open(template_file).read()).render(paths)
865
916
'{}/dump-pg-db'.format(postgresql_scripts_dir),
866
917
dump_script, perms=0755)
1245
1298
run_sql_as_postgres(sql, AsIs(quote_identifier(database)),
1246
1299
AsIs(quote_identifier(user + "_schema")))
1301
postgresql_hba = os.path.join(_get_postgresql_config_dir(), "pg_hba.conf")
1248
1302
generate_postgresql_hba(postgresql_hba)
1250
1304
# Cleanup our local state.
1261
1315
sql = "ALTER USER %s NOSUPERUSER"
1262
1316
run_sql_as_postgres(sql, AsIs(quote_identifier(user)))
1318
postgresql_hba = os.path.join(_get_postgresql_config_dir(), "pg_hba.conf")
1264
1319
generate_postgresql_hba(postgresql_hba)
1266
1321
# Cleanup our local state.
1267
1322
snapshot_relations()
1270
def update_repos_and_packages():
1325
def update_repos_and_packages(version):
1271
1326
extra_repos = hookenv.config('extra_archives')
1272
1327
extra_repos_added = local_state.setdefault('extra_repos_added', set())
1273
1328
if extra_repos:
1284
1339
# It might have been better for debversion and plpython to only get
1285
1340
# installed if they were listed in the extra-packages config item,
1286
1341
# but they predate this feature.
1287
packages = ["postgresql-%s" % config_data["version"],
1288
"postgresql-contrib-%s" % config_data["version"],
1289
"postgresql-plpython-%s" % config_data["version"],
1290
"postgresql-%s-debversion" % config_data["version"],
1342
packages = ["python-psutil", # to obtain system RAM from python
1343
"libc-bin", # for getconf
1344
"postgresql-%s" % version,
1345
"postgresql-contrib-%s" % version,
1346
"postgresql-plpython-%s" % version,
1347
"postgresql-%s-debversion" % version,
1291
1348
"python-jinja2", "syslinux", "python-psycopg2"]
1292
1349
packages.extend((hookenv.config('extra-packages') or '').split())
1293
1350
packages = fetch.filter_installed_packages(packages)
1352
1409
def promote_database():
1353
1410
'''Take the database out of recovery mode.'''
1411
config_data = hookenv.config()
1412
version = config_data['version']
1413
cluster_name = config_data['cluster_name']
1414
postgresql_cluster_dir = os.path.join(
1415
postgresql_data_dir, version, cluster_name)
1354
1416
recovery_conf = os.path.join(postgresql_cluster_dir, 'recovery.conf')
1355
1417
if os.path.exists(recovery_conf):
1356
1418
# Rather than using 'pg_ctl promote', we do the promotion
1749
1813
postgresql_stop()
1750
1814
log("Cloning master {}".format(master_unit))
1816
config_data = hookenv.config()
1817
version = config_data['version']
1818
cluster_name = config_data['cluster_name']
1819
postgresql_cluster_dir = os.path.join(
1820
postgresql_data_dir, version, cluster_name)
1821
postgresql_config_dir = _get_postgresql_config_dir(config_data)
1753
1823
'sudo', '-E', # -E needed to locate pgpass file.
1754
1824
'-u', 'postgres', 'pg_basebackup', '-D', postgresql_cluster_dir,
1804
1874
def postgresql_is_in_backup_mode():
1875
version = hookenv.config('version')
1876
cluster_name = hookenv.config('cluster_name')
1877
postgresql_cluster_dir = os.path.join(
1878
postgresql_data_dir, version, cluster_name)
1805
1880
return os.path.exists(
1806
1881
os.path.join(postgresql_cluster_dir, 'backup_label'))
1919
1994
host.service_reload('nagios-nrpe-server')
1997
def _get_postgresql_config_dir(config_data=None):
1998
""" Return the directory path of the postgresql configuration files. """
1999
if config_data == None:
2000
config_data = hookenv.config()
2001
version = config_data['version']
2002
cluster_name = config_data['cluster_name']
2003
return os.path.join("/etc/postgresql", version, cluster_name)
1922
2005
###############################################################################
1923
2006
# Global variables
1924
2007
###############################################################################
1925
config_data = hookenv.config()
1926
version = config_data['version']
1927
cluster_name = config_data['cluster_name']
1928
2008
postgresql_data_dir = "/var/lib/postgresql"
1929
postgresql_cluster_dir = os.path.join(
1930
postgresql_data_dir, version, cluster_name)
1931
postgresql_bin_dir = os.path.join('/usr/lib/postgresql', version, 'bin')
1932
postgresql_config_dir = os.path.join("/etc/postgresql", version, cluster_name)
1933
postgresql_config = os.path.join(postgresql_config_dir, "postgresql.conf")
1934
postgresql_ident = os.path.join(postgresql_config_dir, "pg_ident.conf")
1935
postgresql_hba = os.path.join(postgresql_config_dir, "pg_hba.conf")
2009
postgresql_scripts_dir = os.path.join(postgresql_data_dir, 'scripts')
2010
postgresql_logs_dir = os.path.join(postgresql_data_dir, 'logs')
2012
postgresql_sysctl = "/etc/sysctl.d/50-postgresql.conf"
1936
2013
postgresql_crontab = "/etc/cron.d/postgresql"
1937
2014
postgresql_service_config_dir = "/var/run/postgresql"
1938
postgresql_scripts_dir = os.path.join(postgresql_data_dir, 'scripts')
1939
postgresql_backups_dir = (
1940
config_data['backup_dir'].strip() or
1941
os.path.join(postgresql_data_dir, 'backups'))
1942
postgresql_logs_dir = os.path.join(postgresql_data_dir, 'logs')
1943
hook_name = os.path.basename(sys.argv[0])
1944
2015
replication_relation_types = ['master', 'slave', 'replication']
1945
2016
local_state = State('local_state.pickle')
2017
hook_name = os.path.basename(sys.argv[0])
2018
juju_log_dir = "/var/log/juju"
1948
2021
if __name__ == '__main__':