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

« back to all changes in this revision

Viewing changes to cloudinit/sources/DataSourceAzure.py

  • Committer: Scott Moser
  • Date: 2014-03-21 16:26:05 UTC
  • mfrom: (317.1.2 saucy-proposed.ben)
  • Revision ID: smoser@ubuntu.com-20140321162605-3u4kmqayg5k7agab
* debian/patches/lp-1269626-azure_new_instance.patch:
  fix handling of new instances on Windows Azure (LP: #1269626).
* debian/patches/lp-1292648-azure-format-ephemeral-new.patch:
  re-format ephemeral disk if necessary (LP: #1292648).

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
 
19
19
import base64
20
20
import crypt
 
21
import fnmatch
21
22
import os
22
23
import os.path
23
24
import time
24
25
from xml.dom import minidom
25
26
 
26
27
from cloudinit import log as logging
 
28
from cloudinit.settings import PER_ALWAYS
27
29
from cloudinit import sources
28
30
from cloudinit import util
29
31
 
34
36
AGENT_START = ['service', 'walinuxagent', 'start']
35
37
BOUNCE_COMMAND = ['sh', '-xc',
36
38
    "i=$interface; x=0; ifdown $i || x=$?; ifup $i || x=$?; exit $x"]
 
39
DATA_DIR_CLEAN_LIST = ['SharedConfig.xml']
37
40
 
38
41
BUILTIN_DS_CONFIG = {
39
42
    'agent_command': AGENT_START,
52
55
    'disk_setup': {
53
56
        'ephemeral0': {'table_type': 'mbr',
54
57
                       'layout': True,
55
 
                       'overwrite': False}
56
 
         },
 
58
                       'overwrite': False},
 
59
        },
57
60
    'fs_setup': [{'filesystem': 'ext4',
58
61
                  'device': 'ephemeral0.1',
59
 
                  'replace_fs': 'ntfs'}]
 
62
                  'replace_fs': 'ntfs'}],
60
63
}
61
64
 
62
65
DS_CFG_PATH = ['datasource', DS_NAME]
 
66
DEF_EPHEMERAL_LABEL = 'Temporary Storage'
63
67
 
64
68
 
65
69
class DataSourceAzureNet(sources.DataSource):
101
105
            except BrokenAzureDataSource as exc:
102
106
                raise exc
103
107
            except util.MountFailedError:
104
 
                LOG.warn("%s was not mountable" % cdev)
 
108
                LOG.warn("%s was not mountable", cdev)
105
109
                continue
106
110
 
107
111
            (md, self.userdata_raw, cfg, files) = ret
128
132
        user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
129
133
        self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg])
130
134
        mycfg = self.ds_cfg
 
135
        ddir = mycfg['data_dir']
 
136
 
 
137
        if found != ddir:
 
138
            cached_ovfenv = util.load_file(
 
139
                os.path.join(ddir, 'ovf-env.xml'), quiet=True)
 
140
            if cached_ovfenv != files['ovf-env.xml']:
 
141
                # source was not walinux-agent's datadir, so we have to clean
 
142
                # up so 'wait_for_files' doesn't return early due to stale data
 
143
                cleaned = []
 
144
                for f in [os.path.join(ddir, f) for f in DATA_DIR_CLEAN_LIST]:
 
145
                    if os.path.exists(f):
 
146
                        util.del_file(f)
 
147
                        cleaned.append(f)
 
148
                if cleaned:
 
149
                    LOG.info("removed stale file(s) in '%s': %s",
 
150
                             ddir, str(cleaned))
131
151
 
132
152
        # walinux agent writes files world readable, but expects
133
153
        # the directory to be protected.
134
 
        write_files(mycfg['data_dir'], files, dirmode=0700)
 
154
        write_files(ddir, files, dirmode=0700)
135
155
 
136
156
        # handle the hostname 'publishing'
137
157
        try:
139
159
                                self.metadata.get('local-hostname'),
140
160
                                mycfg['hostname_bounce'])
141
161
        except Exception as e:
