~ubuntu-branches/ubuntu/quantal/cinder/quantal-updates

« back to all changes in this revision

Viewing changes to .pc/CVE-2013-1664.patch/cinder/utils.py

  • Committer: Package Import Robot
  • Author(s): Jamie Strandboge
  • Date: 2013-02-19 11:36:44 UTC
  • mfrom: (13.1.1 quantal-proposed)
  • Revision ID: package-import@ubuntu.com-20130219113644-s06finfxuiysiuhy
Tags: 2012.2.1-0ubuntu1.1
* SECURITY UPDATE: fix denial of service
  - CVE-2013-1664.patch: Add a new utils.safe_minidom_parse_string function
    and update external API facing Cinder modules to use it
  - CVE-2013-1664
  - LP: #1100282

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
3
 
 
4
# Copyright 2010 United States Government as represented by the
 
5
# Administrator of the National Aeronautics and Space Administration.
 
6
# Copyright 2011 Justin Santa Barbara
 
7
# All Rights Reserved.
 
8
#
 
9
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
10
#    not use this file except in compliance with the License. You may obtain
 
11
#    a copy of the License at
 
12
#
 
13
#         http://www.apache.org/licenses/LICENSE-2.0
 
14
#
 
15
#    Unless required by applicable law or agreed to in writing, software
 
16
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
17
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
18
#    License for the specific language governing permissions and limitations
 
19
#    under the License.
 
20
 
 
21
"""Utilities and helper functions."""
 
22
 
 
23
import contextlib
 
24
import datetime
 
25
import errno
 
26
import functools
 
27
import hashlib
 
28
import inspect
 
29
import itertools
 
30
import os
 
31
import pyclbr
 
32
import random
 
33
import re
 
34
import shlex
 
35
import shutil
 
36
import signal
 
37
import socket
 
38
import struct
 
39
import sys
 
40
import tempfile
 
41
import time
 
42
import types
 
43
import uuid
 
44
import warnings
 
45
from xml.sax import saxutils
 
46
 
 
47
from eventlet import event
 
48
from eventlet import greenthread
 
49
from eventlet.green import subprocess
 
50
 
 
51
from cinder.common import deprecated
 
52
from cinder import exception
 
53
from cinder import flags
 
54
from cinder.openstack.common import log as logging
 
55
from cinder.openstack.common import excutils
 
56
from cinder.openstack.common import importutils
 
57
from cinder.openstack.common import timeutils
 
58
 
 
59
 
 
60
LOG = logging.getLogger(__name__)
 
61
ISO_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
 
62
PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
 
63
FLAGS = flags.FLAGS
 
64
 
 
65
 
 
66
def find_config(config_path):
 
67
    """Find a configuration file using the given hint.
 
68
 
 
69
    :param config_path: Full or relative path to the config.
 
70
    :returns: Full path of the config, if it exists.
 
71
    :raises: `cinder.exception.ConfigNotFound`
 
72
 
 
73
    """
 
74
    possible_locations = [
 
75
        config_path,
 
76
        os.path.join(FLAGS.state_path, "etc", "cinder", config_path),
 
77
        os.path.join(FLAGS.state_path, "etc", config_path),
 
78
        os.path.join(FLAGS.state_path, config_path),
 
79
        "/etc/cinder/%s" % config_path,
 
80
    ]
 
81
 
 
82
    for path in possible_locations:
 
83
        if os.path.exists(path):
 
84
            return os.path.abspath(path)
 
85
 
 
86
    raise exception.ConfigNotFound(path=os.path.abspath(config_path))
 
87
 
 
88
 
 
89
def fetchfile(url, target):
 
90
    LOG.debug(_('Fetching %s') % url)
 
91
    execute('curl', '--fail', url, '-o', target)
 
92
 
 
93
 
 
94
def _subprocess_setup():
 
95
    # Python installs a SIGPIPE handler by default. This is usually not what
 
96
    # non-Python subprocesses expect.
 
97
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
 
98
 
 
99
 
 
100
def execute(*cmd, **kwargs):
 
101
    """Helper method to execute command with optional retry.
 
102
 
 
103
    If you add a run_as_root=True command, don't forget to add the
 
104
    corresponding filter to etc/cinder/rootwrap.d !
 
105
 
 
106
    :param cmd:                Passed to subprocess.Popen.
 
107
    :param process_input:      Send to opened process.
 
108
    :param check_exit_code:    Single bool, int, or list of allowed exit
 
109
                               codes.  Defaults to [0].  Raise
 
110
                               exception.ProcessExecutionError unless
 
111
                               program exits with one of these code.
 
112
    :param delay_on_retry:     True | False. Defaults to True. If set to
 
113
                               True, wait a short amount of time
 
114
                               before retrying.
 
115
    :param attempts:           How many times to retry cmd.
 
116
    :param run_as_root:        True | False. Defaults to False. If set to True,
 
117
                               the command is prefixed by the command specified
 
118
                               in the root_helper FLAG.
 
119
 
 
120
    :raises exception.Error: on receiving unknown arguments
 
121
    :raises exception.ProcessExecutionError:
 
122
 
 
123
    :returns: a tuple, (stdout, stderr) from the spawned process, or None if
 
124
             the command fails.
 
125
    """
 
126
 
 
127
    process_input = kwargs.pop('process_input', None)
 
