Merge lp:~vds/charms/precise/mongodb/mongodb_persistent_volume_support into lp:charms/mongodb
- Precise Pangolin (12.04)
- mongodb_persistent_volume_support
- Merge into trunk
Proposed by
Vincenzo Di Somma
Status: | Merged |
---|---|
Merged at revision: | 33 |
Proposed branch: | lp:~vds/charms/precise/mongodb/mongodb_persistent_volume_support |
Merge into: | lp:charms/mongodb |
Diff against target: |
554 lines (+485/-3) 4 files modified
README.md (+5/-0) config.yaml (+29/-0) hooks/hooks.py (+231/-3) scripts/volume-common.sh (+220/-0) |
To merge this branch: | bzr merge lp:~vds/charms/precise/mongodb/mongodb_persistent_volume_support |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juan L. Negron (community) | Approve | ||
Review via email: mp+186805@code.launchpad.net |
Commit message
Description of the change
Shamelessly copy the permanent volume support from the postgresql charm.
In order to test it, deploy the charm, attach a permanent volume to the unit VM, change the configuration like in the following line, but using the correct value for volume-dev-regexp and the right volid in volume-map.
juju set mongodb volume-
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'README.md' | |||
2 | --- README.md 2013-02-14 16:21:46 +0000 | |||
3 | +++ README.md 2013-09-20 14:13:30 +0000 | |||
4 | @@ -327,6 +327,11 @@ | |||
5 | 327 | - run sh.status() | 327 | - run sh.status() |
6 | 328 | You should see your the hosts for your shards in the status output. | 328 | You should see your the hosts for your shards in the status output. |
7 | 329 | 329 | ||
8 | 330 | To deploy mongodb using permanent volume on Openstack, the permanent volume should be attached to the mongodb unit just after the deployment, then the configuration should be updated like follows. | ||
9 | 331 | |||
10 | 332 | ### Use a permanent Openstack volume to store mongodb data. | ||
11 | 333 | juju set mongodb volume-dev-regexp="/dev/vdc" volume-map='{"mongodb/0": "vol-id-00000000000000"}' volume-ephemeral-storage=false | ||
12 | 334 | |||
13 | 330 | ## Backups | 335 | ## Backups |
14 | 331 | 336 | ||
15 | 332 | Backups can be enabled via config. Note that destroying the service cannot | 337 | Backups can be enabled via config. Note that destroying the service cannot |
16 | 333 | 338 | ||
17 | === modified file 'config.yaml' | |||
18 | --- config.yaml 2013-02-14 16:21:46 +0000 | |||
19 | +++ config.yaml 2013-09-20 14:13:30 +0000 | |||
20 | @@ -155,3 +155,32 @@ | |||
21 | 155 | default: 7 | 155 | default: 7 |
22 | 156 | type: int | 156 | type: int |
23 | 157 | description: Number of backups to keep. Keeps one week's worth by default. | 157 | description: Number of backups to keep. Keeps one week's worth by default. |
24 | 158 | #------------------------------------------------------------------------ | ||
25 | 159 | # Volume management | ||
26 | 160 | # volume-map, volume-dev_regexp are only used | ||
27 | 161 | # if volume-ephemeral-storage == False | ||
28 | 162 | #------------------------------------------------------------------------ | ||
29 | 163 | volume-ephemeral-storage: | ||
30 | 164 | type: boolean | ||
31 | 165 | default: true | ||
32 | 166 | description: > | ||
33 | 167 | If false, a configure-error state will be raised if | ||
34 | 168 | volume-map[$JUJU_UNIT_NAME] is not set (see "volume-map" | ||
35 | 169 | below) - see "volume-map" below. | ||
36 | 170 | If true, service units won't try to use "volume-map" (and | ||
37 | 171 | related variables) to mount and use external (EBS) volumes, | ||
38 | 172 | thus storage lifetime will equal VM, thus ephemeral. | ||
39 | 173 | YOU'VE BEEN WARNED. | ||
40 | 174 | volume-map: | ||
41 | 175 | type: string | ||
42 | 176 | default: "" | ||
43 | 177 | description: > | ||
44 | 178 | YAML map as e.g. "{ mongodb/0: vol-0000010, mongodb/1: vol-0000016 }". | ||
45 | 179 | Service units will raise a "configure-error" condition if no volume-map | ||
46 | 180 | value is set for it - it's expected a human to set it properly to resolve it. | ||
47 | 181 | volume-dev-regexp: | ||
48 | 182 | type: string | ||
49 | 183 | default: "/dev/vd[b-z]" | ||
50 | 184 | description: > | ||
51 | 185 | Block device for attached volumes as seen by the VM, will be "scanned" | ||
52 | 186 | for an unused device when "volume-map" is valid for the unit. | ||
53 | 158 | 187 | ||
54 | === modified file 'hooks/hooks.py' | |||
55 | --- hooks/hooks.py 2013-02-14 16:30:30 +0000 | |||
56 | +++ hooks/hooks.py 2013-09-20 14:13:30 +0000 | |||
57 | @@ -5,19 +5,22 @@ | |||
58 | 5 | @author: negronjl | 5 | @author: negronjl |
59 | 6 | ''' | 6 | ''' |
60 | 7 | 7 | ||
61 | 8 | import commands | ||
62 | 9 | import json | ||
63 | 8 | import os | 10 | import os |
64 | 9 | import sys | ||
65 | 10 | import json | ||
66 | 11 | import subprocess | ||
67 | 12 | import re | 11 | import re |
68 | 13 | import signal | 12 | import signal |
69 | 14 | import socket | 13 | import socket |
70 | 14 | import subprocess | ||
71 | 15 | import sys | ||
72 | 15 | import time | 16 | import time |
73 | 17 | import yaml | ||
74 | 16 | 18 | ||
75 | 17 | from os import chmod | 19 | from os import chmod |
76 | 18 | from os import remove | 20 | from os import remove |
77 | 19 | from os.path import exists | 21 | from os.path import exists |
78 | 20 | from string import Template | 22 | from string import Template |
79 | 23 | from yaml.constructor import ConstructorError | ||
80 | 21 | 24 | ||
81 | 22 | ############################################################################### | 25 | ############################################################################### |
82 | 23 | # Supporting functions | 26 | # Supporting functions |
83 | @@ -898,6 +901,34 @@ | |||
84 | 898 | print "config_data: ", config_data | 901 | print "config_data: ", config_data |
85 | 899 | mongodb_config = open(default_mongodb_config).read() | 902 | mongodb_config = open(default_mongodb_config).read() |
86 | 900 | 903 | ||
87 | 904 | # Trigger volume initialization logic for permanent storage | ||
88 | 905 | volid = volume_get_volume_id() | ||
89 | 906 | if not volid: | ||
90 | 907 | ## Invalid configuration (whether ephemeral, or permanent) | ||
91 | 908 | stop_hook() | ||
92 | 909 | mounts = volume_get_all_mounted() | ||
93 | 910 | if mounts: | ||
94 | 911 | juju_log("current mounted volumes: {}".format(mounts)) | ||
95 | 912 | juju_log( | ||
96 | 913 | "Disabled and stopped mongodb service, " | ||
97 | 914 | "because of broken volume configuration - check " | ||
98 | 915 | "'volume-ephemeral-storage' and 'volume-map'") | ||
99 | 916 | sys.exit(1) | ||
100 | 917 | if volume_is_permanent(volid): | ||
101 | 918 | ## config_changed_volume_apply will stop the service if it founds | ||
102 | 919 | ## it necessary, ie: new volume setup | ||
103 | 920 | if config_changed_volume_apply(): | ||
104 | 921 | start_hook() | ||
105 | 922 | else: | ||
106 | 923 | stop_hook() | ||
107 | 924 | mounts = volume_get_all_mounted() | ||
108 | 925 | if mounts: | ||
109 | 926 | juju_log("current mounted volumes: {}".format(mounts)) | ||
110 | 927 | juju_log( | ||
111 | 928 | "Disabled and stopped mongodb service " | ||
112 | 929 | "(config_changed_volume_apply failure)") | ||
113 | 930 | sys.exit(1) | ||
114 | 931 | |||
115 | 901 | # current ports | 932 | # current ports |
116 | 902 | current_mongodb_port = re.search('^#*port\s+=\s+(\w+)', | 933 | current_mongodb_port = re.search('^#*port\s+=\s+(\w+)', |
117 | 903 | mongodb_config, | 934 | mongodb_config, |
118 | @@ -1007,6 +1038,7 @@ | |||
119 | 1007 | try: | 1038 | try: |
120 | 1008 | retVal = service('mongodb', 'stop') | 1039 | retVal = service('mongodb', 'stop') |
121 | 1009 | os.remove('/var/lib/mongodb/mongod.lock') | 1040 | os.remove('/var/lib/mongodb/mongod.lock') |
122 | 1041 | #FIXME Need to check if this is still needed | ||
123 | 1010 | except Exception, e: | 1042 | except Exception, e: |
124 | 1011 | juju_log(str(e)) | 1043 | juju_log(str(e)) |
125 | 1012 | retVal = False | 1044 | retVal = False |
126 | @@ -1194,6 +1226,202 @@ | |||
127 | 1194 | # return(update_file(default_mongos_list, '\n'.join(config_servers))) | 1226 | # return(update_file(default_mongos_list, '\n'.join(config_servers))) |
128 | 1195 | return(True) | 1227 | return(True) |
129 | 1196 | 1228 | ||
130 | 1229 | |||
131 | 1230 | def run(command, exit_on_error=True): | ||
132 | 1231 | '''Run a command and return the output.''' | ||
133 | 1232 | try: | ||
134 | 1233 | juju_log(command) | ||
135 | 1234 | return subprocess.check_output( | ||
136 | 1235 | command, stderr=subprocess.STDOUT, shell=True) | ||
137 | 1236 | except subprocess.CalledProcessError, e: | ||
138 | 1237 | juju_log("status=%d, output=%s" % (e.returncode, e.output)) | ||
139 | 1238 | if exit_on_error: | ||
140 | 1239 | sys.exit(e.returncode) | ||
141 | 1240 | else: | ||
142 | 1241 | raise | ||
143 | 1242 | |||
144 | 1243 | |||
145 | 1244 | ############################################################################### | ||
146 | 1245 | # Volume managment | ||
147 | 1246 | ############################################################################### | ||
148 | 1247 | #------------------------------ | ||
149 | 1248 | # Get volume-id from juju config "volume-map" dictionary as | ||
150 | 1249 | # volume-map[JUJU_UNIT_NAME] | ||
151 | 1250 | # @return volid | ||
152 | 1251 | # | ||
153 | 1252 | #------------------------------ | ||
154 | 1253 | def volume_get_volid_from_volume_map(): | ||
155 | 1254 | config_data = config_get() | ||
156 | 1255 | volume_map = {} | ||
157 | 1256 | try: | ||
158 | 1257 | volume_map = yaml.load(config_data['volume-map'].strip()) | ||
159 | 1258 | if volume_map: | ||
160 | 1259 | juju_unit_name = os.environ['JUJU_UNIT_NAME'] | ||
161 | 1260 | volid = volume_map.get(juju_unit_name) | ||
162 | 1261 | juju_log("Juju unit name: %s Volid:%s" % (juju_unit_name, volid)) | ||
163 | 1262 | return volid | ||
164 | 1263 | except ConstructorError as e: | ||
165 | 1264 | juju_log("invalid YAML in 'volume-map': {}".format(e)) | ||
166 | 1265 | return None | ||
167 | 1266 | |||
168 | 1267 | |||
169 | 1268 | # Is this volume_id permanent ? | ||
170 | 1269 | # @returns True if volid set and not --ephemeral, else: | ||
171 | 1270 | # False | ||
172 | 1271 | def volume_is_permanent(volid): | ||
173 | 1272 | if volid and volid != "--ephemeral": | ||
174 | 1273 | return True | ||
175 | 1274 | return False | ||
176 | 1275 | |||
177 | 1276 | |||
178 | 1277 | #------------------------------ | ||
179 | 1278 | # Returns a mount point from passed vol-id, e.g. /srv/juju/vol-000012345 | ||
180 | 1279 | # | ||
181 | 1280 | # @param volid volume id (as e.g. EBS volid) | ||
182 | 1281 | # @return mntpoint_path eg /srv/juju/vol-000012345 | ||
183 | 1282 | #------------------------------ | ||
184 | 1283 | def volume_mount_point_from_volid(volid): | ||
185 | 1284 | if volid and volume_is_permanent(volid): | ||
186 | 1285 | return "/srv/juju/%s" % volid | ||
187 | 1286 | return None | ||
188 | 1287 | |||
189 | 1288 | |||
190 | 1289 | # Do we have a valid storage state? | ||
191 | 1290 | # @returns volid | ||
192 | 1291 | # None config state is invalid - we should not serve | ||
193 | 1292 | def volume_get_volume_id(): | ||
194 | 1293 | config_data = config_get() | ||
195 | 1294 | ephemeral_storage = config_data['volume-ephemeral-storage'] | ||
196 | 1295 | volid = volume_get_volid_from_volume_map() | ||
197 | 1296 | juju_unit_name = os.environ['JUJU_UNIT_NAME'] | ||
198 | 1297 | if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']: | ||
199 | 1298 | if volid: | ||
200 | 1299 | juju_log( | ||
201 | 1300 | "volume-ephemeral-storage is True, but " + | ||
202 | 1301 | "volume-map[{!r}] -> {}".format(juju_unit_name, volid)) | ||
203 | 1302 | return None | ||
204 | 1303 | else: | ||
205 | 1304 | return "--ephemeral" | ||
206 | 1305 | else: | ||
207 | 1306 | if not volid: | ||
208 | 1307 | juju_log( | ||
209 | 1308 | "volume-ephemeral-storage is False, but " | ||
210 | 1309 | "no volid found for volume-map[{!r}]".format( | ||
211 | 1310 | juju_unit_name)) | ||
212 | 1311 | return None | ||
213 | 1312 | juju_log("Volid:%s" % (volid)) | ||
214 | 1313 | return volid | ||
215 | 1314 | |||
216 | 1315 | |||
217 | 1316 | # Initialize and/or mount permanent storage, it straightly calls | ||
218 | 1317 | # shell helper | ||
219 | 1318 | def volume_init_and_mount(volid): | ||
220 | 1319 | juju_log("Initialize and mount volume") | ||
221 | 1320 | command = ("scripts/volume-common.sh call " + | ||
222 | 1321 | "volume_init_and_mount %s" % volid) | ||
223 | 1322 | run(command) | ||
224 | 1323 | return True | ||
225 | 1324 | |||
226 | 1325 | |||
227 | 1326 | def volume_get_all_mounted(): | ||
228 | 1327 | command = ("mount |egrep /srv/juju") | ||
229 | 1328 | status, output = commands.getstatusoutput(command) | ||
230 | 1329 | if status != 0: | ||
231 | 1330 | return None | ||
232 | 1331 | return output | ||
233 | 1332 | |||
234 | 1333 | #------------------------------------------------------------------------------ | ||
235 | 1334 | # Core logic for permanent storage changes: | ||
236 | 1335 | # NOTE the only 2 "True" return points: | ||
237 | 1336 | # 1) symlink already pointing to existing storage (no-op) | ||
238 | 1337 | # 2) new storage properly initialized: | ||
239 | 1338 | # - volume: initialized if not already (fdisk, mkfs), | ||
240 | 1339 | # mounts it to e.g.: /srv/juju/vol-000012345 | ||
241 | 1340 | # - if fresh new storage dir: rsync existing data | ||
242 | 1341 | # - manipulate /var/lib/mongodb/VERSION/CLUSTER symlink | ||
243 | 1342 | #------------------------------------------------------------------------------ | ||
244 | 1343 | def config_changed_volume_apply(): | ||
245 | 1344 | config_data = config_get() | ||
246 | 1345 | data_directory_path = config_data["dbpath"] | ||
247 | 1346 | assert(data_directory_path) | ||
248 | 1347 | volid = volume_get_volume_id() | ||
249 | 1348 | if volid: | ||
250 | 1349 | if volume_is_permanent(volid): | ||
251 | 1350 | if not volume_init_and_mount(volid): | ||
252 | 1351 | juju_log( | ||
253 | 1352 | "volume_init_and_mount failed, not applying changes") | ||
254 | 1353 | return False | ||
255 | 1354 | |||
256 | 1355 | if not os.path.exists(data_directory_path): | ||
257 | 1356 | juju_log( | ||
258 | 1357 | "mongodb data dir {} not found, " | ||
259 | 1358 | "not applying changes.".format(data_directory_path)) | ||
260 | 1359 | return False | ||
261 | 1360 | |||
262 | 1361 | mount_point = volume_mount_point_from_volid(volid) | ||
263 | 1362 | new_mongo_dir = os.path.join(mount_point, "mongodb") | ||
264 | 1363 | if not mount_point: | ||
265 | 1364 | juju_log( | ||
266 | 1365 | "invalid mount point from volid = {}, " | ||
267 | 1366 | "not applying changes.".format(mount_point)) | ||
268 | 1367 | return False | ||
269 | 1368 | |||
270 | 1369 | if os.path.islink(data_directory_path): | ||
271 | 1370 | juju_log( | ||
272 | 1371 | "mongodb data dir '%s' already points " | ||
273 | 1372 | "to %s, skipping storage changes." % (data_directory_path, new_mongo_dir)) | ||
274 | 1373 | juju_log( | ||
275 | 1374 | "existing-symlink: to fix/avoid UID changes from " | ||
276 | 1375 | "previous units, doing: " | ||
277 | 1376 | "chown -R mongodb:mongodb {}".format(new_mongo_dir)) | ||
278 | 1377 | run("chown -R mongodb:mongodb %s" % new_mongo_dir) | ||
279 | 1378 | return True | ||
280 | 1379 | |||
281 | 1380 | # Create a directory structure below "new" mount_point | ||
282 | 1381 | curr_dir_stat = os.stat(data_directory_path) | ||
283 | 1382 | if not os.path.isdir(new_mongo_dir): | ||
284 | 1383 | juju_log("mkdir %s" % new_mongo_dir) | ||
285 | 1384 | os.mkdir(new_mongo_dir) | ||
286 | 1385 | # copy permissions from current data_directory_path | ||
287 | 1386 | os.chown(new_mongo_dir, curr_dir_stat.st_uid, curr_dir_stat.st_gid) | ||
288 | 1387 | os.chmod(new_mongo_dir, curr_dir_stat.st_mode) | ||
289 | 1388 | # Carefully build this symlink, e.g.: | ||
290 | 1389 | # /var/lib/mongodb -> | ||
291 | 1390 | # /srv/juju/vol-000012345/mongodb | ||
292 | 1391 | # but keep previous "main/" directory, by renaming it to | ||
293 | 1392 | # main-$TIMESTAMP | ||
294 | 1393 | if not stop_hook(): | ||
295 | 1394 | juju_log("stop_hook() failed - can't migrate data.") | ||
296 | 1395 | return False | ||
297 | 1396 | if not os.path.exists(new_mongo_dir): | ||
298 | 1397 | juju_log("migrating mongo data {}/ -> {}/".format( | ||
299 | 1398 | data_directory_path, new_mongo_dir)) | ||
300 | 1399 | # void copying PID file to perm storage (shouldn't be any...) | ||
301 | 1400 | command = "rsync -a {}/ {}/".format( | ||
302 | 1401 | data_directory_path, new_mongo_dir) | ||
303 | 1402 | juju_log("run: {}".format(command)) | ||
304 | 1403 | run(command) | ||
305 | 1404 | try: | ||
306 | 1405 | os.rename(data_directory_path, "{}-{}".format( | ||
307 | 1406 | data_directory_path, int(time.time()))) | ||
308 | 1407 | juju_log("NOTICE: symlinking {} -> {}".format( | ||
309 | 1408 | new_mongo_dir, data_directory_path)) | ||
310 | 1409 | os.symlink(new_mongo_dir, data_directory_path) | ||
311 | 1410 | juju_log( | ||
312 | 1411 | "after-symlink: to fix/avoid UID changes from " | ||
313 | 1412 | "previous units, doing: " | ||
314 | 1413 | "chown -R mongodb:mongodb {}".format(new_mongo_dir)) | ||
315 | 1414 | run("chown -R mongodb:mongodb {}".format(new_mongo_dir)) | ||
316 | 1415 | return True | ||
317 | 1416 | except OSError: | ||
318 | 1417 | juju_log("failed to symlink {} -> {}".format( | ||
319 | 1418 | data_directory_path, mount_point)) | ||
320 | 1419 | return False | ||
321 | 1420 | else: | ||
322 | 1421 | juju_log( | ||
323 | 1422 | "Invalid volume storage configuration, not applying changes") | ||
324 | 1423 | return False | ||
325 | 1424 | |||
326 | 1197 | ############################################################################### | 1425 | ############################################################################### |
327 | 1198 | # Main section | 1426 | # Main section |
328 | 1199 | ############################################################################### | 1427 | ############################################################################### |
329 | 1200 | 1428 | ||
330 | === added directory 'scripts' | |||
331 | === added file 'scripts/volume-common.sh' | |||
332 | --- scripts/volume-common.sh 1970-01-01 00:00:00 +0000 | |||
333 | +++ scripts/volume-common.sh 2013-09-20 14:13:30 +0000 | |||
334 | @@ -0,0 +1,220 @@ | |||
335 | 1 | #!/bin/bash | ||
336 | 2 | # Author: JuanJo Ciarlante <jjo@canonical.com> | ||
337 | 3 | # Copyright: Canonical Ltd. 2012 | ||
338 | 4 | # License: GPLv2 | ||
339 | 5 | # | ||
340 | 6 | # juju storage common shell library | ||
341 | 7 | # | ||
342 | 8 | |||
343 | 9 | #------------------------------ | ||
344 | 10 | # Returns a mount point from passed vol-id, e.g. /srv/juju/vol-000012345 | ||
345 | 11 | # | ||
346 | 12 | # @param $1 volume id | ||
347 | 13 | # @echoes mntpoint-path eg /srv/juju/vol-000012345 | ||
348 | 14 | #------------------------------ | ||
349 | 15 | _mntpoint_from_volid() { | ||
350 | 16 | local volid=${1?missing volid} | ||
351 | 17 | [[ ${volid} != "" ]] && echo /srv/juju/${volid} || echo "" | ||
352 | 18 | } | ||
353 | 19 | |||
354 | 20 | |||
355 | 21 | #------------------------------ | ||
356 | 22 | # Assert that passed mount points hold different filesystems | ||
357 | 23 | # | ||
358 | 24 | # @param $1 mntpoint1 | ||
359 | 25 | # @param $2 mntpoint2 | ||
360 | 26 | # @return 0 different FS | ||
361 | 27 | # 1 same FS | ||
362 | 28 | #------------------------------ | ||
363 | 29 | _assert_diff_fs() { | ||
364 | 30 | local mnt1="${1:?missing mntpoint1}" | ||
365 | 31 | local mnt2="${2:?missing mntpoint2}" | ||
366 | 32 | local fsid1 fsid2 | ||
367 | 33 | fsid1=$(stat --file-system -c '%i' "${mnt1}" 2>/dev/null) | ||
368 | 34 | fsid2=$(stat --file-system -c '%i' "${mnt2}" 2>/dev/null) | ||
369 | 35 | [[ ${fsid1} != ${fsid2} ]] | ||
370 | 36 | return $? | ||
371 | 37 | } | ||
372 | 38 | |||
373 | 39 | #------------------------------ | ||
374 | 40 | # Initialize volume (sfdisk, mkfs.ext4) IFF NOT already, mount it at | ||
375 | 41 | # /srv/juju/<volume-id> | ||
376 | 42 | # | ||
377 | 43 | # @param $1 volume-id, can be any arbitrary string, better if | ||
378 | 44 | # equal to EC2/OS vol-id name (just for consistency) | ||
379 | 45 | # @return 0 success | ||
380 | 46 | # 1 nil volid/etc | ||
381 | 47 | # 2 error while handling the device (non-block device, sfdisk error, etc) | ||
382 | 48 | #------------------------------ | ||
383 | 49 | volume_init_and_mount() { | ||
384 | 50 | ## Find 1st unused device (reverse sort /dev/vdX) | ||
385 | 51 | local volid=${1:?missing volid} | ||
386 | 52 | local dev_regexp | ||
387 | 53 | local dev found_dev= | ||
388 | 54 | local label="${volid}" | ||
389 | 55 | local func=${FUNCNAME[0]} | ||
390 | 56 | dev_regexp=$(config-get volume-dev-regexp) || return 1 | ||
391 | 57 | mntpoint=$(_mntpoint_from_volid ${volid}) | ||
392 | 58 | |||
393 | 59 | [[ -z ${mntpoint} ]] && return 1 | ||
394 | 60 | if mount | egrep -qw "${mntpoint}";then | ||
395 | 61 | _assert_diff_fs "/" "${mntpoint}" || { | ||
396 | 62 | juju-log "ERROR: returning from ${func} with '${mntpoint}' still at '/' filesystem" | ||
397 | 63 | return 1 | ||
398 | 64 | } | ||
399 | 65 | juju-log "NOTICE: mntpoint=${mntpoint} already mounted, skipping volume_init_and_mount" | ||
400 | 66 | return 0 | ||
401 | 67 | fi | ||
402 | 68 | |||
403 | 69 | # Sanitize | ||
404 | 70 | case "${dev_regexp?}" in | ||
405 | 71 | # Careful: this is glob matching against an regexp - | ||
406 | 72 | # quite narrowed | ||
407 | 73 | /dev/*|/dev/disk/by-*) | ||
408 | 74 | ;; ## Ok | ||
409 | 75 | *) | ||
410 | 76 | juju-log "ERROR: invalid 'volume-dev-regexp' specified" | ||
411 | 77 | return 1 | ||
412 | 78 | ;; | ||
413 | 79 | esac | ||
414 | 80 | |||
415 | 81 | # Assume udev will create only existing devices | ||
416 | 82 | for dev in $(ls -rd1 /dev/* | egrep "${dev_regexp}" | egrep -v "[1-9]$" 2>/dev/null);do | ||
417 | 83 | ## Check it's not already mounted | ||
418 | 84 | mount | egrep -q "${dev}[1-9]?" || { found_dev=${dev}; break;} | ||
419 | 85 | done | ||
420 | 86 | [[ -n "${found_dev}" ]] || { | ||
421 | 87 | juju-log "ERROR: ${func}: coult not find an unused device for regexp: ${dev_regexp}" | ||
422 | 88 | return 1 | ||
423 | 89 | } | ||
424 | 90 | partition1_dev=${found_dev}1 | ||
425 | 91 | |||
426 | 92 | juju-log "INFO: ${func}: found_dev=${found_dev}" | ||
427 | 93 | [[ -b ${found_dev?} ]] || { | ||
428 | 94 | juju-log "ERROR: ${func}: ${found_dev} is not a blockdevice" | ||
429 | 95 | return 2 | ||
430 | 96 | } | ||
431 | 97 | |||
432 | 98 | # Run next set of "dangerous" commands as 'set -e', in a subshell | ||
433 | 99 | ( | ||
434 | 100 | set -e | ||
435 | 101 | # Re-read partition - will fail if already in use | ||
436 | 102 | blockdev --rereadpt ${found_dev} | ||
437 | 103 | |||
438 | 104 | # IFF not present, create partition with full disk | ||
439 | 105 | if [[ -b ${partition1_dev?} ]];then | ||
440 | 106 | juju-log "INFO: ${func}: ${partition1_dev} already present - skipping sfdisk." | ||
441 | 107 | else | ||
442 | 108 | juju-log "NOTICE: ${func}: ${partition1_dev} not present at ${found_dev}, running: sfdisk ${found_dev} ..." | ||
443 | 109 | # Format partition1_dev as max sized | ||
444 | 110 | echo ",+," | sfdisk ${found_dev} | ||
445 | 111 | fi | ||
446 | 112 | |||
447 | 113 | # Create an ext4 filesystem if NOT already present | ||
448 | 114 | # use e.g. LABEl=vol-000012345 | ||
449 | 115 | if file -s ${partition1_dev} | egrep -q ext4 ; then | ||
450 | 116 | juju-log "INFO: ${func}: ${partition1_dev} already formatted as ext4 - skipping mkfs.ext4." | ||
451 | 117 | ## Check e2label - log if it has changed (e.g. already used / initialized with a diff label) | ||
452 | 118 | local curr_label=$(e2label "${partition1_dev}") | ||
453 | 119 | if [[ ${curr_label} != ${label} ]]; then | ||
454 | 120 | juju-log "WARNING: ${func}: ${partition1_dev} had label=${curr_label}, overwritting with label=${label}" | ||
455 | 121 | e2label ${partition1_dev} "${label}" | ||
456 | 122 | fi | ||
457 | 123 | else | ||
458 | 124 | juju-log "NOTICE: ${func}: running: mkfs.ext4 -L ${label} ${partition1_dev}" | ||
459 | 125 | mkfs.ext4 -L "${label}" ${partition1_dev} | ||
460 | 126 | fi | ||
461 | 127 | |||
462 | 128 | # Mount it at e.g. /srv/juju/vol-000012345 | ||
463 | 129 | [[ -d "${mntpoint}" ]] || mkdir -p "${mntpoint}" | ||
464 | 130 | mount | fgrep -wq "${partition1_dev}" || { | ||
465 | 131 | local files_below_mntpoint="$(ls -d "${mntpoint}"/* 2>/dev/null |wc -l )" | ||
466 | 132 | if [[ ${files_below_mntpoint} -ne 0 ]]; then | ||
467 | 133 | juju-log "ERROR: *not* doing 'mount "${partition1_dev}" "${mntpoint}"' because there are already ${files_below_mntpoint} files/dirs beneath '${mntpoint}'" | ||
468 | 134 | exit 1 | ||
469 | 135 | fi | ||
470 | 136 | ## should always fsck before mounting (e.g. fsck after max time (-i) / max mounts (-c) ) | ||
471 | 137 | fsck "${partition1_dev}" | ||
472 | 138 | mount "${partition1_dev}" "${mntpoint}" | ||
473 | 139 | juju-log "INFO: ${func}: mounted as: '$(mount | fgrep -w ${partition1_dev})'" | ||
474 | 140 | } | ||
475 | 141 | |||
476 | 142 | # Add it to fstab is not already there | ||
477 | 143 | fgrep -wq "LABEL=${label}" /etc/fstab || { | ||
478 | 144 | echo "LABEL=${label} ${mntpoint} ext4 defaults,nobootwait,comment=${volid}" | tee -a /etc/fstab | ||
479 | 145 | juju-log "INFO: ${func}: LABEL=${label} added to /etc/fstab" | ||
480 | 146 | } | ||
481 | 147 | ) | ||
482 | 148 | # Final assertion: mounted filesystem id is different from '/' (effectively mounted) | ||
483 | 149 | _assert_diff_fs "/" "${mntpoint}" || { | ||
484 | 150 | juju-log "ERROR: returning from ${func} with '${mntpoint}' still at '/' filesystem (couldn't mount new volume)" | ||
485 | 151 | ## try to rmdir mntpoint directory - should not be 'mistakenly' used | ||
486 | 152 | rmdir ${mntpoint} | ||
487 | 153 | return 1 | ||
488 | 154 | } | ||
489 | 155 | return $? | ||
490 | 156 | } | ||
491 | 157 | |||
492 | 158 | #------------------------------ | ||
493 | 159 | # Get volume-id from juju config "volume-map" dictionary as | ||
494 | 160 | # volume-map[JUJU_UNIT_NAME] | ||
495 | 161 | # @return 0 if volume-map value found ( does echo volid or ""), else: | ||
496 | 162 | # 1 if not found or None | ||
497 | 163 | # | ||
498 | 164 | #------------------------------ | ||
499 | 165 | volume_get_volid_from_volume_map() { | ||
500 | 166 | local volid=$(config-get "volume-map"|python -c$'import sys;import os;from yaml import load;from itertools import chain; volume_map = load(sys.stdin)\nif volume_map: print volume_map.get(os.environ["JUJU_UNIT_NAME"])') | ||
501 | 167 | [[ $volid == None ]] && return 1 | ||
502 | 168 | echo "$volid" | ||
503 | 169 | } | ||
504 | 170 | |||
505 | 171 | # Returns true if permanent storage (considers --ephemeral) | ||
506 | 172 | # @returns 0 if volid set and not --ephemeral, else: | ||
507 | 173 | # 1 | ||
508 | 174 | volume_is_permanent() { | ||
509 | 175 | local volid=${1:?missing volid} | ||
510 | 176 | [[ -n ${volid} && ${volid} != --ephemeral ]] && return 0 || return 1 | ||
511 | 177 | } | ||
512 | 178 | volume_mount_point_from_volid(){ | ||
513 | 179 | local volid=${1:?missing volid} | ||
514 | 180 | if volume_is_permanent;then | ||
515 | 181 | echo "/srv/juju/${volid}" | ||
516 | 182 | return 0 | ||
517 | 183 | else | ||
518 | 184 | return 1 | ||
519 | 185 | fi | ||
520 | 186 | } | ||
521 | 187 | # Do we have a valid storage state? | ||
522 | 188 | # @returns 0 does echo $volid (can be "--ephemeral") | ||
523 | 189 | # 1 config state is invalid - we should not serve | ||
524 | 190 | volume_get_volume_id() { | ||
525 | 191 | local ephemeral_storage | ||
526 | 192 | local volid | ||
527 | 193 | ephemeral_storage=$(config-get volume-ephemeral-storage) || return 1 | ||
528 | 194 | volid=$(volume_get_volid_from_volume_map) || return 1 | ||
529 | 195 | if [[ $ephemeral_storage == True ]];then | ||
530 | 196 | # Ephemeral -> should not have a valid volid | ||
531 | 197 | if [[ $volid != "" ]];then | ||
532 | 198 | juju-log "ERROR: volume-ephemeral-storage is True, but $JUJU_UNIT_NAME maps to volid=${volid}" | ||
533 | 199 | return 1 | ||
534 | 200 | fi | ||
535 | 201 | else | ||
536 | 202 | # Durable (not ephemeral) -> must have a valid volid for this unit | ||
537 | 203 | if [[ $volid == "" ]];then | ||
538 | 204 | juju-log "ERROR: volume-ephemeral-storage is False, but no volid found for: $JUJU_UNIT_NAME" | ||
539 | 205 | return 1 | ||
540 | 206 | fi | ||
541 | 207 | fi | ||
542 | 208 | echo "$volid" | ||
543 | 209 | return 0 | ||
544 | 210 | } | ||
545 | 211 | |||
546 | 212 | case "$1" in | ||
547 | 213 | ## allow non SHELL scripts to call helper functions | ||
548 | 214 | call) | ||
549 | 215 | : ${JUJU_UNIT_NAME?} ## Must be called in juju environment | ||
550 | 216 | shift; | ||
551 | 217 | function="${1:?usage: ${0##*/} call function arg1 arg2 ...}" | ||
552 | 218 | shift; | ||
553 | 219 | ${function} "$@" && exit 0 || exit 1 | ||
554 | 220 | esac |
Approved. Nice work