152
###############################################################################
154
###############################################################################
155
#------------------------------
156
# Get volume-id from juju config "volume-map" dictionary as
157
# volume-map[JUJU_UNIT_NAME]
160
#------------------------------
161
def volume_get_volid_from_volume_map():
164
volume_map = yaml.load(hookenv.config('volume-map').strip())
166
return volume_map.get(os.environ['JUJU_UNIT_NAME'])
167
except ConstructorError as e:
168
log("invalid YAML in 'volume-map': {}".format(e), WARNING)
172
# Is this volume_id permanent ?
173
# @returns True if volid set and not --ephemeral, else:
175
def volume_is_permanent(volid):
176
if volid and volid != "--ephemeral":
181
#------------------------------
182
# Returns a mount point from passed vol-id, e.g. /srv/juju/vol-000012345
184
# @param volid volume id (as e.g. EBS volid)
185
# @return mntpoint_path eg /srv/juju/vol-000012345
186
#------------------------------
187
def volume_mount_point_from_volid(volid):
188
if volid and volume_is_permanent(volid):
189
return "/srv/juju/%s" % volid
193
# Do we have a valid storage state?
195
# None config state is invalid - we should not serve
196
def volume_get_volume_id():
197
ephemeral_storage = hookenv.config('volume-ephemeral-storage')
198
volid = volume_get_volid_from_volume_map()
199
juju_unit_name = hookenv.local_unit()
200
if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']:
203
"volume-ephemeral-storage is True, but " +
204
"volume-map[{!r}] -> {}".format(juju_unit_name, volid), ERROR)
211
"volume-ephemeral-storage is False, but "
212
"no volid found for volume-map[{!r}]".format(
213
hookenv.local_unit()), ERROR)
218
# Initialize and/or mount permanent storage, it straightly calls
220
def volume_init_and_mount(volid):
221
command = ("scripts/volume-common.sh call " +
222
"volume_init_and_mount %s" % volid)
223
output = run(command)
224
if output.find("ERROR") >= 0:
229
150
def volume_get_all_mounted():
230
command = ("mount |egrep /srv/juju")
151
command = ("mount |egrep %s" % external_volume_mount)
231
152
status, output = commands.getstatusoutput(command)
893
814
# NOTE the only 2 "True" return points:
894
815
# 1) symlink already pointing to existing storage (no-op)
895
816
# 2) new storage properly initialized:
896
# - volume: initialized if not already (fdisk, mkfs),
897
# mounts it to e.g.: /srv/juju/vol-000012345
898
817
# - if fresh new storage dir: rsync existing data
899
818
# - manipulate /var/lib/postgresql/VERSION/CLUSTER symlink
900
819
#------------------------------------------------------------------------------
901
def config_changed_volume_apply():
820
def config_changed_volume_apply(mount_point):
902
821
version = pg_version()
903
822
cluster_name = hookenv.config('cluster_name')
904
823
data_directory_path = os.path.join(
905
824
postgresql_data_dir, version, cluster_name)
907
826
assert(data_directory_path)
908
volid = volume_get_volume_id()
910
if volume_is_permanent(volid):
911
if not volume_init_and_mount(volid):
913
"volume_init_and_mount failed, not applying changes",
917
if not os.path.exists(data_directory_path):
919
"postgresql data dir {} not found, "
920
"not applying changes.".format(data_directory_path),
924
mount_point = volume_mount_point_from_volid(volid)
925
new_pg_dir = os.path.join(mount_point, "postgresql")
926
new_pg_version_cluster_dir = os.path.join(
927
new_pg_dir, version, cluster_name)
930
"invalid mount point from volid = {}, "
931
"not applying changes.".format(mount_point), ERROR)
934
if ((os.path.islink(data_directory_path) and
935
os.readlink(data_directory_path) == new_pg_version_cluster_dir and
936
os.path.isdir(new_pg_version_cluster_dir))):
938
"postgresql data dir '%s' already points "
939
"to {}, skipping storage changes.".format(
940
data_directory_path, new_pg_version_cluster_dir))
942
"existing-symlink: to fix/avoid UID changes from "
943
"previous units, doing: "
944
"chown -R postgres:postgres {}".format(new_pg_dir))
945
run("chown -R postgres:postgres %s" % new_pg_dir)
948
# Create a directory structure below "new" mount_point, as e.g.:
949
# /srv/juju/vol-000012345/postgresql/9.1/main , which "mimics":
950
# /var/lib/postgresql/9.1/main
951
curr_dir_stat = os.stat(data_directory_path)
952
for new_dir in [new_pg_dir,
953
os.path.join(new_pg_dir, version),
954
new_pg_version_cluster_dir]:
955
if not os.path.isdir(new_dir):
956
log("mkdir %s".format(new_dir))
958
# copy permissions from current data_directory_path
959
os.chown(new_dir, curr_dir_stat.st_uid, curr_dir_stat.st_gid)
960
os.chmod(new_dir, curr_dir_stat.st_mode)
961
# Carefully build this symlink, e.g.:
962
# /var/lib/postgresql/9.1/main ->
963
# /srv/juju/vol-000012345/postgresql/9.1/main
964
# but keep previous "main/" directory, by renaming it to
968
except subprocess.CalledProcessError:
969
log("postgresql_stop() failed - can't migrate data.", ERROR)
971
if not os.path.exists(os.path.join(
972
new_pg_version_cluster_dir, "PG_VERSION")):
973
log("migrating PG data {}/ -> {}/".format(
974
data_directory_path, new_pg_version_cluster_dir), WARNING)
975
# void copying PID file to perm storage (shouldn't be any...)
976
command = "rsync -a --exclude postmaster.pid {}/ {}/".format(
977
data_directory_path, new_pg_version_cluster_dir)
978
log("run: {}".format(command))
981
os.rename(data_directory_path, "{}-{}".format(
982
data_directory_path, int(time.time())))
983
log("NOTICE: symlinking {} -> {}".format(
984
new_pg_version_cluster_dir, data_directory_path))
985
os.symlink(new_pg_version_cluster_dir, data_directory_path)
987
"after-symlink: to fix/avoid UID changes from "
988
"previous units, doing: "
989
"chown -R postgres:postgres {}".format(new_pg_dir))
990
run("chown -R postgres:postgres {}".format(new_pg_dir))
993
log("failed to symlink {} -> {}".format(
994
data_directory_path, mount_point), CRITICAL)
998
"Invalid volume storage configuration, not applying changes",
828
if not os.path.exists(data_directory_path):
830
"postgresql data dir {} not found, "
831
"not applying changes.".format(data_directory_path),
835
new_pg_dir = os.path.join(mount_point, "postgresql")
836
new_pg_version_cluster_dir = os.path.join(
837
new_pg_dir, version, cluster_name)
840
"invalid mount point = {}, "
841
"not applying changes.".format(mount_point), ERROR)
844
if ((os.path.islink(data_directory_path) and
845
os.readlink(data_directory_path) == new_pg_version_cluster_dir and
846
os.path.isdir(new_pg_version_cluster_dir))):
848
"postgresql data dir '{}' already points "
849
"to {}, skipping storage changes.".format(
850
data_directory_path, new_pg_version_cluster_dir))
852
"existing-symlink: to fix/avoid UID changes from "
853
"previous units, doing: "
854
"chown -R postgres:postgres {}".format(new_pg_dir))
855
run("chown -R postgres:postgres %s" % new_pg_dir)
858
# Create a directory structure below "new" mount_point as
859
# external_volume_mount/postgresql/9.1/main
860
for new_dir in [new_pg_dir,
861
os.path.join(new_pg_dir, version),
862
new_pg_version_cluster_dir]:
863
if not os.path.isdir(new_dir):
864
log("mkdir %s".format(new_dir))
865
host.mkdir(new_dir, owner="postgres", perms=0o700)
866
# Carefully build this symlink, e.g.:
867
# /var/lib/postgresql/9.1/main ->
868
# external_volume_mount/postgresql/9.1/main
869
# but keep previous "main/" directory, by renaming it to
871
if not postgresql_stop() and postgresql_is_running():
872
log("postgresql_stop() failed - can't migrate data.", ERROR)
874
if not os.path.exists(os.path.join(
875
new_pg_version_cluster_dir, "PG_VERSION")):
876
log("migrating PG data {}/ -> {}/".format(
877
data_directory_path, new_pg_version_cluster_dir), WARNING)
878
# void copying PID file to perm storage (shouldn't be any...)
879
command = "rsync -a --exclude postmaster.pid {}/ {}/".format(
880
data_directory_path, new_pg_version_cluster_dir)
881
log("run: {}".format(command))
884
os.rename(data_directory_path, "{}-{}".format(
885
data_directory_path, int(time.time())))
886
log("NOTICE: symlinking {} -> {}".format(
887
new_pg_version_cluster_dir, data_directory_path))
888
os.symlink(new_pg_version_cluster_dir, data_directory_path)
889
run("chown -h postgres:postgres {}".format(data_directory_path))
891
"after-symlink: to fix/avoid UID changes from "
892
"previous units, doing: "
893
"chown -R postgres:postgres {}".format(new_pg_dir))
894
run("chown -R postgres:postgres {}".format(new_pg_dir))
897
log("failed to symlink {} -> {}".format(
898
data_directory_path, mount_point), CRITICAL)
1003
902
def token_sql_safe(value):
1141
1025
def upgrade_charm():
1026
"""Handle saving state during an upgrade-charm hook.
1028
When upgrading from an installation using volume-map, we migrate
1029
that installation to use the storage subordinate charm by remounting
1030
a mountpath that the storage subordinate maintains. We exit(1) only to
1031
raise visibility to manual procedure that we log in juju logs below for the
1032
juju admin to finish the migration by relating postgresql to the storage
1033
and block-storage-broker services. These steps are generalised in the
1142
1036
install(run_pre=False)
1143
1037
snapshot_relations()
1038
version = pg_version()
1039
cluster_name = hookenv.config('cluster_name')
1040
data_directory_path = os.path.join(
1041
postgresql_data_dir, version, cluster_name)
1042
if (os.path.islink(data_directory_path)):
1043
link_target = os.readlink(data_directory_path)
1044
if "/srv/juju" in link_target:
1045
# Then we just upgraded from an installation that was using
1046
# charm config volume_map definitions. We need to stop postgresql
1047
# and remount the device where the storage subordinate expects to
1048
# control the mount in the future if relations/units change
1049
volume_id = link_target.split("/")[3]
1050
unit_name = hookenv.local_unit()
1051
new_mount_root = external_volume_mount
1052
new_pg_version_cluster_dir = os.path.join(
1053
new_mount_root, "postgresql", version, cluster_name)
1054
if not os.exists(new_mount_root):
1055
os.mkdir(new_mount_root)
1057
"WARNING: %s unit has external volume id %s mounted via the\n"
1058
"deprecated volume-map and volume-ephemeral-storage\n"
1059
"configuration parameters.\n"
1060
"These parameters are no longer available in the postgresql\n"
1061
"charm in favor of using the volume_map parameter in the\n"
1062
"storage subordinate charm.\n"
1063
"We are migrating the attached volume to a mount path which\n"
1064
"can be managed by the storage subordinate charm. To\n"
1065
"continue using this volume_id with the storage subordinate\n"
1066
"follow this procedure.\n-----------------------------------\n"
1067
"1. cat > storage.cfg <<EOF\nstorage:\n"
1068
" provider: block-storage-broker\n"
1070
" volume_map: \"{%s: %s}\"\nEOF\n2. juju deploy "
1071
"--config storage.cfg storage\n"
1072
"3. juju deploy block-storage-broker\n4. juju add-relation "
1073
"block-storage-broker storage\n5. juju resolved --retry "
1074
"%s\n6. juju add-relation postgresql storage\n"
1075
"-----------------------------------\n" %
1076
(unit_name, volume_id, new_mount_root, unit_name, volume_id,
1077
unit_name), WARNING)
1079
os.unlink(data_directory_path)
1080
log("Unmounting external storage due to charm upgrade: %s" %
1083
subprocess.check_output(
1084
"umount /srv/juju/%s" % volume_id, shell=True)
1085
# Since e2label truncates labels to 16 characters use only the
1086
# first 16 characters of the volume_id as that's what was
1087
# set by old versions of postgresql charm
1088
subprocess.check_call(
1089
"mount -t ext4 LABEL=%s %s" %
1090
(volume_id[:16], new_mount_root), shell=True)
1091
except subprocess.CalledProcessError, e:
1092
log("upgrade-charm mount migration failed. %s" % str(e), ERROR)
1095
log("NOTICE: symlinking {} -> {}".format(
1096
new_pg_version_cluster_dir, data_directory_path))
1097
os.symlink(new_pg_version_cluster_dir, data_directory_path)
1098
run("chown -h postgres:postgres {}".format(data_directory_path))
1099
postgresql_start() # Will exit(1) if issues
1100
log("Remount and restart success for this external volume.\n"
1101
"This current running installation will break upon\n"
1102
"add/remove postgresql units or relations if you do not\n"
1103
"follow the above procedure to ensure your external\n"
1104
"volumes are preserved by the storage subordinate charm.",
1106
# So juju admins can see the hook fail and note the steps to fix
1107
# per our WARNINGs above