~ubuntu-branches/ubuntu/saucy/cloud-init/saucy

« back to all changes in this revision

Viewing changes to .pc/catchup-887.patch/cloudinit/config/cc_mounts.py

  • Committer: Scott Moser
  • Date: 2013-10-08 00:15:50 UTC
  • Revision ID: smoser@ubuntu.com-20131008001550-98r0dblzlpxfwdth
fix bug where a mount entry of 'ephemeral0' would only consider
the unpartitioned device, not also the first partition (LP: #1236594)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vi: ts=4 expandtab
 
2
#
 
3
#    Copyright (C) 2009-2010 Canonical Ltd.
 
4
#    Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
 
5
#
 
6
#    Author: Scott Moser <scott.moser@canonical.com>
 
7
#    Author: Juerg Haefliger <juerg.haefliger@hp.com>
 
8
#
 
9
#    This program is free software: you can redistribute it and/or modify
 
10
#    it under the terms of the GNU General Public License version 3, as
 
11
#    published by the Free Software Foundation.
 
12
#
 
13
#    This program is distributed in the hope that it will be useful,
 
14
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
#    GNU General Public License for more details.
 
17
#
 
18
#    You should have received a copy of the GNU General Public License
 
19
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
20
 
 
21
from string import whitespace  # pylint: disable=W0402
 
22
 
 
23
import logging
 
24
import os.path
 
25
import re
 
26
 
 
27
from cloudinit import type_utils
 
28
from cloudinit import util
 
29
 
 
30
# Shortname matches 'sda', 'sda1', 'xvda', 'hda', 'sdb', xvdb, vda, vdd1, sr0
 
31
SHORTNAME_FILTER = r"^([x]{0,1}[shv]d[a-z][0-9]*|sr[0-9]+)$"
 
32
SHORTNAME = re.compile(SHORTNAME_FILTER)
 
33
WS = re.compile("[%s]+" % (whitespace))
 
34
FSTAB_PATH = "/etc/fstab"
 
35
 
 
36
LOG = logging.getLogger(__name__)
 
37
 
 
38
 
 
39
def is_mdname(name):
 
40
    # return true if this is a metadata service name
 
41
    if name in ["ami", "root", "swap"]:
 
42
        return True
 
43
    # names 'ephemeral0' or 'ephemeral1'
 
44
    # 'ebs[0-9]' appears when '--block-device-mapping sdf=snap-d4d90bbc'
 
45
    for enumname in ("ephemeral", "ebs"):
 
46
        if name.startswith(enumname) and name.find(":") == -1:
 
47
            return True
 
48
    return False
 
49
 
 
50
 
 
51
def sanitize_devname(startname, transformer, log):
 
52
    log.debug("Attempting to determine the real name of %s", startname)
 
53
 
 
54
    # workaround, allow user to specify 'ephemeral'
 
55
    # rather than more ec2 correct 'ephemeral0'
 
56
    devname = startname
 
57
    if devname == "ephemeral":
 
58
        devname = "ephemeral0"
 
59
        log.debug("Adjusted mount option from ephemeral to ephemeral0")
 
60
 
 
61
    (blockdev, part) = util.expand_dotted_devname(devname)
 
62
 
 
63
    if is_mdname(blockdev):
 
64
        orig = blockdev
 
65
        blockdev = transformer(blockdev)
 
66
        if not blockdev:
 
67
            return None
 
68
        if not blockdev.startswith("/"):
 
69
            blockdev = "/dev/%s" % blockdev
 
70
        log.debug("Mapped metadata name %s to %s", orig, blockdev)
 
71
    else:
 
72
        if SHORTNAME.match(startname):
 
73
            blockdev = "/dev/%s" % blockdev
 
74
 
 
75
    return devnode_for_dev_part(blockdev, part)
 
76
 
 
77
 
 
78
def handle(_name, cfg, cloud, log, _args):
 
79
    # fs_spec, fs_file, fs_vfstype, fs_mntops, fs-freq, fs_passno
 
80
    defvals = [None, None, "auto", "defaults,nobootwait", "0", "2"]
 
81
    defvals = cfg.get("mount_default_fields", defvals)
 
82
 
 
83
    # these are our default set of mounts
 
84
    defmnts = [["ephemeral0", "/mnt", "auto", defvals[3], "0", "2"],
 
85
               ["swap", "none", "swap", "sw", "0", "0"]]
 
86
 
 
87
    cfgmnt = []
 
88
    if "mounts" in cfg:
 
89
        cfgmnt = cfg["mounts"]
 
90
 
 
91
    for i in range(len(cfgmnt)):
 
92
        # skip something that wasn't a list
 
93
        if not isinstance(cfgmnt[i], list):
 
94
            log.warn("Mount option %s not a list, got a %s instead",
 
95
                     (i + 1), type_utils.obj_name(cfgmnt[i]))
 
96
            continue
 
97
 
 
98
        start = str(cfgmnt[i][0])
 
99
        sanitized = sanitize_devname(start, cloud.device_name_to_device, log)
 
100
        if sanitized is None:
 
101
            log.debug("Ignorming nonexistant named mount %s", start)
 
102
            continue
 
103
 
 
104
        if sanitized != start:
 
105
            log.debug("changed %s => %s" % (start, sanitized))
 
106
        cfgmnt[i][0] = sanitized
 
107
 
 
108
        # in case the user did not quote a field (likely fs-freq, fs_passno)
 
109
        # but do not convert None to 'None' (LP: #898365)
 
110
        for j in range(len(cfgmnt[i])):
 
111
            if cfgmnt[i][j] is None:
 
112
                continue
 
113
            else:
 
114
                cfgmnt[i][j] = str(cfgmnt[i][j])
 
115
 
 
116
    for i in range(len(cfgmnt)):
 
117
        # fill in values with defaults from defvals above
 
118
        for j in range(len(defvals)):
 
119
            if len(cfgmnt[i]) <= j:
 
120
                cfgmnt[i].append(defvals[j])
 
121
            elif cfgmnt[i][j] is None:
 
122
                cfgmnt[i][j] = defvals[j]
 
123
 
 
124
        # if the second entry in the list is 'None' this
 
125
        # clears all previous entries of that same 'fs_spec'
 
126
        # (fs_spec is the first field in /etc/fstab, ie, that device)
 
127
        if cfgmnt[i][1] is None:
 
128
            for j in range(i):
 
129
                if cfgmnt[j][0] == cfgmnt[i][0]:
 
130
                    cfgmnt[j][1] = None
 
131
 
 
132
    # for each of the "default" mounts, add them only if no other
 
133
    # entry has the same device name
 
134
    for defmnt in defmnts:
 
135
        start = defmnt[0]
 
136
        sanitized = sanitize_devname(start, cloud.device_name_to_device, log)
 
137
        if sanitized is None:
 
138
            log.debug("Ignoring nonexistant default named mount %s", start)
 
139
            continue
 
140
        if sanitized != start:
 
141
            log.debug("changed default device %s => %s" % (start, sanitized))
 
142
        defmnt[0] = sanitized
 
143
 
 
144
        cfgmnt_has = False
 
145
        for cfgm in cfgmnt:
 
146
            if cfgm[0] == defmnt[0]:
 
147
                cfgmnt_has = True
 
148
                break
 
149
 
 
150
        if cfgmnt_has:
 
151
            log.debug(("Not including %s, already"
 
152
                       " previously included"), start)
 
153
            continue
 
154
        cfgmnt.append(defmnt)
 
155
 
 
156
    # now, each entry in the cfgmnt list has all fstab values
 
157
    # if the second field is None (not the string, the value) we skip it
 
158
    actlist = []
 
159
    for x in cfgmnt:
 
160
        if x[1] is None:
 
161
            log.debug("Skipping non-existent device named %s", x[0])
 
162
        else:
 
163
            actlist.append(x)
 
164
 
 
165
    if len(actlist) == 0:
 
166
        log.debug("No modifications to fstab needed.")
 
167
        return
 
168
 
 
169
    comment = "comment=cloudconfig"
 
170
    cc_lines = []
 
171
    needswap = False
 
172
    dirs = []
 
173
    for line in actlist:
 
174
        # write 'comment' in the fs_mntops, entry,  claiming this
 
175
        line[3] = "%s,%s" % (line[3], comment)
 
176
        if line[2] == "swap":
 
177
            needswap = True
 
178
        if line[1].startswith("/"):
 
179
            dirs.append(line[1])
 
180
        cc_lines.append('\t'.join(line))
 
181
 
 
182
    fstab_lines = []
 
183
    for line in util.load_file(FSTAB_PATH).splitlines():
 
184
        try:
 
185
            toks = WS.split(line)
 
186
            if toks[3].find(comment) != -1:
 
187
                continue
 
188
        except:
 
189
            pass
 
190
        fstab_lines.append(line)
 
191
 
 
192
    fstab_lines.extend(cc_lines)
 
193
    contents = "%s\n" % ('\n'.join(fstab_lines))
 
194
    util.write_file(FSTAB_PATH, contents)
 
195
 
 
196
    if needswap:
 
197
        try:
 
198
            util.subp(("swapon", "-a"))
 
199
        except:
 
200
            util.logexc(log, "Activating swap via 'swapon -a' failed")
 
201
 
 
202
    for d in dirs:
 
203
        try:
 
204
            util.ensure_dir(d)
 
205
        except:
 
206
            util.logexc(log, "Failed to make '%s' config-mount", d)
 
207
 
 
208
    try:
 
209
        util.subp(("mount", "-a"))
 
210
    except:
 
211
        util.logexc(log, "Activating mounts via 'mount -a' failed")
 
212
 
 
213
 
 
214
def devnode_for_dev_part(device, partition):
 
215
    """
 
216
    Find the name of the partition. While this might seem rather
 
217
    straight forward, its not since some devices are '<device><partition>'
 
218
    while others are '<device>p<partition>'. For example, /dev/xvda3 on EC2
 
219
    will present as /dev/xvda3p1 for the first partition since /dev/xvda3 is
 
220
    a block device.
 
221
    """
 
222
    if not os.path.exists(device):
 
223
        return None
 
224
 
 
225
    if not partition:
 
226
        return device
 
227
 
 
228
    short_name = os.path.basename(device)
 
229
    sys_path = "/sys/block/%s" % short_name
 
230
 
 
231
    if not os.path.exists(sys_path):
 
232
        LOG.debug("did not find entry for %s in /sys/block", short_name)
 
233
        return None
 
234
 
 
235
    sys_long_path = sys_path + "/" + short_name
 
236
 
 
237
    if partition is not None:
 
238
        partition = str(partition)
 
239
 
 
240
    if partition is None:
 
241
        valid_mappings = [sys_long_path + "1",
 
242
                          sys_long_path + "p1" % partition]
 
243
    elif partition != "0":
 
244
        valid_mappings = [sys_long_path + "%s" % partition,
 
245
                          sys_long_path + "p%s" % partition]
 
246
    else:
 
247
        valid_mappings = []
 
248
 
 
249
    for cdisk in valid_mappings:
 
250
        if not os.path.exists(cdisk):
 
251
            continue
 
252
 
 
253
        dev_path = "/dev/%s" % os.path.basename(cdisk)
 
254
        if os.path.exists(dev_path):
 
255
            return dev_path
 
256
 
 
257
    if partition is None or partition == "0":
 
258
        return device
 
259
 
 
260
    LOG.debug("Did not fine partition %s for device %s", partition, device)
 
261
    return None