~tribaal/charms/precise/storage/refactor-mount-volume

« back to all changes in this revision

Viewing changes to hooks/common_util.py

  • Committer: David Britton
  • Date: 2014-02-05 20:46:42 UTC
  • mfrom: (26.1.89 storage)
  • Revision ID: dpb@canonical.com-20140205204642-qfwv0x6314bulcx7
merging chad's python/nfs additions

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Common python utilities for storage providers"""
 
2
from charmhelpers.core import hookenv
 
3
import os
 
4
import subprocess
 
5
import sys
 
6
from time import sleep
 
7
from stat import S_ISBLK, ST_MODE
 
8
 
 
9
CHARM_DIR = os.environ.get("CHARM_DIR")
 
10
FSTAB_FILE = "/etc/fstab"
 
11
 
 
12
 
 
13
def log(message, level=None):
 
14
    """Quaint little log wrapper for juju logging"""
 
15
    hookenv.log(message, level)
 
16
 
 
17
 
 
18
def get_provider():
 
19
    """Get the storage charm's provider type as set within it's config.yaml"""
 
20
    provider = hookenv.config("provider")
 
21
    if provider:
 
22
        return provider
 
23
    else:
 
24
        log(
 
25
            "Error: no provider defined in storage charm config",
 
26
            hookenv.ERROR)
 
27
        sys.exit(1)
 
28
 
 
29
 
 
30
def is_mounted():
 
31
    """Return True if the defined mountpoint is mounted"""
 
32
    mountpoint = hookenv.relation_get("mountpoint")
 
33
    if not mountpoint:
 
34
        log("INFO: No mountpoint defined by relation assuming not mounted.")
 
35
        return False
 
36
    return os.path.ismount(mountpoint)
 
37
 
 
38
 
 
39
def unmount_volume():
 
40
    """Unmount the relation-requested C{mountpoint}"""
 
41
    success = False
 
42
    mountpoint = hookenv.relation_get("mountpoint")
 
43
    if not mountpoint:
 
44
        log("INFO: No mountpoint defined by relation no unmount performed.")
 
45
        return
 
46
 
 
47
    if not is_mounted():
 
48
        log("%s is not mounted, Done." % mountpoint)
 
49
        success = True
 
50
    else:
 
51
        for x in range(0, 20):
 
52
            if subprocess.call(["umount", mountpoint]) == 0:
 
53
                success = True
 
54
                log("Unmounted %s, Done" % mountpoint)
 
55
                break
 
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()))
 
61
            sleep(5)
 
62
 
 
63
    if not success:
 
64
        log(
 
65
            "ERROR: Unmount failed, leaving relation in errored state",
 
66
            hookenv.ERROR)
 
67
        sys.exit(1)
 
68
 
 
69
 
 
70
def _assert_block_device(device_path):
 
71
    """C{device_path} is a valid block device or exit in error"""
 
72
    isBlockDevice = False
 
73
    for x in range(0, 10):
 
74
        if not os.path.exists(device_path):
 
75
            log("WARNING: %s does not exist" % device_path)
 
76
        else:
 
77
            try:
 
78
                mode = os.stat(device_path)[ST_MODE]
 
79
                if S_ISBLK(mode):
 
80
                    log(
 
81
                        "DEBUG: block device found. Proceeding.",
 
82
                        hookenv.DEBUG)
 
83
                    isBlockDevice = True
 
84
                    break
 
85
            except OSError:
 
86
                pass
 
87
        sleep(5)
 
88
    if not isBlockDevice:
 
89
        log("ERROR: %s is not a block device." % device_path)
 
90
        sys.exit(1)
 
91
 
 
92
 
 
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)
 
97
        sys.exit(1)
 
98
 
 
99
    current_label = subprocess.check_output(
 
100
        "e2label %s" % device_path, shell=True)
 
101
    if current_label:
 
102
        current_label = current_label.strip()
 
103
        if current_label == label:
 
104
            # Label already set, return
 
105
            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)
 
110
 
 
111
 
 
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)
 
116
 
 
117
 
 
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
 
122
 
 
123
 
 
124
def _format_device(device_path):
 
125
    """
 
126
    Create an ext4 partition, if needed, for C{device_path} and fsck that
 
127
    partition.
 
128
    """
 
129
    result = _read_partition_table(device_path)
 
130
    if result == 1:
 
131
        log("INFO: %s is busy, no fsck performed. Assuming formatted." %
 
132
            device_path)
 
133
    elif result == 0:
 
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)
 
138
        else:
 
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)
 
144
            sys.exit(1)
 
145
 
 
146
 
 
147
def mount_volume(device_path=None):
 
148
    """
 
149
    Mount the attached volume, nfs or blockstoragebroker type, to the principal
 
150
    requested C{mountpoint}.
 
151
 
 
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
 
154
    or nfs device.
 
155
    """
 
156
    mountpoint = hookenv.relation_get("mountpoint")
 
157
    if not mountpoint:
 
158
        log("No mountpoint defined by relation. Cannot mount volume yet.")
 
159
        sys.exit(0)
 
160
 
 
161
    if is_mounted():
 
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})
 
167
        sys.exit(0)
 
168
 
 
169
    provider = get_provider()
 
170
    options = "default"
 
171
    if provider == "nfs":
 
172
        relids = hookenv.relation_ids("nfs")
 
173
        for relid in relids:
 
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")
 
181
                break
 
182
 
 
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.",
 
186
                hookenv.ERROR)
 
187
            sys.exit(1)
 
188
 
 
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)
 
194
            sys.exit(1)
 
195
 
 
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()
 
201
    else:
 
202
        log("ERROR: Unknown provider %s. Cannot mount volume." % hookenv.ERROR)
 
203
        sys.exit(1)
 
204
 
 
205
    if not os.path.exists(mountpoint):
 
206
        os.makedirs(mountpoint)
 
207
 
 
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:
 
214
        output_file.write(
 
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})