128
    check_exit_code = kwargs.pop('check_exit_code', [0])
 
129
    ignore_exit_code = False
 
130
    if isinstance(check_exit_code, bool):
 
131
        ignore_exit_code = not check_exit_code
 
132
        check_exit_code = [0]
 
133
    elif isinstance(check_exit_code, int):
 
134
        check_exit_code = [check_exit_code]
 
135
    delay_on_retry = kwargs.pop('delay_on_retry', True)
 
136
    attempts = kwargs.pop('attempts', 1)
 
137
    run_as_root = kwargs.pop('run_as_root', False)
 
138
    shell = kwargs.pop('shell', False)
 
139
 
 
140
    if len(kwargs):
 
141
        raise exception.Error(_('Got unknown keyword args '
 
142
                                'to utils.execute: %r') % kwargs)
 
143
 
 
144
    if run_as_root:
 
145
 
 
146
        if FLAGS.rootwrap_config is None or FLAGS.root_helper != 'sudo':
 
147
            deprecated.warn(_('The root_helper option (which lets you specify '
 
148
                              'a root wrapper different from cinder-rootwrap, '
 
149
                              'and defaults to using sudo) is now deprecated. '
 
150
                              'You should use the rootwrap_config option '
 
151
                              'instead.'))
 
152
 
 
153
        if (FLAGS.rootwrap_config is not None):
 
154
            cmd = ['sudo', 'cinder-rootwrap',
 
155
                   FLAGS.rootwrap_config] + list(cmd)
 
156
        else:
 
157
            cmd = shlex.split(FLAGS.root_helper) + list(cmd)
 
158
    cmd = map(str, cmd)
 
159
 
 
160
    while attempts > 0:
 
161
        attempts -= 1
 
162
        try:
 
163
            LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd))
 
164
            _PIPE = subprocess.PIPE  # pylint: disable=E1101
 
165
            obj = subprocess.Popen(cmd,
 
166
                                   stdin=_PIPE,
 
167
                                   stdout=_PIPE,
 
168
                                   stderr=_PIPE,
 
169
                                   close_fds=True,
 
170
                                   preexec_fn=_subprocess_setup,
 
171
                                   shell=shell)
 
172
            result = None
 
173
            if process_input is not None:
 
174
                result = obj.communicate(process_input)
 
175
            else:
 
176
                result = obj.communicate()
 
177
            obj.stdin.close()  # pylint: disable=E1101
 
178
            _returncode = obj.returncode  # pylint: disable=E1101
 
179
            if _returncode:
 
180
                LOG.debug(_('Result was %s') % _returncode)
 
181
                if not ignore_exit_code and _returncode not in check_exit_code:
 
182
                    (stdout, stderr) = result
 
183
                    raise exception.ProcessExecutionError(
 
184
                            exit_code=_returncode,
 
185
                            stdout=stdout,
 
186
                            stderr=stderr,
 
187
                            cmd=' '.join(cmd))
 
188
            return result
 
189
        except exception.ProcessExecutionError:
 
190
            if not attempts:
 
191
                raise
 
192
            else:
 
193
                LOG.debug(_('%r failed. Retrying.'), cmd)
 
194
                if delay_on_retry:
 
195
                    greenthread.sleep(random.randint(20, 200) / 100.0)
 
196
        finally:
 
197
            # NOTE(termie): this appears to be necessary to let the subprocess
 
198
            #               call clean something up in between calls, without
 
199
            #               it two execute calls in a row hangs the second one
 
200
            greenthread.sleep(0)
 
201
 
 
202
 
 
203
def trycmd(*args, **kwargs):
 
204
    """
 
205
    A wrapper around execute() to more easily handle warnings and errors.
 
206
 
 
207
    Returns an (out, err) tuple of strings containing the output of
 
208
    the command's stdout and stderr.  If 'err' is not empty then the
 
209
    command can be considered to have failed.
 
210
 
 
211
    :discard_warnings   True | False. Defaults to False. If set to True,
 
212
                        then for succeeding commands, stderr is cleared
 
213
 
 
214
    """
 
215
    discard_warnings = kwargs.pop('discard_warnings', False)
 
216
 
 
217
    try:
 
218
        out, err = execute(*args, **kwargs)
 
219
        failed = False
 
220
    except exception.ProcessExecutionError, exn:
 
221
        out, err = '', str(exn)
 
222
        LOG.debug(err)
 
223
        failed = True
 
224
 
 
225
    if not failed and discard_warnings and err:
 
226
        # Handle commands that output to stderr but otherwise succeed
 
227
        LOG.debug(err)
 
228
        err = ''
 
229
 
 
230
    return out, err
 
231
 
 
232
 
 
233
def ssh_execute(ssh, cmd, process_input=None,
 
234
                addl_env=None, check_exit_code=True):
 
235
    LOG.debug(_('Running cmd (SSH): %s'), ' '.join(cmd))
 
236
    if addl_env:
 
237
        raise exception.Error(_('Environment not supported over SSH'))
 
238
 
 
239
    if process_input:
 
240
        # This is (probably) fixable if we need it...
 
241
        raise exception.Error(_('process_input not supported over SSH'))
 
242
 
 
243
    stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd)
 
244
    channel = stdout_stream.channel
 
245
 
 
246
    #stdin.write('process_input would go here')
 
247
    #stdin.flush()
 
