~ubuntu-branches/ubuntu/quantal/nova/quantal-proposed

« back to all changes in this revision

Viewing changes to nova/utils.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-08-16 14:04:11 UTC
  • mto: This revision was merged to the branch mainline in revision 84.
  • Revision ID: package-import@ubuntu.com-20120816140411-0mr4n241wmk30t9l
Tags: upstream-2012.2~f3
ImportĀ upstreamĀ versionĀ 2012.2~f3

Show diffs side-by-side

added added

removed removed

Lines of Context:
37
37
import tempfile
38
38
import threading
39
39
import time
40
 
import types
41
40
import uuid
 
41
import weakref
42
42
from xml.sax import saxutils
43
43
 
44
44
from eventlet import corolocal
46
46
from eventlet.green import subprocess
47
47
from eventlet import greenthread
48
48
from eventlet import semaphore
49
 
import lockfile
50
49
import netaddr
51
50
 
 
51
from nova.common import deprecated
52
52
from nova import exception
53
53
from nova import flags
54
54
from nova.openstack.common import cfg
118
118
    """Helper method to execute command with optional retry.
119
119
 
120
120
    If you add a run_as_root=True command, don't forget to add the
121
 
    corresponding filter to nova.rootwrap !
 
121
    corresponding filter to etc/nova/rootwrap.d !
122
122
 
123
123
    :param cmd:                Passed to subprocess.Popen.
124
124
    :param process_input:      Send to opened process.
140
140
    :returns: a tuple, (stdout, stderr) from the spawned process, or None if
141
141
             the command fails.
142
142
    """
143
 
 
144
143
    process_input = kwargs.pop('process_input', None)
145
144
    check_exit_code = kwargs.pop('check_exit_code', [0])
146
145
    ignore_exit_code = False
159
158
                                        'to utils.execute: %r') % kwargs)
160
159
 
161
160
    if run_as_root:
162
 
        cmd = shlex.split(FLAGS.root_helper) + list(cmd)
 
161
 
 
162
        if FLAGS.rootwrap_config is None or FLAGS.root_helper != 'sudo':
 
163
            deprecated.warn(_('The root_helper option (which lets you specify '
 
164
                              'a root wrapper different from nova-rootwrap, '
 
165
                              'and defaults to using sudo) is now deprecated. '
 
166
                              'You should use the rootwrap_config option '
 
167
                              'instead.'))
 
168
 
 
169
        if (FLAGS.rootwrap_config is not None):
 
170
            cmd = ['sudo', 'nova-rootwrap', FLAGS.rootwrap_config] + list(cmd)
 
171
        else:
 
172
            cmd = shlex.split(FLAGS.root_helper) + list(cmd)
163
173
    cmd = map(str, cmd)
164
174
 
165
175
    while attempts > 0:
223
233
        failed = False
224
234
    except exception.ProcessExecutionError, exn:
225
235
        out, err = '', str(exn)
226
 
        LOG.debug(err)
227
236
        failed = True
228
237
 
229
238
    if not failed and discard_warnings and err:
230
239
        # Handle commands that output to stderr but otherwise succeed
231
 
        LOG.debug(err)
232
240
        err = ''
233
241
 
234
242
    return out, err
299
307
                           'ABCDEFGHJKLMNPQRSTUVWXYZ')  # Removed: I, O
300
308
 
301
309
 
302
 
def last_completed_audit_period(unit=None):
 
310
def last_completed_audit_period(unit=None, before=None):
303
311
    """This method gives you the most recently *completed* audit period.
304
312
 
305
313
    arguments:
311
319
                    like so:  'day@18'  This will begin the period at 18:00
312
320
                    UTC.  'month@15' starts a monthly period on the 15th,
313
321
                    and year@3 begins a yearly one on March 1st.
 
322
            before: Give the audit period most recently completed before
 
323
                    <timestamp>. Defaults to now.
314
324
 
315
325
 
316
326
    returns:  2 tuple of datetimes (begin, end)
324
334
        unit, offset = unit.split("@", 1)
325
335
        offset = int(offset)
326
336
 
327
 
    rightnow = timeutils.utcnow()
 
337
    if before is not None:
 
338
        rightnow = before
 
339
    else:
 
340
        rightnow = timeutils.utcnow()
328
341
    if unit not in ('month', 'day', 'year', 'hour'):
329
342
        raise ValueError('Time period must be hour, day, month or year')
330
343
    if unit == 'month':
569
582
    return value
570
583
 
571
584
 
572
 
class GreenLockFile(lockfile.FileLock):
573
 
    """Implementation of lockfile that allows for a lock per greenthread.
574
 
 
575
 
    Simply implements lockfile:LockBase init with an addiontall suffix
576
 
    on the unique name of the greenthread identifier
 
585
class _InterProcessLock(object):
 
586
    """Lock implementation which allows multiple locks, working around
 
587
    issues like bugs.debian.org/cgi-bin/bugreport.cgi?bug=632857 and does
 
588
    not require any cleanup. Since the lock is always held on a file
 
589
    descriptor rather than outside of the process, the lock gets dropped
 
590
    automatically if the process crashes, even if __exit__ is not executed.
 
591
 
 
592
    There are no guarantees regarding usage by multiple green threads in a
 
593
    single process here. This lock works only between processes. Exclusive
 
594
    access between local threads should be achieved using the semaphores
 
595
    in the @synchronized decorator.
 
596
 
 
597
    Note these locks are released when the descriptor is closed, so it's not
 
598
    safe to close the file descriptor while another green thread holds the
 
599
    lock. Just opening and closing the lock file can break synchronisation,
 
600
    so lock files must be accessed only using this abstraction.
577
601
    """
578
 
    def __init__(self, path, threaded=True):
579
 
        self.path = path
580
 
        self.lock_file = os.path.abspath(path) + ".lock"
581
 
        self.hostname = socket.gethostname()
582
 
        self.pid = os.getpid()
583
 
        if threaded:
584
 
            t = threading.current_thread()
585
 
            # Thread objects in Python 2.4 and earlier do not have ident
586
 
            # attrs.  Worm around that.
587
 
            ident = getattr(t, "ident", hash(t)) or hash(t)
588
 
            gident = corolocal.get_ident()
589
 
            self.tname = "-%x-%x" % (ident & 0xffffffff, gident & 0xffffffff)
590
 
        else:
591
 
            self.tname = ""
592
 
        dirname = os.path.dirname(self.lock_file)
593
 
        self.unique_name = os.path.join(dirname,
594
 
                                        "%s%s.%s" % (self.hostname,
595
 
                                                     self.tname,
596
 
                                                     self.pid))
597
 
 
598
 
 
599
 
_semaphores = {}
 
602
 
 
603
    def __init__(self, name):
 
604
        self.lockfile = None
 
605
        self.fname = name
 
606
 
 
607
    def __enter__(self):
 
608
        self.lockfile = open(self.fname, 'w')
 
609
 
 
610
        while True:
 
611
            try:
 
612
                # Using non-blocking locks since green threads are not
 
613
                # patched to deal with blocking locking calls.
 
614
                # Also upon reading the MSDN docs for locking(), it seems
 
615
                # to have a laughable 10 attempts "blocking" mechanism.
 
616
                self.trylock()
 
617
                return self
 
618
            except IOError, e:
 
619
                if e.errno in (errno.EACCES, errno.EAGAIN):
 
620
                    # external locks synchronise things like iptables
 
621
                    # updates - give it some time to prevent busy spinning
 
622
                    time.sleep(0.01)
 
623
                else:
 
624
                    raise
 
625
 
 
626
    def __exit__(self, exc_type, exc_val, exc_tb):
 
627
        try:
 
628
            self.unlock()
 
629
            self.lockfile.close()
 
630
        except IOError:
 
631
            LOG.exception(_("Could not release the aquired lock `%s`")
 
632
                             % self.fname)
 
633
 
 
634
    def trylock(self):
 
635
        raise NotImplementedError()
 
636
 
 
637
    def unlock(self):
 
638
        raise NotImplementedError()
 
639
 
 
640
 
 
641
class _WindowsLock(_InterProcessLock):
 
642
    def trylock(self):
 
643
        msvcrt.locking(self.lockfile, msvcrt.LK_NBLCK, 1)
 
644
 
 
645
    def unlock(self):
 
646
        msvcrt.locking(self.lockfile, msvcrt.LK_UNLCK, 1)
 
647
 
 
648
 
 
649
class _PosixLock(_InterProcessLock):
 
650
    def trylock(self):
 
651
        fcntl.lockf(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
652
 
 
653
    def unlock(self):
 
654
        fcntl.lockf(self.lockfile, fcntl.LOCK_UN)
 
655
 
 
656
 
 
657
if os.name == 'nt':
 
658
    import msvcrt
 
659
    InterProcessLock = _WindowsLock
 
660
else:
 
661
    import fcntl
 
662
    InterProcessLock = _PosixLock
 
663
 
 
664
_semaphores = weakref.WeakValueDictionary()
600
665
 
601
666
 
602
667
def synchronized(name, external=False):
626
691
    multiple processes. This means that if two different workers both run a
627
692
    a method decorated with @synchronized('mylock', external=True), only one
628
693
    of them will execute at a time.
629
 
 
630
 
    Important limitation: you can only have one external lock running per
631
 
    thread at a time. For example the following will fail:
632
 
 
633
 
        @utils.synchronized('testlock1', external=True)
634
 
        def outer_lock():
635
 
 
636
 
            @utils.synchronized('testlock2', external=True)
637
 
            def inner_lock():
638
 
                pass
639
 
            inner_lock()
640
 
 
641
 
        outer_lock()
642
 
 
643
694
    """
644
695
 
645
696
    def wrap(f):
648
699
            # NOTE(soren): If we ever go natively threaded, this will be racy.
649
700
            #              See http://stackoverflow.com/questions/5390569/dyn
650
701
            #              amically-allocating-and-destroying-mutexes
 
702
            sem = _semaphores.get(name, semaphore.Semaphore())
651
703
            if name not in _semaphores:
652
 
                _semaphores[name] = semaphore.Semaphore()
653
 
            sem = _semaphores[name]
 
704
                # this check is not racy - we're already holding ref locally
 
705
                # so GC won't remove the item and there was no IO switch
 
706
                # (only valid in greenthreads)
 
707
                _semaphores[name] = sem
 
708
 
654
709
            LOG.debug(_('Attempting to grab semaphore "%(lock)s" for method '
655
710
                        '"%(method)s"...'), {'lock': name,
656
711
                                             'method': f.__name__})
664
719
                              {'lock': name, 'method': f.__name__})
665
720
                    lock_file_path = os.path.join(FLAGS.lock_path,
666
721
                                                  'nova-%s' % name)
667
 
                    lock = GreenLockFile(lock_file_path)
 
722
                    lock = InterProcessLock(lock_file_path)
668
723
                    with lock:
669
724
                        LOG.debug(_('Got file lock "%(lock)s" for '
670
725
                                    'method "%(method)s"...'),
673
728
                else:
674
729
                    retval = f(*args, **kwargs)
675
730
 
676
 
            # If no-one else is waiting for it, delete it.
677
 
            # See note about possible raciness above.
678
 
            if not sem.balance < 1:
679
 
                del _semaphores[name]
680
 
 
681
731
            return retval
682
732
        return inner
683
733
    return wrap
684
734
 
685
735
 
686
 
def cleanup_file_locks():
687
 
    """clean up stale locks left behind by process failures
688
 
 
689
 
    The lockfile module, used by @synchronized, can leave stale lockfiles
690
 
    behind after process failure. These locks can cause process hangs
691
 
    at startup, when a process deadlocks on a lock which will never
692
 
    be unlocked.
693
 
 
694
 
    Intended to be called at service startup.
695
 
 
696
 
    """
697
 
 
698
 
    # NOTE(mikeyp) this routine incorporates some internal knowledge
699
 
    #              from the lockfile module, and this logic really
700
 
    #              should be part of that module.
701
 
    #
702
 
    # cleanup logic:
703
 
    # 1) look for the lockfile modules's 'sentinel' files, of the form
704
 
    #    hostname.[thread-.*]-pid, extract the pid.
705
 
    #    if pid doesn't match a running process, delete the file since
706
 
    #    it's from a dead process.
707
 
    # 2) check for the actual lockfiles. if lockfile exists with linkcount
708
 
    #    of 1, it's bogus, so delete it. A link count >= 2 indicates that
709
 
    #    there are probably sentinels still linked to it from active
710
 
    #    processes.  This check isn't perfect, but there is no way to
711
 
    #    reliably tell which sentinels refer to which lock in the
712
 
    #    lockfile implementation.
713
 
 
714
 
    if FLAGS.disable_process_locking:
715
 
        return
716
 
 
717
 
    hostname = socket.gethostname()
718
 
    sentinel_re = hostname + r'\..*-(\d+$)'
719
 
    lockfile_re = r'nova-.*\.lock'
720
 
    files = os.listdir(FLAGS.lock_path)
721
 
 
722
 
    # cleanup sentinels
723
 
    for filename in files:
724
 
        match = re.match(sentinel_re, filename)
725
 
        if match is None:
726
 
            continue
727
 
        pid = match.group(1)
728
 
        LOG.debug(_('Found sentinel %(filename)s for pid %(pid)s'),
729
 
                  {'filename': filename, 'pid': pid})
730
 
        try:
731
 
            os.kill(int(pid), 0)
732
 
        except OSError, e:
733
 
            # PID wasn't found
734
 
            delete_if_exists(os.path.join(FLAGS.lock_path, filename))
735
 
            LOG.debug(_('Cleaned sentinel %(filename)s for pid %(pid)s'),
736
 
                      {'filename': filename, 'pid': pid})
737
 
 
738
 
    # cleanup lock files
739
 
    for filename in files:
740
 
        match = re.match(lockfile_re, filename)
741
 
        if match is None:
742
 
            continue
743
 
        try:
744
 
            stat_info = os.stat(os.path.join(FLAGS.lock_path, filename))
745
 
        except OSError as e:
746
 
            if e.errno == errno.ENOENT:
747
 
                continue
748
 
            else:
749
 
                raise
750
 
        LOG.debug(_('Found lockfile %(file)s with link count %(count)d'),
751
 
                  {'file': filename, 'count': stat_info.st_nlink})
752
 
        if stat_info.st_nlink == 1:
753
 
            delete_if_exists(os.path.join(FLAGS.lock_path, filename))
754
 
            LOG.debug(_('Cleaned lockfile %(file)s with link count %(count)d'),
755
 
                      {'file': filename, 'count': stat_info.st_nlink})
756
 
 
757
 
 
758
736
def delete_if_exists(pathname):
759
737
    """delete a file, but ignore file not found error"""
760
738
 
856
834
    return subset
857
835
 
858
836
 
 
837
def diff_dict(orig, new):
 
838
    """
 
839
    Return a dict describing how to change orig to new.  The keys
 
840
    correspond to values that have changed; the value will be a list
 
841
    of one or two elements.  The first element of the list will be
 
842
    either '+' or '-', indicating whether the key was updated or
 
843
    deleted; if the key was updated, the list will contain a second
 
844
    element, giving the updated value.
 
845
    """
 
846
    # Figure out what keys went away
 
847
    result = dict((k, ['-']) for k in set(orig.keys()) - set(new.keys()))
 
848
    # Compute the updates
 
849
    for key, value in new.items():
 
850
        if key not in orig or value != orig[key]:
 
851
            result[key] = ['+', value]
 
852
    return result
 
853
 
 
854
 
859
855
def check_isinstance(obj, cls):
860
856
    """Checks that obj is of type cls, and lets PyLint infer types."""
861
857
    if isinstance(obj, cls):
1108
1104
def hash_file(file_like_object):
1109
1105
    """Generate a hash for the contents of a file."""
1110
1106
    checksum = hashlib.sha1()
1111
 
    any(map(checksum.update, iter(lambda: file_like_object.read(32768), '')))
 
1107
    for chunk in iter(lambda: file_like_object.read(32768), b''):
 
1108
        checksum.update(chunk)
1112
1109
    return checksum.hexdigest()
1113
1110
 
1114
1111
 
1233
1230
    return arch
1234
1231
 
1235
1232
 
 
1233
def walk_class_hierarchy(clazz, encountered=None):
 
1234
    """Walk class hierarchy, yielding most derived classes first"""
 
1235
    if not encountered:
 
1236
        encountered = []
 
1237
    for subclass in clazz.__subclasses__():
 
1238
        if subclass not in encountered:
 
1239
            encountered.append(subclass)
 
1240
            # drill down to leaves first
 
1241
            for subsubclass in walk_class_hierarchy(subclass, encountered):
 
1242
                yield subsubclass
 
1243
            yield subclass
 
1244
 
 
1245
 
1236
1246
class UndoManager(object):
1237
1247
    """Provides a mechanism to facilitate rolling back a series of actions
1238
1248
    when an exception is raised.