1
"""Common python utilities for storage providers"""
2
from charmhelpers.core import hookenv
7
from stat import S_ISBLK, ST_MODE
9
CHARM_DIR = os.environ.get("CHARM_DIR")
10
FSTAB_FILE = "/etc/fstab"
13
def log(message, level=None):
14
"""Quaint little log wrapper for juju logging"""
15
hookenv.log(message, level)
19
"""Get the storage charm's provider type as set within it's config.yaml"""
20
provider = hookenv.config("provider")
25
"Error: no provider defined in storage charm config",
31
"""Return True if the defined mountpoint is mounted"""
32
mountpoint = hookenv.relation_get("mountpoint")
34
log("INFO: No mountpoint defined by relation assuming not mounted.")
36
return os.path.ismount(mountpoint)
40
"""Unmount the relation-requested C{mountpoint}"""
42
mountpoint = hookenv.relation_get("mountpoint")
44
log("INFO: No mountpoint defined by relation no unmount performed.")
48
log("%s is not mounted, Done." % mountpoint)
51
for x in range(0, 20):
52
if subprocess.call(["umount", mountpoint]) == 0:
54
log("Unmounted %s, Done" % mountpoint)
56
else: # Then device is in use let's report the offending process
57
process_name = subprocess.check_output(
58
"lsof %s | awk '(NR == 2){print $1}'" % mountpoint)
59
log("WARNING: umount %s failed. Device in use by (%s)" %
60
(mountpoint, process_name.strip()))
65
"ERROR: Unmount failed, leaving relation in errored state",
70
def _assert_block_device(device_path):
71
"""C{device_path} is a valid block device or exit in error"""
73
for x in range(0, 10):
74
if not os.path.exists(device_path):
75
log("WARNING: %s does not exist" % device_path)
78
mode = os.stat(device_path)[ST_MODE]
81
"DEBUG: block device found. Proceeding.",
89
log("ERROR: %s is not a block device." % device_path)
93
def _set_label(device_path, label):
94
"""Set label if necessary on C{device_path}"""
95
if not os.path.exists(device_path):
96
log("ERROR: Cannot set label of %s. Path does not exist" % device_path)
99
current_label = subprocess.check_output(
100
"e2label %s" % device_path, shell=True)
102
current_label = current_label.strip()
103
if current_label == label:
104
# Label already set, return
106
log("WARNING: %s had label=%s, overwriting with label=%s" %
107
(device_path, current_label, label))
108
subprocess.check_call(
109
"e2label %s %s" % (device_path, label), shell=True)
112
def _read_partition_table(device_path):
113
"""Call blockdev to read a valid partition table and return exit code"""
114
return subprocess.call(
115
"blockdev --rereadpt %s" % device_path, shell=True)
118
def _assert_fstype(device_path, fstype):
119
"""Return C{True} if a filesystem is of type C{fstype}"""
120
command = "file -s %s | egrep -q %s" % (device_path, fstype)
121
return subprocess.call(command, shell=True) == 0
124
def _format_device(device_path):
126
Create an ext4 partition, if needed, for C{device_path} and fsck that
129
result = _read_partition_table(device_path)
131
log("INFO: %s is busy, no fsck performed. Assuming formatted." %
134
# Create an ext4 filesystem if NOT already present
135
# use e.g. LABEl=vol-000012345
136
if _assert_fstype(device_path, "ext4"):
137
log("%s already formatted - skipping mkfs.ext4." % device_path)
139
command = "mkfs.ext4 %s" % device_path
140
log("NOTICE: Running: %s" % command)
141
subprocess.check_call(command, shell=True)
142
if subprocess.call("fsck -p %s" % device_path, shell=True) != 0:
143
log("ERROR: fsck -p %s failed" % device_path)
147
def mount_volume(device_path=None):
149
Mount the attached volume, nfs or blockstoragebroker type, to the principal
150
requested C{mountpoint}.
152
If the C{mountpoint} required relation data does not exist, log the
153
issue and exit. Otherwise attempt to initialize and mount the blockstorage
156
mountpoint = hookenv.relation_get("mountpoint")
158
log("No mountpoint defined by relation. Cannot mount volume yet.")
162
log("Volume (%s) already mounted" % mountpoint)
163
# publish changes to data relation and exit
164
for relid in hookenv.relation_ids("data"):
165
hookenv.relation_set(
166
relid, relation_settings={"mountpoint": mountpoint})
169
provider = get_provider()
171
if provider == "nfs":
172
relids = hookenv.relation_ids("nfs")
174
for unit in hookenv.related_units(relid):
175
relation = hookenv.relation_get(unit=unit, rid=relid)
176
# XXX Only handle 1 related nfs unit. No nfs cluster support
177
nfs_server = relation.get("private-address", None)
178
nfs_path = relation.get("mountpoint", None)
179
fstype = relation.get("fstype", None)
180
options = relation.get("options", "default")
183
if None in [nfs_server, nfs_path, fstype]:
184
log("ERROR: Missing required relation values. "
185
"nfs-relation-changed hook needs to run first.",
189
device_path = "%s:%s" % (nfs_server, nfs_path)
190
elif provider == "blockstoragebroker":
191
if device_path is None:
192
log("ERROR: No persistent nova device provided by "
193
"blockstoragebroker.", hookenv.ERROR)
196
_assert_block_device(device_path)
197
_format_device(device_path)
198
_set_label(device_path, mountpoint)
199
fstype = subprocess.check_output(
200
"blkid -o value -s TYPE %s" % device_path, shell=True).strip()
202
log("ERROR: Unknown provider %s. Cannot mount volume." % hookenv.ERROR)
205
if not os.path.exists(mountpoint):
206
os.makedirs(mountpoint)
208
options_string = "" if options == "default" else " -o %s" % options
209
command = "mount -t %s%s %s %s" % (
210
fstype, options_string, device_path, mountpoint)
211
subprocess.check_call(command, shell=True)
212
log("Mount (%s) successful" % mountpoint)
213
with open(FSTAB_FILE, "a") as output_file:
215
"%s %s %s %s 0 0" % (device_path, mountpoint, fstype, options))
216
# publish changes to data relation and exit
217
for relid in hookenv.relation_ids("data"):
218
hookenv.relation_set(
219
relid, relation_settings={"mountpoint": mountpoint})