248
 
 
249
    # NOTE(justinsb): This seems suspicious...
 
250
    # ...other SSH clients have buffering issues with this approach
 
251
    stdout = stdout_stream.read()
 
252
    stderr = stderr_stream.read()
 
253
    stdin_stream.close()
 
254
 
 
255
    exit_status = channel.recv_exit_status()
 
256
 
 
257
    # exit_status == -1 if no exit code was returned
 
258
    if exit_status != -1:
 
259
        LOG.debug(_('Result was %s') % exit_status)
 
260
        if check_exit_code and exit_status != 0:
 
261
            raise exception.ProcessExecutionError(exit_code=exit_status,
 
262
                                                  stdout=stdout,
 
263
                                                  stderr=stderr,
 
264
                                                  cmd=' '.join(cmd))
 
265
 
 
266
    return (stdout, stderr)
 
267
 
 
268
 
 
269
def cinderdir():
 
270
    import cinder
 
271
    return os.path.abspath(cinder.__file__).split('cinder/__init__.py')[0]
 
272
 
 
273
 
 
274
def debug(arg):
 
275
    LOG.debug(_('debug in callback: %s'), arg)
 
276
    return arg
 
277
 
 
278
 
 
279
def generate_uid(topic, size=8):
 
280
    characters = '01234567890abcdefghijklmnopqrstuvwxyz'
 
281
    choices = [random.choice(characters) for x in xrange(size)]
 
282
    return '%s-%s' % (topic, ''.join(choices))
 
283
 
 
284
 
 
285
# Default symbols to use for passwords. Avoids visually confusing characters.
 
286
# ~6 bits per symbol
 
287
DEFAULT_PASSWORD_SYMBOLS = ('23456789',  # Removed: 0,1
 
288
                            'ABCDEFGHJKLMNPQRSTUVWXYZ',   # Removed: I, O
 
289
                            'abcdefghijkmnopqrstuvwxyz')  # Removed: l
 
290
 
 
291
 
 
292
# ~5 bits per symbol
 
293
EASIER_PASSWORD_SYMBOLS = ('23456789',  # Removed: 0, 1
 
294
                           'ABCDEFGHJKLMNPQRSTUVWXYZ')  # Removed: I, O
 
295
 
 
296
 
 
297
def last_completed_audit_period(unit=None):
 
298
    """This method gives you the most recently *completed* audit period.
 
299
 
 
300
    arguments:
 
301
            units: string, one of 'hour', 'day', 'month', 'year'
 
302
                    Periods normally begin at the beginning (UTC) of the
 
303
                    period unit (So a 'day' period begins at midnight UTC,
 
304
                    a 'month' unit on the 1st, a 'year' on Jan, 1)
 
305
                    unit string may be appended with an optional offset
 
306
                    like so:  'day@18'  This will begin the period at 18:00
 
307
                    UTC.  'month@15' starts a monthly period on the 15th,
 
308
                    and year@3 begins a yearly one on March 1st.
 
309
 
 
310
 
 
311
    returns:  2 tuple of datetimes (begin, end)
 
312
              The begin timestamp of this audit period is the same as the
 
313
              end of the previous."""
 
314
    if not unit:
 
315
        unit = FLAGS.instance_usage_audit_period
 
316
 
 
317
    offset = 0
 
318
    if '@' in unit:
 
319
        unit, offset = unit.split("@", 1)
 
320
        offset = int(offset)
 
321
 
 
322
    rightnow = timeutils.utcnow()
 
323
    if unit not in ('month', 'day', 'year', 'hour'):
 
324
        raise ValueError('Time period must be hour, day, month or year')
 
325
    if unit == 'month':
 
326
        if offset == 0:
 
327
            offset = 1
 
328
        end = datetime.datetime(day=offset,
 
329
                                month=rightnow.month,
 
330
                                year=rightnow.year)
 
331
        if end >= rightnow:
 
332
            year = rightnow.year
 
333
            if 1 >= rightnow.month:
 
334
                year -= 1
 
335
                month = 12 + (rightnow.month - 1)
 
336
            else:
 
337
                month = rightnow.month - 1
 
338
            end = datetime.datetime(day=offset,
 
339
                                    month=month,
 
340
                                    year=year)
 
341
        year = end.year
 
342
        if 1 >= end.month:
 
343
            year -= 1
 
344
            month = 12 + (end.month - 1)
 
345
        else:
 
346
            month = end.month - 1
 
347
        begin = datetime.datetime(day=offset, month=month, year=year)
 
348
 
 
349
    elif unit == 'year':
 
350
        if offset == 0:
 
351
            offset = 1
 
352
        end = datetime.datetime(day=1, month=offset, year=rightnow.year)
 
353
        if end >= rightnow:
 
354
            end = datetime.datetime(day=1,
 
355
                                    month=offset,
 
356
                                    year=rightnow.year - 1)
 
357
            begin = datetime.datetime(day=1,
 
358
                                      month=offset,
 
359
                                      year=rightnow.year - 2)
 
360
        else:
 
361
            begin = datetime.datetime(day=1,
 
362
                                      month=offset,
 
363
                                      year=rightnow.year - 1)
 
364
 
 
365
    elif unit == 'day':
 
366
        end = datetime.datetime(hour=offset,
 
367
                               day=rightnow.day,
 
368
                               month=rightnow.month,
 
369
                               year=rightnow.year)
 
370
        if end >= rightnow:
 
371
            end = end - datetime.timedelta(days=1)
 
372
        begin = end - datetime.timedelta(days=1)
 
373
 
 
374
    elif unit == 'hour':
 
375
        end = rightnow.replace(minute=offset, second=0, microsecond=0)
 
376
        if end >= rightnow:
 
377
            end = end - datetime.timedelta(hours=1)
 
378
        begin = end - datetime.timedelta(hours=1)
 
379
 
 
380
    return (begin, end)
 
381
 
 
382
 
 
383
def generate_password(length=20, symbolgroups=DEFAULT_PASSWORD_SYMBOLS):
 
384
    """Generate a random password from the supplied symbol groups.
 
385
 
 
386
    At least one symbol from each group will be included. Unpredictable
 
387
    results if length is less than the number of symbol groups.
 
388
 
 
389
    Believed to be reasonably secure (with a reasonable password length!)
 
390
 
 
391
    """
 
392
    r = random.SystemRandom()
 
393
 
 
394
    # NOTE(jerdfelt): Some password policies require at least one character
 
395
    # from each group of symbols, so start off with one random character
 
396
    # from each symbol group
 
397
    password = [r.choice(s) for s in symbolgroups]
 
398
    # If length < len(symbolgroups), the leading characters will only
 
399
    # be from the first length groups. Try our best to not be predictable
 
400
    # by shuffling and then truncating.
 
401
    r.shuffle(password)
 
402
    password = password[:length]
 
403
    length -= len(password)
 
404
 
 
405
    # then fill with random characters from all symbol groups
 
406
    symbols = ''.join(symbolgroups)
 
407
    password.extend([r.choice(symbols) for _i in xrange(length)])
 
408
 
 
409
    # finally shuffle to ensure first x characters aren't from a
 
410
    # predictable group
 
411
    r.shuffle(password)
 
412
 
 
413
    return ''.join(password)
 
414
 
 
415
 
 
416
def last_octet(address):
 
417
    return int(address.split('.')[-1])
 
418
 
 
419
 
 
420
def get_my_linklocal(interface):
 
421
    try:
 
422
        if_str = execute('ip', '-f', 'inet6', '-o', 'addr', 'show', interface)
 
423
        condition = '\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link'
 
424
        links = [re.search(condition, x) for x in if_str[0].split('\n')]
 
425
        address = [w.group(1) for w in links if w is not None]
 
426
        if address[0] is not None:
 
427
            return address[0]
 
428
        else:
 
429
            raise exception.Error(_('Link Local address is not found.:%s')
 
430
                                  % if_str)
 
431
    except Exception as ex:
 
432
        raise exception.Error(_("Couldn't get Link Local IP of %(interface)s"
 
433
                                " :%(ex)s") % locals())
 
434
 
 
435
 
 
436
def parse_mailmap(mailmap='.mailmap'):
 
437
    mapping = {}
 
438
    if os.path.exists(mailmap):
 
439
        fp = open(mailmap, 'r')
 
440
        for l in fp:
 
441
            l = l.strip()
 
442
            if not l.startswith('#') and ' ' in l:
 
443
                canonical_email, alias = l.split(' ')
 
444
                mapping[alias.lower()] = canonical_email.lower()
 
445
    return mapping
 
446
 
 
447
 
 
448
def str_dict_replace(s, mapping):
 
449
    for s1, s2 in mapping.iteritems():
 
450
        s = s.replace(s1, s2)
 
451
    return s
 
452
 
 
453
 
 
454
class LazyPluggable(object):
 
455
    """A pluggable backend loaded lazily based on some value."""
 
456
 
 
457
    def __init__(self, pivot, **backends):
 
458
        self.__backends = backends
 
459
        self.__pivot = pivot
 
460
        self.__backend = None
 
461
 
 
462
    def __get_backend(self):
 
463
        if not self.__backend:
 
464
            backend_name = FLAGS[self.__pivot]
 
465
            if backend_name not in self.__backends:
 
466
                raise exception.Error(_('Invalid backend: %s') % backend_name)
 
467
 
 
468
            backend = self.__backends[backend_name]
 
469
            if isinstance(backend, tuple):
 
470
                name = backend[0]
 
471
                fromlist = backend[1]
 
472
            else:
 
473
                name = backend
 
474
                fromlist = backend
 
475
 
 
476
            self.__backend = __import__(name, None, None, fromlist)
 
477
            LOG.debug(_('backend %s'), self.__backend)
 
478
        return self.__backend
 
479
 
 
480
    def __getattr__(self, key):
 
481
        backend = self.__get_backend()
 
482
        return getattr(backend, key)
 
483
 
 
484
 
 
485
class LoopingCallDone(Exception):
 
486
    """Exception to break out and stop a LoopingCall.
 
487
 
 
488
    The poll-function passed to LoopingCall can raise this exception to
 
489
    break out of the loop normally. This is somewhat analogous to
 
490
    StopIteration.
 
491
 
 
492
    An optional return-value can be included as the argument to the exception;
 
493
    this return-value will be returned by LoopingCall.wait()
 
494
 
 
495
    """
 
496
 
 
497
    def __init__(self, retvalue=True):
 
498
        """:param retvalue: Value that LoopingCall.wait() should return."""
 
499
        self.retvalue = retvalue
 
500
 
 
501
 
 
502
class LoopingCall(object):
 
503
    def __init__(self, f=None, *args, **kw):
 
504
        self.args = args
 
505
        self.kw = kw
 
506
        self.f = f
 
507
        self._running = False
 
508
 
 
509
    def start(self, interval, initial_delay=None):
 
510
        self._running = True
 
511
        done = event.Event()
 
512
 
 
513
        def _inner():
 
514
            if initial_delay:
 
515
                greenthread.sleep(initial_delay)
 
516
 
 
517
            try:
 
518
                while self._running:
 
519
                    self.f(*self.args, **self.kw)
 
520
                    if not self._running:
 
521
                        break
 
522
                    greenthread.sleep(interval)
 
523
            except LoopingCallDone, e:
 
524
                self.stop()
 
525
                done.send(e.retvalue)
 
526
            except Exception:
 
527
                LOG.exception(_('in looping call'))
 
528
                done.send_exception(*sys.exc_info())
 
529
                return
 
530
            else:
 
531
                done.send(True)
 
532
 
 
533
        self.done = done
 
534
 
 
535
        greenthread.spawn(_inner)
 
536
        return self.done
 
537
 
 
538
    def stop(self):
 
539
        self._running = False
 
540
 
 
541
    def wait(self):
 
542
        return self.done.wait()
 
543
 
 
544
 
 
545
def xhtml_escape(value):
 
546
    """Escapes a string so it is valid within XML or XHTML.
 
547
 
 
548
    """
 
549
    return saxutils.escape(value, {'"': '&quot;', "'": '&apos;'})
 
550
 
 
551
 
 
552
def utf8(value):
 
553
    """Try to turn a string into utf-8 if possible.
 
554
 
 
555
    Code is directly from the utf8 function in
 
556
    http://github.com/facebook/tornado/blob/master/tornado/escape.py
 
557
 
 
558
    """
 
559
    if isinstance(value, unicode):
 
560
        return value.encode('utf-8')
 
561
    assert isinstance(value, str)
 
562
    return value
 
563
 
 
564
 
 
565
def delete_if_exists(pathname):
 
566
    """delete a file, but ignore file not found error"""
 
567
 
 
568
    try:
 
569
        os.unlink(pathname)
 
570
    except OSError as e:
 
571
        if e.errno == errno.ENOENT:
 
572
            return
 
573
        else:
 
574
            raise
 
575
 
 
576
 
 
577
def get_from_path(items, path):
 
578
    """Returns a list of items matching the specified path.
 
579
 
 
580
    Takes an XPath-like expression e.g. prop1/prop2/prop3, and for each item
 
581
    in items, looks up items[prop1][prop2][prop3].  Like XPath, if any of the
 
582
    intermediate results are lists it will treat each list item individually.
 
583
    A 'None' in items or any child expressions will be ignored, this function
 
584
    will not throw because of None (anywhere) in items.  The returned list
 
585
    will contain no None values.
 
586
 
 
587
    """
 
588
    if path is None:
 
589
        raise exception.Error('Invalid mini_xpath')
 
590
 
 
591
    (first_token, sep, remainder) = path.partition('/')
 
592
 
 
593
    if first_token == '':
 
594
        raise exception.Error('Invalid mini_xpath')
 
595
 
 
596
    results = []
 
597
 
 
598
    if items is None:
 
599
        return results
 
600
 
 
601
    if not isinstance(items, list):
 
602
        # Wrap single objects in a list
 
603
        items = [items]
 
604
 
 
605
    for item in items:
 
606
        if item is None:
 
607
            continue
 
608
        get_method = getattr(item, 'get', None)
 
609
        if get_method is None:
 
610
            continue
 
611
        child = get_method(first_token)
 
612
        if child is None:
 
613
            continue
 
614
        if isinstance(child, list):
 
615
            # Flatten intermediate lists
 
616
            for x in child:
 
617
                results.append(x)
 
618
        else:
 
619
            results.append(child)
 
620
 
 
621
    if not sep:
 
622
        # No more tokens
 
623
        return results
 
624
    else:
 
625
        return get_from_path(results, remainder)
 
626
 
 
627
 
 
628
def flatten_dict(dict_, flattened=None):
 
629
    """Recursively flatten a nested dictionary."""
 
630
    flattened = flattened or {}
 
631
    for key, value in dict_.iteritems():
 
632
        if hasattr(value, 'iteritems'):
 
633
            flatten_dict(value, flattened)
 
634
        else:
 
635
            flattened[key] = value
 
636
    return flattened
 
637
 
 
638
 
 
639
def partition_dict(dict_, keys):
 
640
    """Return two dicts, one with `keys` the other with everything else."""
 
641
    intersection = {}
 
642
    difference = {}
 
643
    for key, value in dict_.iteritems():
 
644
        if key in keys:
 
645
            intersection[key] = value
 
646
        else:
 
647
            difference[key] = value
 
648
    return intersection, difference
 
649
 
 
650
 
 
651
def map_dict_keys(dict_, key_map):
 
652
    """Return a dict in which the dictionaries keys are mapped to new keys."""
 
653
    mapped = {}
 
654
    for key, value in dict_.iteritems():
 
655
        mapped_key = key_map[key] if key in key_map else key
 
656
        mapped[mapped_key] = value
 
657
    return mapped
 
658
 
 
659
 
 
660
def subset_dict(dict_, keys):
 
661
    """Return a dict that only contains a subset of keys."""
 
662
    subset = partition_dict(dict_, keys)[0]
 
663
    return subset
 
664
 
 
665
 
 
666
def check_isinstance(obj, cls):
 
667
    """Checks that obj is of type cls, and lets PyLint infer types."""
 
668
    if isinstance(obj, cls):
 
669
        return obj
 
670
    raise Exception(_('Expected object of type: %s') % (str(cls)))
 
671
    # TODO(justinsb): Can we make this better??
 
672
    return cls()  # Ugly PyLint hack
 
673
 
 
674
 
 
675
def gen_uuid():
 
676
    return uuid.uuid4()
 
677
 
 
678
 
 
679
def is_uuid_like(val):
 
680
    """For our purposes, a UUID is a string in canonical form:
 
681
 
 
682
        aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
 
683
    """
 
684
    try:
 
685
        uuid.UUID(val)
 
686
        return True
 
687
    except (TypeError, ValueError, AttributeError):
 
688
        return False
 
689
 
 
690
 
 
691
def bool_from_str(val):
 
692
    """Convert a string representation of a bool into a bool value"""
 
693
 
 
694
    if not val:
 
695
        return False
 
696
    try:
 
697
        return True if int(val) else False
 
698
    except ValueError:
 
699
        return val.lower() == 'true'
 
700
 
 
701
 
 
702
def is_valid_boolstr(val):
 
703
    """Check if the provided string is a valid bool string or not. """
 
704
    val = str(val).lower()
 
705
    return val == 'true' or val == 'false' or \
 
706
           val == 'yes' or val == 'no' or \
 
707
           val == 'y' or val == 'n' or \
 
708
           val == '1' or val == '0'
 
709
 
 
710
 
 
711
def is_valid_ipv4(address):
 
712
    """valid the address strictly as per format xxx.xxx.xxx.xxx.
 
713
    where xxx is a value between 0 and 255.
 
714
    """
 
715
    parts = address.split(".")
 
716
    if len(parts) != 4:
 
717
        return False
 
718
    for item in parts:
 
719
        try:
 
720
            if not 0 <= int(item) <= 255:
 
721
                return False
 
722
        except ValueError:
 
723
            return False
 
724
    return True
 
725
 
 
726
 
 
727
def monkey_patch():
 
728
    """  If the Flags.monkey_patch set as True,
 
729
    this function patches a decorator
 
730
    for all functions in specified modules.
 
731
    You can set decorators for each modules
 
732
    using FLAGS.monkey_patch_modules.
 
733
    The format is "Module path:Decorator function".
 
734
    Example: 'cinder.api.ec2.cloud:' \
 
735
     cinder.openstack.common.notifier.api.notify_decorator'
 
736
 
 
737
    Parameters of the decorator is as follows.
 
738
    (See cinder.openstack.common.notifier.api.notify_decorator)
 
739
 
 
740
    name - name of the function
 
741
    function - object of the function
 
742
    """
 
743
    # If FLAGS.monkey_patch is not True, this function do nothing.
 
744
    if not FLAGS.monkey_patch:
 
745
        return
 
746
    # Get list of modules and decorators
 
747
    for module_and_decorator in FLAGS.monkey_patch_modules:
 
748
        module, decorator_name = module_and_decorator.split(':')
 
749
        # import decorator function
 
750
        decorator = importutils.import_class(decorator_name)
 
751
        __import__(module)
 
752
        # Retrieve module information using pyclbr
 
753
        module_data = pyclbr.readmodule_ex(module)
 
754
        for key in module_data.keys():
 
755
            # set the decorator for the class methods
 
756
            if isinstance(module_data[key], pyclbr.Class):
 
757
                clz = importutils.import_class("%s.%s" % (module, key))
 
758
                for method, func in inspect.getmembers(clz, inspect.ismethod):
 
759
                    setattr(clz, method,
 
760
                        decorator("%s.%s.%s" % (module, key, method), func))
 
761
            # set the decorator for the function
 
762
            if isinstance(module_data[key], pyclbr.Function):
 
763
                func = importutils.import_class("%s.%s" % (module, key))
 
764
                setattr(sys.modules[module], key,
 
765
                    decorator("%s.%s" % (module, key), func))
 
766
 
 
767
 
 
768
def convert_to_list_dict(lst, label):
 
769
    """Convert a value or list into a list of dicts"""
 
770
    if not lst:
 
771
        return None
 
772
    if not isinstance(lst, list):
 
773
        lst = [lst]
 
774
    return [{label: x} for x in lst]
 
775
 
 
776
 
 
777
def timefunc(func):
 
778
    """Decorator that logs how long a particular function took to execute"""
 
779
    @functools.wraps(func)
 
780
    def inner(*args, **kwargs):
 
781
        start_time = time.time()
 
782
        try:
 
783
            return func(*args, **kwargs)
 
784
        finally:
 
785
            total_time = time.time() - start_time
 
786
            LOG.debug(_("timefunc: '%(name)s' took %(total_time).2f secs") %
 
787
                      dict(name=func.__name__, total_time=total_time))
 
788
    return inner
 
789
 
 
790
 
 
791
def generate_glance_url():
 
792
    """Generate the URL to glance."""
 
793
    # TODO(jk0): This will eventually need to take SSL into consideration
 
794
    # when supported in glance.
 
795
    return "http://%s:%d" % (FLAGS.glance_host, FLAGS.glance_port)
 
796
 
 
797
 
 
798
@contextlib.contextmanager
 
799
def logging_error(message):
 
800
    """Catches exception, write message to the log, re-raise.
 
801
    This is a common refinement of save_and_reraise that writes a specific
 
802
    message to the log.
 
803
    """
 
804
    try:
 
805
        yield
 
806
    except Exception as error:
 
807
        with excutils.save_and_reraise_exception():
 
808
            LOG.exception(message)
 
809
 
 
810
 
 
811
@contextlib.contextmanager
 
812
def remove_path_on_error(path):
 
813
    """Protect code that wants to operate on PATH atomically.
 
814
    Any exception will cause PATH to be removed.
 
815
    """
 
816
    try:
 
817
        yield
 
818
    except Exception:
 
819
        with excutils.save_and_reraise_exception():
 
820
            delete_if_exists(path)
 
821
 
 
822
 
 
823
def make_dev_path(dev, partition=None, base='/dev'):
 
824
    """Return a path to a particular device.
 
825
 
 
826
    >>> make_dev_path('xvdc')
 
827
    /dev/xvdc
 
828
 
 
829
    >>> make_dev_path('xvdc', 1)
 
830
    /dev/xvdc1
 
831
    """
 
832
    path = os.path.join(base, dev)
 
833
    if partition:
 
834
        path += str(partition)
 
835
    return path
 
836
 
 
837
 
 
838
def total_seconds(td):
 
839
    """Local total_seconds implementation for compatibility with python 2.6"""
 
840
    if hasattr(td, 'total_seconds'):
 
841
        return td.total_seconds()
 
842
    else:
 
843
        return ((td.days * 86400 + td.seconds) * 10 ** 6 +
 
844
                td.microseconds) / 10.0 ** 6
 
845
 
 
846
 
 
847
def sanitize_hostname(hostname):
 
848
    """Return a hostname which conforms to RFC-952 and RFC-1123 specs."""
 
849
    if isinstance(hostname, unicode):
 
850
        hostname = hostname.encode('latin-1', 'ignore')
 
851
 
 
852
    hostname = re.sub('[ _]', '-', hostname)
 
853
    hostname = re.sub('[^\w.-]+', '', hostname)
 
854
    hostname = hostname.lower()
 
855
    hostname = hostname.strip('.-')
 
856
 
 
857
    return hostname
 
858
 
 
859
 
 
860
def read_cached_file(filename, cache_info, reload_func=None):
 
861
    """Read from a file if it has been modified.
 
862
 
 
863
    :param cache_info: dictionary to hold opaque cache.
 
864
    :param reload_func: optional function to be called with data when
 
865
                        file is reloaded due to a modification.
 
866
 
 
867
    :returns: data from file
 
868
 
 
869
    """
 
870
    mtime = os.path.getmtime(filename)
 
871
    if not cache_info or mtime != cache_info.get('mtime'):
 
872
        with open(filename) as fap:
 
873
            cache_info['data'] = fap.read()
 
874
        cache_info['mtime'] = mtime
 
875
        if reload_func:
 
876
            reload_func(cache_info['data'])
 
877
    return cache_info['data']
 
878
 
 
879
 
 
880
def file_open(*args, **kwargs):
 
881
    """Open file
 
882
 
 
883
    see built-in file() documentation for more details
 
884
 
 
885
    Note: The reason this is kept in a separate module is to easily
 
886
          be able to provide a stub module that doesn't alter system
 
887
          state at all (for unit tests)
 
888
    """
 
889
    return file(*args, **kwargs)
 
890
 
 
891
 
 
892
def hash_file(file_like_object):
 
893
    """Generate a hash for the contents of a file."""
 
894
    checksum = hashlib.sha1()
 
895
    any(map(checksum.update, iter(lambda: file_like_object.read(32768), '')))
 
896
    return checksum.hexdigest()
 
897
 
 
898
 
 
899
@contextlib.contextmanager
 
900
def temporary_mutation(obj, **kwargs):
 
901
    """Temporarily set the attr on a particular object to a given value then
 
902
    revert when finished.
 
903
 
 
904
    One use of this is to temporarily set the read_deleted flag on a context
 
905
    object:
 
906
 
 
907
        with temporary_mutation(context, read_deleted="yes"):
 
908
            do_something_that_needed_deleted_objects()
 
909
    """
 
910
    NOT_PRESENT = object()
 
911
 
 
912
    old_values = {}
 
913
    for attr, new_value in kwargs.items():
 
914
        old_values[attr] = getattr(obj, attr, NOT_PRESENT)
 
915
        setattr(obj, attr, new_value)
 
916
 
 
917
    try:
 
918
        yield
 
919
    finally:
 
920
        for attr, old_value in old_values.items():
 
921
            if old_value is NOT_PRESENT:
 
922
                del obj[attr]
 
923
            else:
 
924
                setattr(obj, attr, old_value)
 
925
 
 
926
 
 
927
def service_is_up(service):
 
928
    """Check whether a service is up based on last heartbeat."""
 
929
    last_heartbeat = service['updated_at'] or service['created_at']
 
930
    # Timestamps in DB are UTC.
 
931
    elapsed = total_seconds(timeutils.utcnow() - last_heartbeat)
 
932
    return abs(elapsed) <= FLAGS.service_down_time
 
933
 
 
934
 
 
935
def generate_mac_address():
 
936
    """Generate an Ethernet MAC address."""
 
937
    # NOTE(vish): We would prefer to use 0xfe here to ensure that linux
 
938
    #             bridge mac addresses don't change, but it appears to
 
939
    #             conflict with libvirt, so we use the next highest octet
 
940
    #             that has the unicast and locally administered bits set
 
941
    #             properly: 0xfa.
 
942
    #             Discussion: https://bugs.launchpad.net/cinder/+bug/921838
 
943
    mac = [0xfa, 0x16, 0x3e,
 
944
           random.randint(0x00, 0x7f),
 
945
           random.randint(0x00, 0xff),
 
946
           random.randint(0x00, 0xff)]
 
947
    return ':'.join(map(lambda x: "%02x" % x, mac))
 
948
 
 
949
 
 
950
def read_file_as_root(file_path):
 
951
    """Secure helper to read file as root."""
 
952
    try:
 
953
        out, _err = execute('cat', file_path, run_as_root=True)
 
954
        return out
 
955
    except exception.ProcessExecutionError:
 
956
        raise exception.FileNotFound(file_path=file_path)
 
957
 
 
958
 
 
959
@contextlib.contextmanager
 
960
def temporary_chown(path, owner_uid=None):
 
961
    """Temporarily chown a path.
 
962
 
 
963
    :params owner_uid: UID of temporary owner (defaults to current user)
 
964
    """
 
965
    if owner_uid is None:
 
966
        owner_uid = os.getuid()
 
967
 
 
968
    orig_uid = os.stat(path).st_uid
 
969
 
 
970
    if orig_uid != owner_uid:
 
971
        execute('chown', owner_uid, path, run_as_root=True)
 
972
    try:
 
973
        yield
 
974
    finally:
 
975
        if orig_uid != owner_uid:
 
976
            execute('chown', orig_uid, path, run_as_root=True)
 
977
 
 
978
 
 
979
@contextlib.contextmanager
 
980
def tempdir(**kwargs):
 
981
    tmpdir = tempfile.mkdtemp(**kwargs)
 
982
    try:
 
983
        yield tmpdir
 
984
    finally:
 
985
        try:
 
986
            shutil.rmtree(tmpdir)
 
987
        except OSError, e:
 
988
            LOG.debug(_('Could not remove tmpdir: %s'), str(e))
 
989
 
 
990
 
 
991
def strcmp_const_time(s1, s2):
 
992
    """Constant-time string comparison.
 
993
 
 
994
    :params s1: the first string
 
995
    :params s2: the second string
 
996
 
 
997
    :return: True if the strings are equal.
 
998
 
 
999
    This function takes two strings and compares them.  It is intended to be
 
1000
    used when doing a comparison for authentication purposes to help guard
 
1001
    against timing attacks.
 
1002
    """
 
1003
    if len(s1) != len(s2):
 
1004
        return False
 
1005
    result = 0
 
1006
    for (a, b) in zip(s1, s2):
 
1007
        result |= ord(a) ^ ord(b)
 
1008
    return result == 0
 
1009
 
 
1010
 
 
1011
def walk_class_hierarchy(clazz, encountered=None):
 
1012
    """Walk class hierarchy, yielding most derived classes first"""
 
1013
    if not encountered:
 
1014
        encountered = []
 
1015
    for subclass in clazz.__subclasses__():
 
1016
        if subclass not in encountered:
 
1017
            encountered.append(subclass)
 
1018
            # drill down to leaves first
 
1019
            for subsubclass in walk_class_hierarchy(subclass, encountered):
 
1020
                yield subsubclass
 
1021
            yield subclass
 
1022
 
 
1023
 
 
1024
class UndoManager(object):
 
1025
    """Provides a mechanism to facilitate rolling back a series of actions
 
1026
    when an exception is raised.
 
1027
    """
 
1028
    def __init__(self):
 
1029
        self.undo_stack = []
 
1030
 
 
1031
    def undo_with(self, undo_func):
 
1032
        self.undo_stack.append(undo_func)
 
1033
 
 
1034
    def _rollback(self):
 
1035
        for undo_func in reversed(self.undo_stack):
 
1036
            undo_func()
 
1037
 
 
1038
    def rollback_and_reraise(self, msg=None, **kwargs):
 
1039
        """Rollback a series of actions then re-raise the exception.
 
1040
 
 
1041
        .. note:: (sirp) This should only be called within an
 
1042
                  exception handler.
 
1043
        """
 
1044
        with excutils.save_and_reraise_exception():
 
1045
            if msg:
 
1046
                LOG.exception(msg, **kwargs)
 
1047
 
 
1048
            self._rollback()
 
1049
 
 
1050
 
 
1051
def ensure_tree(path):
 
1052
    """Create a directory (and any ancestor directories required)
 
1053
 
 
1054
    :param path: Directory to create
 
1055
    """
 
1056
    try:
 
1057
        os.makedirs(path)
 
1058
    except OSError as exc:
 
1059
        if exc.errno == errno.EEXIST:
 
1060
            if not os.path.isdir(path):
 
1061
                raise
 
1062
        else:
 
1063
            raise