~evarlast/cloud-init/lp-1244355

« back to all changes in this revision

Viewing changes to .pc/lp-974509-detect-dns-server-redirection.patch/cloudinit/util.py

  • Committer: Package Import Robot
  • Author(s): Scott Moser
  • Date: 2012-11-12 17:01:54 UTC
  • Revision ID: package-import@ubuntu.com-20121112170154-37170939vlg68nst
Tags: 0.6.3-0ubuntu1.2
* debian/patches/lp-978127-maas-oauth-fix-bad-clock.patch: fix usage of
  oauth in maas data source if local system has a bad clock (LP: #978127)
* debian/cloud-init.preinst: fix bug where user data scripts re-ran on
  upgrade from 10.04 versions (LP: #1049146)
* debian/patches/lp-974509-detect-dns-server-redirection.patch: detect dns
  server redirection and disable searching dns for a mirror named
  'ubuntu-mirror' (LP: #974509)
* debian/patches/lp-1018554-shutdown-message-to-console.patch: write a
  message to the console on system shutdown. (LP: #1018554)
* debian/patches/lp-1066115-landscape-install-fix-perms.patch: install
  landscape package if needed which will ensure proper permissions on config
  file (LP: #1066115).
* debian/patches/lp-1070345-landscape-restart-after-change.patch: restart
  landscape after modifying config (LP: #1070345)
* debian/patches/lp-1073077-zsh-workaround-for-locale_warn.patch: avoid
  warning when user's shell is zsh (LP: #1073077)
* debian/patches/rework-mirror-selection.patch: improve mirror selection by:
  * allowing region/availability-zone to be part of mirror (LP: #1037727)
  * making mirror selection arch aware (LP: #1028501)
  * allow specification of a security mirror (LP: #1006963)

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 Hafliger <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
import yaml
 
22
import os
 
23
import os.path
 
24
import shutil
 
25
import errno
 
26
import subprocess
 
27
from Cheetah.Template import Template
 
28
import urllib2
 
29
import urllib
 
30
import logging
 
31
import re
 
32
import socket
 
33
import sys
 
34
import time
 
35
import tempfile
 
36
import traceback
 
37
import urlparse
 
38
 
 
39
try:
 
40
    import selinux
 
41
    HAVE_LIBSELINUX = True
 
42
except ImportError:
 
43
    HAVE_LIBSELINUX = False
 
44
 
 
45
 
 
46
def read_conf(fname):
 
47
    try:
 
48
        stream = open(fname, "r")
 
49
        conf = yaml.load(stream)
 
50
        stream.close()
 
51
        return conf
 
52
    except IOError as e:
 
53
        if e.errno == errno.ENOENT:
 
54
            return {}
 
55
        raise
 
56
 
 
57
 
 
58
def get_base_cfg(cfgfile, cfg_builtin="", parsed_cfgs=None):
 
59
    kerncfg = {}
 
60
    syscfg = {}
 
61
    if parsed_cfgs and cfgfile in parsed_cfgs:
 
62
        return(parsed_cfgs[cfgfile])
 
63
 
 
64
    syscfg = read_conf_with_confd(cfgfile)
 
65
 
 
66
    kern_contents = read_cc_from_cmdline()
 
67
    if kern_contents:
 
68
        kerncfg = yaml.load(kern_contents)
 
69
 
 
70
    # kernel parameters override system config
 
71
    combined = mergedict(kerncfg, syscfg)
 
72
 
 
73
    if cfg_builtin:
 
74
        builtin = yaml.load(cfg_builtin)
 
75
        fin = mergedict(combined, builtin)
 
76
    else:
 
77
        fin = combined
 
78
 
 
79
    if parsed_cfgs != None:
 
80
        parsed_cfgs[cfgfile] = fin
 
81
    return(fin)
 
82
 
 
83
 
 
84
def get_cfg_option_bool(yobj, key, default=False):
 
85
    if key not in yobj:
 
86
        return default
 
87
    val = yobj[key]
 
88
    if val is True:
 
89
        return True
 
90
    if str(val).lower() in ['true', '1', 'on', 'yes']:
 
91
        return True
 
92
    return False
 
93
 
 
94
 
 
95
def get_cfg_option_str(yobj, key, default=None):
 
96
    if key not in yobj:
 
97
        return default
 
98
    return yobj[key]
 
99
 
 
100
 
 
101
def get_cfg_option_list_or_str(yobj, key, default=None):
 
102
    """
 
103
    Gets the C{key} config option from C{yobj} as a list of strings. If the
 
104
    key is present as a single string it will be returned as a list with one
 
105
    string arg.
 
106
 
 
107
    @param yobj: The configuration object.
 
108
    @param key: The configuration key to get.
 
109
    @param default: The default to return if key is not found.
 
110
    @return: The configuration option as a list of strings or default if key
 
111
        is not found.
 
112
    """
 
113
    if not key in yobj:
 
114
        return default
 
115
    if yobj[key] is None:
 
116
        return []
 
117
    if isinstance(yobj[key], list):
 
118
        return yobj[key]
 
119
    return [yobj[key]]
 
120
 
 
121
 
 
122
# get a cfg entry by its path array
 
123
# for f['a']['b']: get_cfg_by_path(mycfg,('a','b'))
 
124
def get_cfg_by_path(yobj, keyp, default=None):
 
125
    cur = yobj
 
126
    for tok in keyp:
 
127
        if tok not in cur:
 
128
            return(default)
 
129
        cur = cur[tok]
 
130
    return(cur)
 
131
 
 
132
 
 
133
def mergedict(src, cand):
 
134
    """
 
135
    Merge values from C{cand} into C{src}. If C{src} has a key C{cand} will
 
136
    not override. Nested dictionaries are merged recursively.
 
137
    """
 
138
    if isinstance(src, dict) and isinstance(cand, dict):
 
139
        for k, v in cand.iteritems():
 
140
            if k not in src:
 
141
                src[k] = v
 
142
            else:
 
143
                src[k] = mergedict(src[k], v)
 
144
    return src
 
145
 
 
146
 
 
147
def delete_dir_contents(dirname):
 
148
    """
 
149
    Deletes all contents of a directory without deleting the directory itself.
 
150
 
 
151
    @param dirname: The directory whose contents should be deleted.
 
152
    """
 
153
    for node in os.listdir(dirname):
 
154
        node_fullpath = os.path.join(dirname, node)
 
155
        if os.path.isdir(node_fullpath):
 
156
            shutil.rmtree(node_fullpath)
 
157
        else:
 
158
            os.unlink(node_fullpath)
 
159
 
 
160
 
 
161
def write_file(filename, content, mode=0644, omode="wb"):
 
162
    """
 
163
    Writes a file with the given content and sets the file mode as specified.
 
164
    Resotres the SELinux context if possible.
 
165
 
 
166
    @param filename: The full path of the file to write.
 
167
    @param content: The content to write to the file.
 
168
    @param mode: The filesystem mode to set on the file.
 
169
    @param omode: The open mode used when opening the file (r, rb, a, etc.)
 
170
    """
 
171
    try:
 
172
        os.makedirs(os.path.dirname(filename))
 
173
    except OSError as e:
 
174
        if e.errno != errno.EEXIST:
 
175
            raise e
 
176
 
 
177
    f = open(filename, omode)
 
178
    if mode is not None:
 
179
        os.chmod(filename, mode)
 
180
    f.write(content)
 
181
    f.close()
 
182
    restorecon_if_possible(filename)
 
183
 
 
184
 
 
185
def restorecon_if_possible(path, recursive=False):
 
186
    if HAVE_LIBSELINUX and selinux.is_selinux_enabled():
 
187
        selinux.restorecon(path, recursive=recursive)
 
188
 
 
189
 
 
190
# get keyid from keyserver
 
191
def getkeybyid(keyid, keyserver):
 
192
    shcmd = """
 
193
    k=${1} ks=${2};
 
194
    exec 2>/dev/null
 
195
    [ -n "$k" ] || exit 1;
 
196
    armour=$(gpg --list-keys --armour "${k}")
 
197
    if [ -z "${armour}" ]; then
 
198
       gpg --keyserver ${ks} --recv $k >/dev/null &&
 
199
          armour=$(gpg --export --armour "${k}") &&
 
200
          gpg --batch --yes --delete-keys "${k}"
 
201
    fi
 
202
    [ -n "${armour}" ] && echo "${armour}"
 
203
    """
 
204
    args = ['sh', '-c', shcmd, "export-gpg-keyid", keyid, keyserver]
 
205
    return(subp(args)[0])
 
206
 
 
207
 
 
208
def runparts(dirp, skip_no_exist=True):
 
209
    if skip_no_exist and not os.path.isdir(dirp):
 
210
        return
 
211
 
 
212
    failed = 0
 
213
    for exe_name in sorted(os.listdir(dirp)):
 
214
        exe_path = os.path.join(dirp, exe_name)
 
215
        if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
 
216
            popen = subprocess.Popen([exe_path])
 
217
            popen.communicate()
 
218
            if popen.returncode is not 0:
 
219
                failed += 1
 
220
                sys.stderr.write("failed: %s [%i]\n" %
 
221
                    (exe_path, popen.returncode))
 
222
    if failed:
 
223
        raise RuntimeError('runparts: %i failures' % failed)
 
224
 
 
225
 
 
226
def subp(args, input_=None):
 
227
    sp = subprocess.Popen(args, stdout=subprocess.PIPE,
 
228
        stderr=subprocess.PIPE, stdin=subprocess.PIPE)
 
229
    out, err = sp.communicate(input_)
 
230
    if sp.returncode is not 0:
 
231
        raise subprocess.CalledProcessError(sp.returncode, args, (out, err))
 
232
    return(out, err)
 
233
 
 
234
 
 
235
def render_to_file(template, outfile, searchList):
 
236
    t = Template(file='/etc/cloud/templates/%s.tmpl' % template,
 
237
                 searchList=[searchList])
 
238
    f = open(outfile, 'w')
 
239
    f.write(t.respond())
 
240
    f.close()
 
241
 
 
242
 
 
243
def render_string(template, searchList):
 
244
    return(Template(template, searchList=[searchList]).respond())
 
245
 
 
246
 
 
247
# read_optional_seed
 
248
# returns boolean indicating success or failure (presense of files)
 
249
# if files are present, populates 'fill' dictionary with 'user-data' and
 
250
# 'meta-data' entries
 
251
def read_optional_seed(fill, base="", ext="", timeout=5):
 
252
    try:
 
253
        (md, ud) = read_seeded(base, ext, timeout)
 
254
        fill['user-data'] = ud
 
255
        fill['meta-data'] = md
 
256
        return True
 
257
    except OSError, e:
 
258
        if e.errno == errno.ENOENT:
 
259
            return False
 
260
        raise
 
261
 
 
262
 
 
263
# raise OSError with enoent if not found
 
264
def read_seeded(base="", ext="", timeout=5, retries=10, file_retries=0):
 
265
    if base.startswith("/"):
 
266
        base = "file://%s" % base
 
267
 
 
268
    # default retries for file is 0. for network is 10
 
269
    if base.startswith("file://"):
 
270
        retries = file_retries
 
271
 
 
272
    if base.find("%s") >= 0:
 
273
        ud_url = base % ("user-data" + ext)
 
274
        md_url = base % ("meta-data" + ext)
 
275
    else:
 
276
        ud_url = "%s%s%s" % (base, "user-data", ext)
 
277
        md_url = "%s%s%s" % (base, "meta-data", ext)
 
278
 
 
279
    no_exc = object()
 
280
    raise_err = no_exc
 
281
    for attempt in range(0, retries + 1):
 
282
        try:
 
283
            md_str = readurl(md_url, timeout=timeout)
 
284
            ud = readurl(ud_url, timeout=timeout)
 
285
            md = yaml.load(md_str)
 
286
 
 
287
            return(md, ud)
 
288
        except urllib2.HTTPError as e:
 
289
            raise_err = e
 
290
        except urllib2.URLError as e:
 
291
            raise_err = e
 
292
            if (isinstance(e.reason, OSError) and
 
293
                e.reason.errno == errno.ENOENT):
 
294
                raise_err = e.reason
 
295
 
 
296
        if attempt == retries:
 
297
            break
 
298
 
 
299
        #print "%s failed, sleeping" % attempt
 
300
        time.sleep(1)
 
301
 
 
302
    raise(raise_err)
 
303
 
 
304
 
 
305
def logexc(log, lvl=logging.DEBUG):
 
306
    log.log(lvl, traceback.format_exc())
 
307
 
 
308
 
 
309
class RecursiveInclude(Exception):
 
310
    pass
 
311
 
 
312
 
 
313
def read_file_with_includes(fname, rel=".", stack=None, patt=None):
 
314
    if stack is None:
 
315
        stack = []
 
316
    if not fname.startswith("/"):
 
317
        fname = os.sep.join((rel, fname))
 
318
 
 
319
    fname = os.path.realpath(fname)
 
320
 
 
321
    if fname in stack:
 
322
        raise(RecursiveInclude("%s recursively included" % fname))
 
323
    if len(stack) > 10:
 
324
        raise(RecursiveInclude("%s included, stack size = %i" %
 
325
                               (fname, len(stack))))
 
326
 
 
327
    if patt == None:
 
328
        patt = re.compile("^#(opt_include|include)[ \t].*$", re.MULTILINE)
 
329
 
 
330
    try:
 
331
        fp = open(fname)
 
332
        contents = fp.read()
 
333
        fp.close()
 
334
    except:
 
335
        raise
 
336
 
 
337
    rel = os.path.dirname(fname)
 
338
    stack.append(fname)
 
339
 
 
340
    cur = 0
 
341
    while True:
 
342
        match = patt.search(contents[cur:])
 
343
        if not match:
 
344
            break
 
345
        loc = match.start() + cur
 
346
        endl = match.end() + cur
 
347
 
 
348
        (key, cur_fname) = contents[loc:endl].split(None, 2)
 
349
        cur_fname = cur_fname.strip()
 
350
 
 
351
        try:
 
352
            inc_contents = read_file_with_includes(cur_fname, rel, stack, patt)
 
353
        except IOError, e:
 
354
            if e.errno == errno.ENOENT and key == "#opt_include":
 
355
                inc_contents = ""
 
356
            else:
 
357
                raise
 
358
        contents = contents[0:loc] + inc_contents + contents[endl + 1:]
 
359
        cur = loc + len(inc_contents)
 
360
    stack.pop()
 
361
    return(contents)
 
362
 
 
363
 
 
364
def read_conf_d(confd):
 
365
    # get reverse sorted list (later trumps newer)
 
366
    confs = sorted(os.listdir(confd), reverse=True)
 
367
 
 
368
    # remove anything not ending in '.cfg'
 
369
    confs = [f for f in confs if f.endswith(".cfg")]
 
370
 
 
371
    # remove anything not a file
 
372
    confs = [f for f in confs if os.path.isfile("%s/%s" % (confd, f))]
 
373
 
 
374
    cfg = {}
 
375
    for conf in confs:
 
376
        cfg = mergedict(cfg, read_conf("%s/%s" % (confd, conf)))
 
377
 
 
378
    return(cfg)
 
379
 
 
380
 
 
381
def read_conf_with_confd(cfgfile):
 
382
    cfg = read_conf(cfgfile)
 
383
    confd = False
 
384
    if "conf_d" in cfg:
 
385
        if cfg['conf_d'] is not None:
 
386
            confd = cfg['conf_d']
 
387
            if not isinstance(confd, str):
 
388
                raise Exception("cfgfile %s contains 'conf_d' "
 
389
                                "with non-string" % cfgfile)
 
390
    elif os.path.isdir("%s.d" % cfgfile):
 
391
        confd = "%s.d" % cfgfile
 
392
 
 
393
    if not confd:
 
394
        return(cfg)
 
395
 
 
396
    confd_cfg = read_conf_d(confd)
 
397
 
 
398
    return(mergedict(confd_cfg, cfg))
 
399
 
 
400
 
 
401
def get_cmdline():
 
402
    if 'DEBUG_PROC_CMDLINE' in os.environ:
 
403
        cmdline = os.environ["DEBUG_PROC_CMDLINE"]
 
404
    else:
 
405
        try:
 
406
            cmdfp = open("/proc/cmdline")
 
407
            cmdline = cmdfp.read().strip()
 
408
            cmdfp.close()
 
409
        except:
 
410
            cmdline = ""
 
411
    return(cmdline)
 
412
 
 
413
 
 
414
def read_cc_from_cmdline(cmdline=None):
 
415
    # this should support reading cloud-config information from
 
416
    # the kernel command line.  It is intended to support content of the
 
417
    # format:
 
418
    #  cc: <yaml content here> [end_cc]
 
419
    # this would include:
 
420
    # cc: ssh_import_id: [smoser, kirkland]\\n
 
421
    # cc: ssh_import_id: [smoser, bob]\\nruncmd: [ [ ls, -l ], echo hi ] end_cc
 
422
    # cc:ssh_import_id: [smoser] end_cc cc:runcmd: [ [ ls, -l ] ] end_cc
 
423
    if cmdline is None:
 
424
        cmdline = get_cmdline()
 
425
 
 
426
    tag_begin = "cc:"
 
427
    tag_end = "end_cc"
 
428
    begin_l = len(tag_begin)
 
429
    end_l = len(tag_end)
 
430
    clen = len(cmdline)
 
431
    tokens = []
 
432
    begin = cmdline.find(tag_begin)
 
433
    while begin >= 0:
 
434
        end = cmdline.find(tag_end, begin + begin_l)
 
435
        if end < 0:
 
436
            end = clen
 
437
        tokens.append(cmdline[begin + begin_l:end].lstrip().replace("\\n",
 
438
                                                                    "\n"))
 
439
 
 
440
        begin = cmdline.find(tag_begin, end + end_l)
 
441
 
 
442
    return('\n'.join(tokens))
 
443
 
 
444
 
 
445
def ensure_dirs(dirlist, mode=0755):
 
446
    fixmodes = []
 
447
    for d in dirlist:
 
448
        try:
 
449
            if mode != None:
 
450
                os.makedirs(d)
 
451
            else:
 
452
                os.makedirs(d, mode)
 
453
        except OSError as e:
 
454
            if e.errno != errno.EEXIST:
 
455
                raise
 
456
            if mode != None:
 
457
                fixmodes.append(d)
 
458
 
 
459
    for d in fixmodes:
 
460
        os.chmod(d, mode)
 
461
 
 
462
 
 
463
def chownbyname(fname, user=None, group=None):
 
464
    uid = -1
 
465
    gid = -1
 
466
    if user == None and group == None:
 
467
        return
 
468
    if user:
 
469
        import pwd
 
470
        uid = pwd.getpwnam(user).pw_uid
 
471
    if group:
 
472
        import grp
 
473
        gid = grp.getgrnam(group).gr_gid
 
474
 
 
475
    os.chown(fname, uid, gid)
 
476
 
 
477
 
 
478
def readurl(url, data=None, timeout=None):
 
479
    openargs = {}
 
480
    if timeout != None:
 
481
        openargs['timeout'] = timeout
 
482
 
 
483
    if data is None:
 
484
        req = urllib2.Request(url)
 
485
    else:
 
486
        encoded = urllib.urlencode(data)
 
487
        req = urllib2.Request(url, encoded)
 
488
 
 
489
    response = urllib2.urlopen(req, **openargs)
 
490
    return(response.read())
 
491
 
 
492
 
 
493
# shellify, takes a list of commands
 
494
#  for each entry in the list
 
495
#    if it is an array, shell protect it (with single ticks)
 
496
#    if it is a string, do nothing
 
497
def shellify(cmdlist):
 
498
    content = "#!/bin/sh\n"
 
499
    escaped = "%s%s%s%s" % ("'", '\\', "'", "'")
 
500
    for args in cmdlist:
 
501
        # if the item is a list, wrap all items in single tick
 
502
        # if its not, then just write it directly
 
503
        if isinstance(args, list):
 
504
            fixed = []
 
505
            for f in args:
 
506
                fixed.append("'%s'" % str(f).replace("'", escaped))
 
507
            content = "%s%s\n" % (content, ' '.join(fixed))
 
508
        else:
 
509
            content = "%s%s\n" % (content, str(args))
 
510
    return content
 
511
 
 
512
 
 
513
def dos2unix(string):
 
514
    # find first end of line
 
515
    pos = string.find('\n')
 
516
    if pos <= 0 or string[pos - 1] != '\r':
 
517
        return(string)
 
518
    return(string.replace('\r\n', '\n'))
 
519
 
 
520
 
 
521
def is_container():
 
522
    # is this code running in a container of some sort
 
523
 
 
524
    for helper in ('running-in-container', 'lxc-is-container'):
 
525
        try:
 
526
            # try to run a helper program. if it returns true
 
527
            # then we're inside a container. otherwise, no
 
528
            sp = subprocess.Popen(helper, stdout=subprocess.PIPE,
 
529
                                  stderr=subprocess.PIPE)
 
530
            sp.communicate(None)
 
531
            return(sp.returncode == 0)
 
532
        except OSError as e:
 
533
            if e.errno != errno.ENOENT:
 
534
                raise
 
535
 
 
536
    # this code is largely from the logic in
 
537
    # ubuntu's /etc/init/container-detect.conf
 
538
    try:
 
539
        # Detect old-style libvirt
 
540
        # Detect OpenVZ containers
 
541
        pid1env = get_proc_env(1)
 
542
        if "container" in pid1env:
 
543
            return True
 
544
 
 
545
        if "LIBVIRT_LXC_UUID" in pid1env:
 
546
            return True
 
547
 
 
548
    except IOError as e:
 
549
        if e.errno != errno.ENOENT:
 
550
            pass
 
551
 
 
552
    # Detect OpenVZ containers
 
553
    if os.path.isdir("/proc/vz") and not os.path.isdir("/proc/bc"):
 
554
        return True
 
555
 
 
556
    try:
 
557
        # Detect Vserver containers
 
558
        with open("/proc/self/status") as fp:
 
559
            lines = fp.read().splitlines()
 
560
            for line in lines:
 
561
                if line.startswith("VxID:"):
 
562
                    (_key, val) = line.strip().split(":", 1)
 
563
                    if val != "0":
 
564
                        return True
 
565
    except IOError as e:
 
566
        if e.errno != errno.ENOENT:
 
567
            pass
 
568
 
 
569
    return False
 
570
 
 
571
 
 
572
def get_proc_env(pid):
 
573
    # return the environment in a dict that a given process id was started with
 
574
    env = {}
 
575
    with open("/proc/%s/environ" % pid) as fp:
 
576
        toks = fp.read().split("\0")
 
577
        for tok in toks:
 
578
            if tok == "":
 
579
                continue
 
580
            (name, val) = tok.split("=", 1)
 
581
            env[name] = val
 
582
    return env
 
583
 
 
584
 
 
585
def get_hostname_fqdn(cfg, cloud):
 
586
    # return the hostname and fqdn from 'cfg'.  If not found in cfg,
 
587
    # then fall back to data from cloud
 
588
    if "fqdn" in cfg:
 
589
        # user specified a fqdn.  Default hostname then is based off that
 
590
        fqdn = cfg['fqdn']
 
591
        hostname = get_cfg_option_str(cfg, "hostname", fqdn.split('.')[0])
 
592
    else:
 
593
        if "hostname" in cfg and cfg['hostname'].find('.') > 0:
 
594
            # user specified hostname, and it had '.' in it
 
595
            # be nice to them.  set fqdn and hostname from that
 
596
            fqdn = cfg['hostname']
 
597
            hostname = cfg['hostname'][:fqdn.find('.')]
 
598
        else:
 
599
            # no fqdn set, get fqdn from cloud.
 
600
            # get hostname from cfg if available otherwise cloud
 
601
            fqdn = cloud.get_hostname(fqdn=True)
 
602
            if "hostname" in cfg:
 
603
                hostname = cfg['hostname']
 
604
            else:
 
605
                hostname = cloud.get_hostname()
 
606
    return(hostname, fqdn)
 
607
 
 
608
 
 
609
def get_fqdn_from_hosts(hostname, filename="/etc/hosts"):
 
610
    # this parses /etc/hosts to get a fqdn.  It should return the same
 
611
    # result as 'hostname -f <hostname>' if /etc/hosts.conf
 
612
    # did not have did not have 'bind' in the order attribute
 
613
    fqdn = None
 
614
    try:
 
615
        with open(filename, "r") as hfp:
 
616
            for line in hfp.readlines():
 
617
                hashpos = line.find("#")
 
618
                if hashpos >= 0:
 
619
                    line = line[0:hashpos]
 
620
                toks = line.split()
 
621
 
 
622
                # if there there is less than 3 entries (ip, canonical, alias)
 
623
                # then ignore this line
 
624
                if len(toks) < 3:
 
625
                    continue
 
626
 
 
627
                if hostname in toks[2:]:
 
628
                    fqdn = toks[1]
 
629
                    break
 
630
            hfp.close()
 
631
    except IOError as e:
 
632
        if e.errno == errno.ENOENT:
 
633
            pass
 
634
 
 
635
    return fqdn
 
636
 
 
637
 
 
638
def is_resolvable(name):
 
639
    """ determine if a url is resolvable, return a boolean """
 
640
    try:
 
641
        socket.getaddrinfo(name, None)
 
642
        return True
 
643
    except socket.gaierror:
 
644
        return False
 
645
 
 
646
 
 
647
def is_resolvable_url(url):
 
648
    """ determine if this url is resolvable (existing or ip) """
 
649
    return(is_resolvable(urlparse.urlparse(url).hostname))
 
650
 
 
651
 
 
652
def search_for_mirror(candidates):
 
653
    """ Search through a list of mirror urls for one that works """
 
654
    for cand in candidates:
 
655
        try:
 
656
            if is_resolvable_url(cand):
 
657
                return cand
 
658
        except Exception:
 
659
            raise
 
660
 
 
661
    return None
 
662
 
 
663
 
 
664
def close_stdin():
 
665
    """
 
666
    reopen stdin as /dev/null so even subprocesses or other os level things get
 
667
    /dev/null as input.
 
668
 
 
669
    if _CLOUD_INIT_SAVE_STDIN is set in environment to a non empty or '0' value
 
670
    then input will not be closed (only useful potentially for debugging).
 
671
    """
 
672
    if os.environ.get("_CLOUD_INIT_SAVE_STDIN") in ("", "0", False):
 
673
        return
 
674
    with open(os.devnull) as fp:
 
675
        os.dup2(fp.fileno(), sys.stdin.fileno())
 
676
 
 
677
 
 
678
def find_devs_with(criteria):
 
679
    """
 
680
    find devices matching given criteria (via blkid)
 
681
    criteria can be *one* of:
 
682
      TYPE=<filesystem>
 
683
      LABEL=<label>
 
684
      UUID=<uuid>
 
685
    """
 
686
    try:
 
687
        (out, _err) = subp(['blkid', '-t%s' % criteria, '-odevice'])
 
688
    except subprocess.CalledProcessError:
 
689
        return([])
 
690
    return(str(out).splitlines())
 
691
 
 
692
 
 
693
class mountFailedError(Exception):
 
694
    pass
 
695
 
 
696
 
 
697
def mount_callback_umount(device, callback, data=None):
 
698
    """
 
699
    mount the device, call method 'callback' passing the directory
 
700
    in which it was mounted, then unmount.  Return whatever 'callback'
 
701
    returned.  If data != None, also pass data to callback.
 
702
    """
 
703
 
 
704
    def _cleanup(umount, tmpd):
 
705
        if umount:
 
706
            try:
 
707
                subp(["umount", '-l', umount])
 
708
            except subprocess.CalledProcessError:
 
709
                raise
 
710
        if tmpd:
 
711
            os.rmdir(tmpd)
 
712
 
 
713
    # go through mounts to see if it was already mounted
 
714
    fp = open("/proc/mounts")
 
715
    mounts = fp.readlines()
 
716
    fp.close()
 
717
 
 
718
    tmpd = None
 
719
 
 
720
    mounted = {}
 
721
    for mpline in mounts:
 
722
        (dev, mp, fstype, _opts, _freq, _passno) = mpline.split()
 
723
        mp = mp.replace("\\040", " ")
 
724
        mounted[dev] = (dev, fstype, mp, False)
 
725
 
 
726
    umount = False
 
727
    if device in mounted:
 
728
        mountpoint = "%s/" % mounted[device][2]
 
729
    else:
 
730
        tmpd = tempfile.mkdtemp()
 
731
 
 
732
        mountcmd = ["mount", "-o", "ro", device, tmpd]
 
733
 
 
734
        try:
 
735
            (_out, _err) = subp(mountcmd)
 
736
            umount = tmpd
 
737
        except subprocess.CalledProcessError as exc:
 
738
            _cleanup(umount, tmpd)
 
739
            raise mountFailedError(exc.output[1])
 
740
 
 
741
        mountpoint = "%s/" % tmpd
 
742
 
 
743
    try:
 
744
        if data == None:
 
745
            ret = callback(mountpoint)
 
746
        else:
 
747
            ret = callback(mountpoint, data)
 
748
 
 
749
    except Exception as exc:
 
750
        _cleanup(umount, tmpd)
 
751
        raise exc
 
752
 
 
753
    _cleanup(umount, tmpd)
 
754
 
 
755
    return(ret)
 
756
 
 
757
 
 
758
def wait_for_url(urls, max_wait=None, timeout=None,
 
759
                 status_cb=None, headers_cb=None, exception_cb=None):
 
760
    """
 
761
    urls:      a list of urls to try
 
762
    max_wait:  roughly the maximum time to wait before giving up
 
763
               The max time is *actually* len(urls)*timeout as each url will
 
764
               be tried once and given the timeout provided.
 
765
    timeout:   the timeout provided to urllib2.urlopen
 
766
    status_cb: call method with string message when a url is not available
 
767
    headers_cb: call method with single argument of url to get headers
 
768
                for request.
 
769
    exception_cb: call method with 2 arguments 'msg' (per status_cb) and
 
770
                  'exception', the exception that occurred.
 
771
 
 
772
    the idea of this routine is to wait for the EC2 metdata service to
 
773
    come up.  On both Eucalyptus and EC2 we have seen the case where
 
774
    the instance hit the MD before the MD service was up.  EC2 seems
 
775
    to have permenantely fixed this, though.
 
776
 
 
777
    In openstack, the metadata service might be painfully slow, and
 
778
    unable to avoid hitting a timeout of even up to 10 seconds or more
 
779
    (LP: #894279) for a simple GET.
 
780
 
 
781
    Offset those needs with the need to not hang forever (and block boot)
 
782
    on a system where cloud-init is configured to look for EC2 Metadata
 
783
    service but is not going to find one.  It is possible that the instance
 
784
    data host (169.254.169.254) may be firewalled off Entirely for a sytem,
 
785
    meaning that the connection will block forever unless a timeout is set.
 
786
    """
 
787
    starttime = time.time()
 
788
 
 
789
    sleeptime = 1
 
790
 
 
791
    def nullstatus_cb(msg):
 
792
        return
 
793
 
 
794
    if status_cb == None:
 
795
        status_cb = nullstatus_cb
 
796
 
 
797
    def timeup(max_wait, starttime):
 
798
        return((max_wait <= 0 or max_wait == None) or
 
799
               (time.time() - starttime > max_wait))
 
800
 
 
801
    loop_n = 0
 
802
    while True:
 
803
        sleeptime = int(loop_n / 5) + 1
 
804
        for url in urls:
 
805
            now = time.time()
 
806
            if loop_n != 0:
 
807
                if timeup(max_wait, starttime):
 
808
                    break
 
809
                if timeout and (now + timeout > (starttime + max_wait)):
 
810
                    # shorten timeout to not run way over max_time
 
811
                    timeout = int((starttime + max_wait) - now)
 
812
 
 
813
            reason = ""
 
814
            try:
 
815
                if headers_cb != None:
 
816
                    headers = headers_cb(url)
 
817
                else:
 
818
                    headers = {}
 
819
 
 
820
                req = urllib2.Request(url, data=None, headers=headers)
 
821
                resp = urllib2.urlopen(req, timeout=timeout)
 
822
                contents = resp.read()
 
823
                if not contents:
 
824
                    reason = "empty data [%s]" % (resp.code)
 
825
                    e = ValueError(reason)
 
826
                elif not (resp.code >= 200 and resp.code < 400):
 
827
                    reason = "bad status code [%s]" % (resp.code)
 
828
                    e = ValueError(reason)
 
829
                else:
 
830
                    return url
 
831
            except urllib2.HTTPError as e:
 
832
                reason = "http error [%s]" % e.code
 
833
            except urllib2.URLError as e:
 
834
                reason = "url error [%s]" % e.reason
 
835
            except socket.timeout as e:
 
836
                reason = "socket timeout [%s]" % e
 
837
            except Exception as e:
 
838
                reason = "unexpected error [%s]" % e
 
839
 
 
840
            status_msg = ("'%s' failed [%s/%ss]: %s" %
 
841
                          (url, int(time.time() - starttime), max_wait,
 
842
                           reason))
 
843
            status_cb(status_msg)
 
844
            if exception_cb:
 
845
                exception_cb(msg=status_msg, exception=e)
 
846
 
 
847
        if timeup(max_wait, starttime):
 
848
            break
 
849
 
 
850
        loop_n = loop_n + 1
 
851
        time.sleep(sleeptime)
 
852
 
 
853
    return False
 
854
 
 
855
 
 
856
def keyval_str_to_dict(kvstring):
 
857
    ret = {}
 
858
    for tok in kvstring.split():
 
859
        try:
 
860
            (key, val) = tok.split("=", 1)
 
861
        except ValueError:
 
862
            key = tok
 
863
            val = True
 
864
        ret[key] = val
 
865
 
 
866
    return(ret)