142
 
            LOG.warn("Failed publishing hostname: %s" % e)
 
162
            LOG.warn("Failed publishing hostname: %s", e)
143
163
            util.logexc(LOG, "handling set_hostname failed")
144
164
 
145
165
        try:
149
169
            util.logexc(LOG, "agent command '%s' failed.",
150
170
                        mycfg['agent_command'])
151
171
 
152
 
        shcfgxml = os.path.join(mycfg['data_dir'], "SharedConfig.xml")
 
172
        shcfgxml = os.path.join(ddir, "SharedConfig.xml")
153
173
        wait_for = [shcfgxml]
154
174
 
155
175
        fp_files = []
156
176
        for pk in self.cfg.get('_pubkeys', []):
157
 
            bname = pk['fingerprint'] + ".crt"
158
 
            fp_files += [os.path.join(mycfg['data_dir'], bname)]
 
177
            bname = str(pk['fingerprint'] + ".crt")
 
178
            fp_files += [os.path.join(ddir, bname)]
159
179
 
160
180
        missing = util.log_time(logfunc=LOG.debug, msg="waiting for files",
161
181
                                func=wait_for_files,
169
189
            try:
170
190
                self.metadata['instance-id'] = iid_from_shared_config(shcfgxml)
171
191
            except ValueError as e:
172
 
                LOG.warn("failed to get instance id in %s: %s" % (shcfgxml, e))
 
192
                LOG.warn("failed to get instance id in %s: %s", shcfgxml, e)
173
193
 
174
194
        pubkeys = pubkeys_from_crt_files(fp_files)
175
 
 
176
195
        self.metadata['public-keys'] = pubkeys
 
196
 
 
197
        found_ephemeral = find_ephemeral_disk()
 
198
        if found_ephemeral:
 
199
            self.ds_cfg['disk_aliases']['ephemeral0'] = found_ephemeral
 
200
            LOG.debug("using detected ephemeral0 of %s", found_ephemeral)
 
201
 
 
202
        cc_modules_override = support_new_ephemeral(self.sys_cfg)
 
203
        if cc_modules_override:
 
204
            self.cfg['cloud_config_modules'] = cc_modules_override
 
205
 
177
206
        return True
178
207
 
179
208
    def device_name_to_device(self, name):
183
212
        return self.cfg
184
213
 
185
214
 
 
215
def count_files(mp):
 
216
    return len(fnmatch.filter(os.listdir(mp), '*[!cdrom]*'))
 
217
 
 
218
 
 
219
def find_ephemeral_part():
 
220
    """
 
221
    Locate the default ephmeral0.1 device. This will be the first device
 
222
    that has a LABEL of DEF_EPHEMERAL_LABEL and is a NTFS device. If Azure
 
223
    gets more ephemeral devices, this logic will only identify the first
 
224
    such device.
 
225
    """
 
226
    c_label_devs = util.find_devs_with("LABEL=%s" % DEF_EPHEMERAL_LABEL)
 
227
    c_fstype_devs = util.find_devs_with("TYPE=ntfs")
 
228
    for dev in c_label_devs:
 
229
        if dev in c_fstype_devs:
 
230
            return dev
 
231
    return None
 
232
 
 
233
 
 
234
def find_ephemeral_disk():
 
235
    """
 
236
    Get the ephemeral disk.
 
237
    """
 
238
    part_dev = find_ephemeral_part()
 
239
    if part_dev and str(part_dev[-1]).isdigit():
 
240
        return part_dev[:-1]
 
241
    elif part_dev:
 
242
        return part_dev
 
243
    return None
 
244
 
 
245
 
 
246
def support_new_ephemeral(cfg):
 
247
    """
 
248
    Windows Azure makes ephemeral devices ephemeral to boot; a ephemeral device
 
249
    may be presented as a fresh device, or not.
 
250
 
 
251
    Since the knowledge of when a disk is supposed to be plowed under is
 
252
    specific to Windows Azure, the logic resides here in the datasource. When a
 
253
    new ephemeral device is detected, cloud-init overrides the default
 
254
    frequency for both disk-setup and mounts for the current boot only.
 
255
    """
 
256
    device = find_ephemeral_part()
 
257
    if not device:
 
258
        LOG.debug("no default fabric formated ephemeral0.1 found")
 
259
        return None
 
260
    LOG.debug("fabric formated ephemeral0.1 device at %s", device)
 
261
 
 
262
    file_count = 0
 
263
    try:
 
264
        file_count = util.mount_cb(device, count_files)
 
265
    except:
 
266
        return None
 
267
    LOG.debug("fabric prepared ephmeral0.1 has %s files on it", file_count)
 
268
 
 
269
    if file_count >= 1:
 
270
        LOG.debug("fabric prepared ephemeral0.1 will be preserved")
 
271
        return None
 
272
    else:
 
273
        # if device was already mounted, then we need to unmount it
 
274
        # race conditions could allow for a check-then-unmount
 
275
        # to have a false positive. so just unmount and then check.
 
276
        try:
 
277
            util.subp(['umount', device])
 
278
        except util.ProcessExecutionError as e:
 
279
            if device in util.mounts():
 
280
                LOG.warn("Failed to unmount %s, will not reformat.", device)
 
281
                LOG.debug("Failed umount: %s", e)
 
282
                return None
 
283
 
 
284
    LOG.debug("cloud-init will format ephemeral0.1 this boot.")
 
285
    LOG.debug("setting disk_setup and mounts modules 'always' for this boot")
 
286
 
 
287
    cc_modules = cfg.get('cloud_config_modules')
 
288
    if not cc_modules:
 
289
        return None
 
290
 
 
291
    mod_list = []
 
292
    for mod in cc_modules:
 
293
        if mod in ("disk_setup", "mounts"):
 
294
            mod_list.append([mod, PER_ALWAYS])
 
295
            LOG.debug("set module '%s' to 'always' for this boot", mod)
 
296
        else:
 
297
            mod_list.append(mod)
 
298
    return mod_list
 
299
 
 
300
 
186
301
def handle_set_hostname(enabled, hostname, cfg):
187
302
    if not util.is_true(enabled):
188
303
        return
247
362
        try:
248
363
            pubkeys.append(crtfile_to_pubkey(fname))
249
364
        except util.ProcessExecutionError:
250
 
            errors.extend(fname)
 
365
            errors.append(fname)
251
366
 
252
367
    if errors:
253
 
        LOG.warn("failed to convert the crt files to pubkey: %s" % errors)
 
368
        LOG.warn("failed to convert the crt files to pubkey: %s", errors)
254
369
 
255
370
    return pubkeys
256
371
 
281
396
def invoke_agent(cmd):
282
397
    # this is a function itself to simplify patching it for test
283
398
    if cmd:
284
 
        LOG.debug("invoking agent: %s" % cmd)
 
399
        LOG.debug("invoking agent: %s", cmd)
285
400
        util.subp(cmd, shell=(not isinstance(cmd, list)))
286
401
    else:
287
402
        LOG.debug("not invoking agent")
328
443
            continue
329
444
        cur = {'fingerprint': "", 'path': ""}
330
445
        for child in pk_node.childNodes:
331
 
            if (child.nodeType == text_node or not child.localName):
 
446
            if child.nodeType == text_node or not child.localName:
332
447
                continue
333
448
 
334
449
            name = child.localName.lower()
410
525
            simple = True
411
526
            value = child.childNodes[0].wholeText
412
527
 
413
 
        attrs = {k: v for k, v in child.attributes.items()}
 
528
        attrs = dict([(k, v) for k, v in child.attributes.items()])
414
529
 
415
530
        # we accept either UserData or CustomData.  If both are present
416
531
        # then behavior is undefined.
417
 
        if (name == "userdata" or name == "customdata"):
 
532
        if name == "userdata" or name == "customdata":
418
533
            if attrs.get('encoding') in (None, "base64"):
419
534
                ud = base64.b64decode(''.join(value.split()))
420
535
            else: