1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2010 United States Government as represented by the
4
# Administrator of the National Aeronautics and Space Administration.
5
# Copyright 2011 Justin Santa Barbara
8
# Licensed under the Apache License, Version 2.0 (the "License"); you may
9
# not use this file except in compliance with the License. You may obtain
10
# a copy of the License at
12
# http://www.apache.org/licenses/LICENSE-2.0
14
# Unless required by applicable law or agreed to in writing, software
15
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
# License for the specific language governing permissions and limitations
20
"""Utilities and helper functions."""
42
from xml.sax import saxutils
44
from eventlet import event
45
from eventlet.green import subprocess
46
from eventlet import greenthread
47
from eventlet import semaphore
50
from nova.common import deprecated
51
from nova import exception
52
from nova import flags
53
from nova.openstack.common import cfg
54
from nova.openstack.common import excutils
55
from nova.openstack.common import importutils
56
from nova.openstack.common import log as logging
57
from nova.openstack.common import timeutils
60
LOG = logging.getLogger(__name__)
64
cfg.BoolOpt('disable_process_locking', default=False,
65
help='Whether to disable inter-process locks'))
68
def vpn_ping(address, port, timeout=0.05, session_id=None):
69
"""Sends a vpn negotiation packet and returns the server session.
71
Returns False on a failure. Basic packet structure is below.
73
Client packet (14 bytes)::
79
x = packet identifier 0x38
80
cli_id = 64 bit identifier
81
? = unknown, probably flags/padding
83
Server packet (26 bytes)::
86
+-+--------+-----+--------+----+
87
|x| srv_id |?????| cli_id |????|
88
+-+--------+-----+--------+----+
89
x = packet identifier 0x40
90
cli_id = 64 bit identifier
91
? = unknown, probably flags/padding
92
bit 9 was 1 and the rest were 0 in testing
95
if session_id is None:
96
session_id = random.randint(0, 0xffffffffffffffff)
97
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
98
data = struct.pack('!BQxxxxx', 0x38, session_id)
99
sock.sendto(data, (address, port))
100
sock.settimeout(timeout)
102
received = sock.recv(2048)
103
except socket.timeout:
107
fmt = '!BQxxxxxQxxxx'
108
if len(received) != struct.calcsize(fmt):
109
print struct.calcsize(fmt)
111
(identifier, server_sess, client_sess) = struct.unpack(fmt, received)
112
if identifier == 0x40 and client_sess == session_id:
116
def _subprocess_setup():
117
# Python installs a SIGPIPE handler by default. This is usually not what
118
# non-Python subprocesses expect.
119
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
122
def execute(*cmd, **kwargs):
123
"""Helper method to execute command with optional retry.
125
If you add a run_as_root=True command, don't forget to add the
126
corresponding filter to etc/nova/rootwrap.d !
128
:param cmd: Passed to subprocess.Popen.
129
:param process_input: Send to opened process.
130
:param check_exit_code: Single bool, int, or list of allowed exit
131
codes. Defaults to [0]. Raise
132
exception.ProcessExecutionError unless
133
program exits with one of these code.
134
:param delay_on_retry: True | False. Defaults to True. If set to
135
True, wait a short amount of time
137
:param attempts: How many times to retry cmd.
138
:param run_as_root: True | False. Defaults to False. If set to True,
139
the command is prefixed by the command specified
140
in the root_helper FLAG.
142
:raises exception.NovaException: on receiving unknown arguments
143
:raises exception.ProcessExecutionError:
145
:returns: a tuple, (stdout, stderr) from the spawned process, or None if
148
process_input = kwargs.pop('process_input', None)
149
check_exit_code = kwargs.pop('check_exit_code', [0])
150
ignore_exit_code = False
151
if isinstance(check_exit_code, bool):
152
ignore_exit_code = not check_exit_code
153
check_exit_code = [0]
154
elif isinstance(check_exit_code, int):
155
check_exit_code = [check_exit_code]
156
delay_on_retry = kwargs.pop('delay_on_retry', True)
157
attempts = kwargs.pop('attempts', 1)
158
run_as_root = kwargs.pop('run_as_root', False)
159
shell = kwargs.pop('shell', False)
162
raise exception.NovaException(_('Got unknown keyword args '
163
'to utils.execute: %r') % kwargs)
167
if FLAGS.rootwrap_config is None or FLAGS.root_helper != 'sudo':
168
deprecated.warn(_('The root_helper option (which lets you specify '
169
'a root wrapper different from nova-rootwrap, '
170
'and defaults to using sudo) is now deprecated. '
171
'You should use the rootwrap_config option '
174
if (FLAGS.rootwrap_config is not None):
175
cmd = ['sudo', 'nova-rootwrap', FLAGS.rootwrap_config] + list(cmd)
177
cmd = shlex.split(FLAGS.root_helper) + list(cmd)
183
LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd))
184
_PIPE = subprocess.PIPE # pylint: disable=E1101
185
obj = subprocess.Popen(cmd,
190
preexec_fn=_subprocess_setup,
193
if process_input is not None:
194
result = obj.communicate(process_input)
196
result = obj.communicate()
197
obj.stdin.close() # pylint: disable=E1101
198
_returncode = obj.returncode # pylint: disable=E1101
199
LOG.debug(_('Result was %s') % _returncode)
200
if not ignore_exit_code and _returncode not in check_exit_code:
201
(stdout, stderr) = result
202
raise exception.ProcessExecutionError(
203
exit_code=_returncode,
208
except exception.ProcessExecutionError:
212
LOG.debug(_('%r failed. Retrying.'), cmd)
214
greenthread.sleep(random.randint(20, 200) / 100.0)
216
# NOTE(termie): this appears to be necessary to let the subprocess
217
# call clean something up in between calls, without
218
# it two execute calls in a row hangs the second one
222
def trycmd(*args, **kwargs):
224
A wrapper around execute() to more easily handle warnings and errors.
226
Returns an (out, err) tuple of strings containing the output of
227
the command's stdout and stderr. If 'err' is not empty then the
228
command can be considered to have failed.
230
:discard_warnings True | False. Defaults to False. If set to True,
231
then for succeeding commands, stderr is cleared
234
discard_warnings = kwargs.pop('discard_warnings', False)
237
out, err = execute(*args, **kwargs)
239
except exception.ProcessExecutionError, exn:
240
out, err = '', str(exn)
243
if not failed and discard_warnings and err:
244
# Handle commands that output to stderr but otherwise succeed
250
def ssh_execute(ssh, cmd, process_input=None,
251
addl_env=None, check_exit_code=True):
252
LOG.debug(_('Running cmd (SSH): %s'), ' '.join(cmd))
254
raise exception.NovaException(_('Environment not supported over SSH'))
257
# This is (probably) fixable if we need it...
258
msg = _('process_input not supported over SSH')
259
raise exception.NovaException(msg)
261
stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd)
262
channel = stdout_stream.channel
264
#stdin.write('process_input would go here')
267
# NOTE(justinsb): This seems suspicious...
268
# ...other SSH clients have buffering issues with this approach
269
stdout = stdout_stream.read()
270
stderr = stderr_stream.read()
273
exit_status = channel.recv_exit_status()
275
# exit_status == -1 if no exit code was returned
276
if exit_status != -1:
277
LOG.debug(_('Result was %s') % exit_status)
278
if check_exit_code and exit_status != 0:
279
raise exception.ProcessExecutionError(exit_code=exit_status,
284
return (stdout, stderr)
289
return os.path.abspath(nova.__file__).split('nova/__init__.py')[0]
293
LOG.debug(_('debug in callback: %s'), arg)
297
def generate_uid(topic, size=8):
298
characters = '01234567890abcdefghijklmnopqrstuvwxyz'
299
choices = [random.choice(characters) for _x in xrange(size)]
300
return '%s-%s' % (topic, ''.join(choices))
303
# Default symbols to use for passwords. Avoids visually confusing characters.
305
DEFAULT_PASSWORD_SYMBOLS = ('23456789', # Removed: 0,1
306
'ABCDEFGHJKLMNPQRSTUVWXYZ', # Removed: I, O
307
'abcdefghijkmnopqrstuvwxyz') # Removed: l
311
EASIER_PASSWORD_SYMBOLS = ('23456789', # Removed: 0, 1
312
'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O
315
def last_completed_audit_period(unit=None, before=None):
316
"""This method gives you the most recently *completed* audit period.
319
units: string, one of 'hour', 'day', 'month', 'year'
320
Periods normally begin at the beginning (UTC) of the
321
period unit (So a 'day' period begins at midnight UTC,
322
a 'month' unit on the 1st, a 'year' on Jan, 1)
323
unit string may be appended with an optional offset
324
like so: 'day@18' This will begin the period at 18:00
325
UTC. 'month@15' starts a monthly period on the 15th,
326
and year@3 begins a yearly one on March 1st.
327
before: Give the audit period most recently completed before
328
<timestamp>. Defaults to now.
331
returns: 2 tuple of datetimes (begin, end)
332
The begin timestamp of this audit period is the same as the
333
end of the previous."""
335
unit = FLAGS.instance_usage_audit_period
339
unit, offset = unit.split("@", 1)
342
if before is not None:
345
rightnow = timeutils.utcnow()
346
if unit not in ('month', 'day', 'year', 'hour'):
347
raise ValueError('Time period must be hour, day, month or year')
351
end = datetime.datetime(day=offset,
352
month=rightnow.month,
356
if 1 >= rightnow.month:
358
month = 12 + (rightnow.month - 1)
360
month = rightnow.month - 1
361
end = datetime.datetime(day=offset,
367
month = 12 + (end.month - 1)
369
month = end.month - 1
370
begin = datetime.datetime(day=offset, month=month, year=year)
375
end = datetime.datetime(day=1, month=offset, year=rightnow.year)
377
end = datetime.datetime(day=1,
379
year=rightnow.year - 1)
380
begin = datetime.datetime(day=1,
382
year=rightnow.year - 2)
384
begin = datetime.datetime(day=1,
386
year=rightnow.year - 1)
389
end = datetime.datetime(hour=offset,
391
month=rightnow.month,
394
end = end - datetime.timedelta(days=1)
395
begin = end - datetime.timedelta(days=1)
398
end = rightnow.replace(minute=offset, second=0, microsecond=0)
400
end = end - datetime.timedelta(hours=1)
401
begin = end - datetime.timedelta(hours=1)
406
def generate_password(length=20, symbolgroups=DEFAULT_PASSWORD_SYMBOLS):
407
"""Generate a random password from the supplied symbol groups.
409
At least one symbol from each group will be included. Unpredictable
410
results if length is less than the number of symbol groups.
412
Believed to be reasonably secure (with a reasonable password length!)
415
r = random.SystemRandom()
417
# NOTE(jerdfelt): Some password policies require at least one character
418
# from each group of symbols, so start off with one random character
419
# from each symbol group
420
password = [r.choice(s) for s in symbolgroups]
421
# If length < len(symbolgroups), the leading characters will only
422
# be from the first length groups. Try our best to not be predictable
423
# by shuffling and then truncating.
425
password = password[:length]
426
length -= len(password)
428
# then fill with random characters from all symbol groups
429
symbols = ''.join(symbolgroups)
430
password.extend([r.choice(symbols) for _i in xrange(length)])
432
# finally shuffle to ensure first x characters aren't from a
436
return ''.join(password)
439
def last_octet(address):
440
return int(address.split('.')[-1])
443
def get_my_linklocal(interface):
445
if_str = execute('ip', '-f', 'inet6', '-o', 'addr', 'show', interface)
446
condition = '\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link'
447
links = [re.search(condition, x) for x in if_str[0].split('\n')]
448
address = [w.group(1) for w in links if w is not None]
449
if address[0] is not None:
452
msg = _('Link Local address is not found.:%s') % if_str
453
raise exception.NovaException(msg)
454
except Exception as ex:
455
msg = _("Couldn't get Link Local IP of %(interface)s"
456
" :%(ex)s") % locals()
457
raise exception.NovaException(msg)
460
def parse_mailmap(mailmap='.mailmap'):
462
if os.path.exists(mailmap):
463
fp = open(mailmap, 'r')
466
if not l.startswith('#') and ' ' in l:
467
canonical_email, alias = l.split(' ')
468
mapping[alias.lower()] = canonical_email.lower()
472
def str_dict_replace(s, mapping):
473
for s1, s2 in mapping.iteritems():
474
s = s.replace(s1, s2)
478
class LazyPluggable(object):
479
"""A pluggable backend loaded lazily based on some value."""
481
def __init__(self, pivot, **backends):
482
self.__backends = backends
484
self.__backend = None
486
def __get_backend(self):
487
if not self.__backend:
488
backend_name = FLAGS[self.__pivot]
489
if backend_name not in self.__backends:
490
msg = _('Invalid backend: %s') % backend_name
491
raise exception.NovaException(msg)
493
backend = self.__backends[backend_name]
494
if isinstance(backend, tuple):
496
fromlist = backend[1]
501
self.__backend = __import__(name, None, None, fromlist)
502
LOG.debug(_('backend %s'), self.__backend)
503
return self.__backend
505
def __getattr__(self, key):
506
backend = self.__get_backend()
507
return getattr(backend, key)
510
class LoopingCallDone(Exception):
511
"""Exception to break out and stop a LoopingCall.
513
The poll-function passed to LoopingCall can raise this exception to
514
break out of the loop normally. This is somewhat analogous to
517
An optional return-value can be included as the argument to the exception;
518
this return-value will be returned by LoopingCall.wait()
522
def __init__(self, retvalue=True):
523
""":param retvalue: Value that LoopingCall.wait() should return."""
524
self.retvalue = retvalue
527
class LoopingCall(object):
528
def __init__(self, f=None, *args, **kw):
532
self._running = False
534
def start(self, interval, initial_delay=None):
540
greenthread.sleep(initial_delay)
544
self.f(*self.args, **self.kw)
545
if not self._running:
547
greenthread.sleep(interval)
548
except LoopingCallDone, e:
550
done.send(e.retvalue)
552
LOG.exception(_('in looping call'))
553
done.send_exception(*sys.exc_info())
560
greenthread.spawn(_inner)
564
self._running = False
567
return self.done.wait()
570
def xhtml_escape(value):
571
"""Escapes a string so it is valid within XML or XHTML.
574
return saxutils.escape(value, {'"': '"', "'": '''})
578
"""Try to turn a string into utf-8 if possible.
580
Code is directly from the utf8 function in
581
http://github.com/facebook/tornado/blob/master/tornado/escape.py
584
if isinstance(value, unicode):
585
return value.encode('utf-8')
586
assert isinstance(value, str)
590
class _InterProcessLock(object):
591
"""Lock implementation which allows multiple locks, working around
592
issues like bugs.debian.org/cgi-bin/bugreport.cgi?bug=632857 and does
593
not require any cleanup. Since the lock is always held on a file
594
descriptor rather than outside of the process, the lock gets dropped
595
automatically if the process crashes, even if __exit__ is not executed.
597
There are no guarantees regarding usage by multiple green threads in a
598
single process here. This lock works only between processes. Exclusive
599
access between local threads should be achieved using the semaphores
600
in the @synchronized decorator.
602
Note these locks are released when the descriptor is closed, so it's not
603
safe to close the file descriptor while another green thread holds the
604
lock. Just opening and closing the lock file can break synchronisation,
605
so lock files must be accessed only using this abstraction.
608
def __init__(self, name):
613
self.lockfile = open(self.fname, 'w')
617
# Using non-blocking locks since green threads are not
618
# patched to deal with blocking locking calls.
619
# Also upon reading the MSDN docs for locking(), it seems
620
# to have a laughable 10 attempts "blocking" mechanism.
624
if e.errno in (errno.EACCES, errno.EAGAIN):
625
# external locks synchronise things like iptables
626
# updates - give it some time to prevent busy spinning
631
def __exit__(self, exc_type, exc_val, exc_tb):
634
self.lockfile.close()
636
LOG.exception(_("Could not release the acquired lock `%s`")
640
raise NotImplementedError()
643
raise NotImplementedError()
646
class _WindowsLock(_InterProcessLock):
648
msvcrt.locking(self.lockfile, msvcrt.LK_NBLCK, 1)
651
msvcrt.locking(self.lockfile, msvcrt.LK_UNLCK, 1)
654
class _PosixLock(_InterProcessLock):
656
fcntl.lockf(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
659
fcntl.lockf(self.lockfile, fcntl.LOCK_UN)
664
InterProcessLock = _WindowsLock
667
InterProcessLock = _PosixLock
669
_semaphores = weakref.WeakValueDictionary()
672
def synchronized(name, external=False, lock_path=None):
673
"""Synchronization decorator.
675
Decorating a method like so::
677
@synchronized('mylock')
678
def foo(self, *args):
681
ensures that only one thread will execute the bar method at a time.
683
Different methods can share the same lock::
685
@synchronized('mylock')
686
def foo(self, *args):
689
@synchronized('mylock')
690
def bar(self, *args):
693
This way only one of either foo or bar can be executing at a time.
695
The external keyword argument denotes whether this lock should work across
696
multiple processes. This means that if two different workers both run a
697
a method decorated with @synchronized('mylock', external=True), only one
698
of them will execute at a time.
700
The lock_path keyword argument is used to specify a special location for
701
external lock files to live. If nothing is set, then FLAGS.lock_path is
707
def inner(*args, **kwargs):
708
# NOTE(soren): If we ever go natively threaded, this will be racy.
709
# See http://stackoverflow.com/questions/5390569/dyn
710
# amically-allocating-and-destroying-mutexes
711
sem = _semaphores.get(name, semaphore.Semaphore())
712
if name not in _semaphores:
713
# this check is not racy - we're already holding ref locally
714
# so GC won't remove the item and there was no IO switch
715
# (only valid in greenthreads)
716
_semaphores[name] = sem
719
LOG.debug(_('Got semaphore "%(lock)s" for method '
720
'"%(method)s"...'), {'lock': name,
721
'method': f.__name__})
722
if external and not FLAGS.disable_process_locking:
723
LOG.debug(_('Attempting to grab file lock "%(lock)s" for '
724
'method "%(method)s"...'),
725
{'lock': name, 'method': f.__name__})
728
# We need a copy of lock_path because it is non-local
729
local_lock_path = lock_path
730
if not local_lock_path:
731
local_lock_path = FLAGS.lock_path
733
if not local_lock_path:
735
local_lock_path = tempfile.mkdtemp()
737
if not os.path.exists(local_lock_path):
739
ensure_tree(local_lock_path)
741
# NOTE(mikal): the lock name cannot contain directory
743
safe_name = name.replace(os.sep, '_')
744
lock_file_path = os.path.join(local_lock_path,
745
'nova-%s' % safe_name)
747
lock = InterProcessLock(lock_file_path)
749
LOG.debug(_('Got file lock "%(lock)s" for '
750
'method "%(method)s"...'),
751
{'lock': name, 'method': f.__name__})
752
retval = f(*args, **kwargs)
754
# NOTE(vish): This removes the tempdir if we needed
755
# to create one. This is used to cleanup
756
# the locks left behind by unit tests.
758
shutil.rmtree(local_lock_path)
760
retval = f(*args, **kwargs)
767
def delete_if_exists(pathname):
768
"""delete a file, but ignore file not found error"""
773
if e.errno == errno.ENOENT:
779
def get_from_path(items, path):
780
"""Returns a list of items matching the specified path.
782
Takes an XPath-like expression e.g. prop1/prop2/prop3, and for each item
783
in items, looks up items[prop1][prop2][prop3]. Like XPath, if any of the
784
intermediate results are lists it will treat each list item individually.
785
A 'None' in items or any child expressions will be ignored, this function
786
will not throw because of None (anywhere) in items. The returned list
787
will contain no None values.
791
raise exception.NovaException('Invalid mini_xpath')
793
(first_token, sep, remainder) = path.partition('/')
795
if first_token == '':
796
raise exception.NovaException('Invalid mini_xpath')
803
if not isinstance(items, list):
804
# Wrap single objects in a list
810
get_method = getattr(item, 'get', None)
811
if get_method is None:
813
child = get_method(first_token)
816
if isinstance(child, list):
817
# Flatten intermediate lists
821
results.append(child)
827
return get_from_path(results, remainder)
830
def flatten_dict(dict_, flattened=None):
831
"""Recursively flatten a nested dictionary."""
832
flattened = flattened or {}
833
for key, value in dict_.iteritems():
834
if hasattr(value, 'iteritems'):
835
flatten_dict(value, flattened)
837
flattened[key] = value
841
def partition_dict(dict_, keys):
842
"""Return two dicts, one with `keys` the other with everything else."""
845
for key, value in dict_.iteritems():
847
intersection[key] = value
849
difference[key] = value
850
return intersection, difference
853
def map_dict_keys(dict_, key_map):
854
"""Return a dict in which the dictionaries keys are mapped to new keys."""
856
for key, value in dict_.iteritems():
857
mapped_key = key_map[key] if key in key_map else key
858
mapped[mapped_key] = value
862
def subset_dict(dict_, keys):
863
"""Return a dict that only contains a subset of keys."""
864
subset = partition_dict(dict_, keys)[0]
868
def diff_dict(orig, new):
870
Return a dict describing how to change orig to new. The keys
871
correspond to values that have changed; the value will be a list
872
of one or two elements. The first element of the list will be
873
either '+' or '-', indicating whether the key was updated or
874
deleted; if the key was updated, the list will contain a second
875
element, giving the updated value.
877
# Figure out what keys went away
878
result = dict((k, ['-']) for k in set(orig.keys()) - set(new.keys()))
879
# Compute the updates
880
for key, value in new.items():
881
if key not in orig or value != orig[key]:
882
result[key] = ['+', value]
886
def check_isinstance(obj, cls):
887
"""Checks that obj is of type cls, and lets PyLint infer types."""
888
if isinstance(obj, cls):
890
raise Exception(_('Expected object of type: %s') % (str(cls)))
893
def parse_server_string(server_str):
895
Parses the given server_string and returns a list of host and port.
896
If it's not a combination of host part and port, the port element
897
is a null string. If the input is invalid expression, return a null
901
# First of all, exclude pure IPv6 address (w/o port).
902
if netaddr.valid_ipv6(server_str):
903
return (server_str, '')
905
# Next, check if this is IPv6 address with a port number combination.
906
if server_str.find("]:") != -1:
907
(address, port) = server_str.replace('[', '', 1).split(']:')
908
return (address, port)
910
# Third, check if this is a combination of an address and a port
911
if server_str.find(':') == -1:
912
return (server_str, '')
914
# This must be a combination of an address and a port
915
(address, port) = server_str.split(':')
916
return (address, port)
919
LOG.error(_('Invalid server_string: %s'), server_str)
927
def is_uuid_like(val):
928
"""For our purposes, a UUID is a string in canonical form:
930
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
935
except (TypeError, ValueError, AttributeError):
939
def bool_from_str(val):
940
"""Convert a string representation of a bool into a bool value"""
945
return True if int(val) else False
947
return val.lower() == 'true' or \
948
val.lower() == 'yes' or \
952
def is_valid_boolstr(val):
953
"""Check if the provided string is a valid bool string or not. """
954
val = str(val).lower()
955
return val == 'true' or val == 'false' or \
956
val == 'yes' or val == 'no' or \
957
val == 'y' or val == 'n' or \
958
val == '1' or val == '0'
961
def is_valid_ipv4(address):
962
"""valid the address strictly as per format xxx.xxx.xxx.xxx.
963
where xxx is a value between 0 and 255.
965
parts = address.split(".")
970
if not 0 <= int(item) <= 255:
977
def is_valid_cidr(address):
978
"""Check if the provided ipv4 or ipv6 address is a valid
979
CIDR address or not"""
981
# Validate the correct CIDR Address
982
netaddr.IPNetwork(address)
983
except netaddr.core.AddrFormatError:
985
except UnboundLocalError:
986
# NOTE(MotoKen): work around bug in netaddr 0.7.5 (see detail in
987
# https://github.com/drkjam/netaddr/issues/2)
990
# Prior validation partially verify /xx part
992
ip_segment = address.split('/')
994
if (len(ip_segment) <= 1 or
995
ip_segment[1] == ''):
1002
""" If the Flags.monkey_patch set as True,
1003
this function patches a decorator
1004
for all functions in specified modules.
1005
You can set decorators for each modules
1006
using FLAGS.monkey_patch_modules.
1007
The format is "Module path:Decorator function".
1008
Example: 'nova.api.ec2.cloud:nova.notifier.api.notify_decorator'
1010
Parameters of the decorator is as follows.
1011
(See nova.notifier.api.notify_decorator)
1013
name - name of the function
1014
function - object of the function
1016
# If FLAGS.monkey_patch is not True, this function do nothing.
1017
if not FLAGS.monkey_patch:
1019
# Get list of modules and decorators
1020
for module_and_decorator in FLAGS.monkey_patch_modules:
1021
module, decorator_name = module_and_decorator.split(':')
1022
# import decorator function
1023
decorator = importutils.import_class(decorator_name)
1025
# Retrieve module information using pyclbr
1026
module_data = pyclbr.readmodule_ex(module)
1027
for key in module_data.keys():
1028
# set the decorator for the class methods
1029
if isinstance(module_data[key], pyclbr.Class):
1030
clz = importutils.import_class("%s.%s" % (module, key))
1031
for method, func in inspect.getmembers(clz, inspect.ismethod):
1032
setattr(clz, method,
1033
decorator("%s.%s.%s" % (module, key, method), func))
1034
# set the decorator for the function
1035
if isinstance(module_data[key], pyclbr.Function):
1036
func = importutils.import_class("%s.%s" % (module, key))
1037
setattr(sys.modules[module], key,
1038
decorator("%s.%s" % (module, key), func))
1041
def convert_to_list_dict(lst, label):
1042
"""Convert a value or list into a list of dicts"""
1045
if not isinstance(lst, list):
1047
return [{label: x} for x in lst]
1051
"""Decorator that logs how long a particular function took to execute"""
1052
@functools.wraps(func)
1053
def inner(*args, **kwargs):
1054
start_time = time.time()
1056
return func(*args, **kwargs)
1058
total_time = time.time() - start_time
1059
LOG.debug(_("timefunc: '%(name)s' took %(total_time).2f secs") %
1060
dict(name=func.__name__, total_time=total_time))
1064
def generate_glance_url():
1065
"""Generate the URL to glance."""
1066
# TODO(jk0): This will eventually need to take SSL into consideration
1067
# when supported in glance.
1068
return "http://%s:%d" % (FLAGS.glance_host, FLAGS.glance_port)
1071
def generate_image_url(image_ref):
1072
"""Generate an image URL from an image_ref."""
1073
return "%s/images/%s" % (generate_glance_url(), image_ref)
1076
@contextlib.contextmanager
1077
def remove_path_on_error(path):
1078
"""Protect code that wants to operate on PATH atomically.
1079
Any exception will cause PATH to be removed.
1084
with excutils.save_and_reraise_exception():
1085
delete_if_exists(path)
1088
def make_dev_path(dev, partition=None, base='/dev'):
1089
"""Return a path to a particular device.
1091
>>> make_dev_path('xvdc')
1094
>>> make_dev_path('xvdc', 1)
1097
path = os.path.join(base, dev)
1099
path += str(partition)
1103
def total_seconds(td):
1104
"""Local total_seconds implementation for compatibility with python 2.6"""
1105
if hasattr(td, 'total_seconds'):
1106
return td.total_seconds()
1108
return ((td.days * 86400 + td.seconds) * 10 ** 6 +
1109
td.microseconds) / 10.0 ** 6
1112
def sanitize_hostname(hostname):
1113
"""Return a hostname which conforms to RFC-952 and RFC-1123 specs."""
1114
if isinstance(hostname, unicode):
1115
hostname = hostname.encode('latin-1', 'ignore')
1117
hostname = re.sub('[ _]', '-', hostname)
1118
hostname = re.sub('[^\w.-]+', '', hostname)
1119
hostname = hostname.lower()
1120
hostname = hostname.strip('.-')
1125
def read_cached_file(filename, cache_info, reload_func=None):
1126
"""Read from a file if it has been modified.
1128
:param cache_info: dictionary to hold opaque cache.
1129
:param reload_func: optional function to be called with data when
1130
file is reloaded due to a modification.
1132
:returns: data from file
1135
mtime = os.path.getmtime(filename)
1136
if not cache_info or mtime != cache_info.get('mtime'):
1137
LOG.debug(_("Reloading cached file %s") % filename)
1138
with open(filename) as fap:
1139
cache_info['data'] = fap.read()
1140
cache_info['mtime'] = mtime
1142
reload_func(cache_info['data'])
1143
return cache_info['data']
1146
def file_open(*args, **kwargs):
1149
see built-in file() documentation for more details
1151
Note: The reason this is kept in a separate module is to easily
1152
be able to provide a stub module that doesn't alter system
1153
state at all (for unit tests)
1155
return file(*args, **kwargs)
1158
def hash_file(file_like_object):
1159
"""Generate a hash for the contents of a file."""
1160
checksum = hashlib.sha1()
1161
for chunk in iter(lambda: file_like_object.read(32768), b''):
1162
checksum.update(chunk)
1163
return checksum.hexdigest()
1166
@contextlib.contextmanager
1167
def temporary_mutation(obj, **kwargs):
1168
"""Temporarily set the attr on a particular object to a given value then
1169
revert when finished.
1171
One use of this is to temporarily set the read_deleted flag on a context
1174
with temporary_mutation(context, read_deleted="yes"):
1175
do_something_that_needed_deleted_objects()
1177
NOT_PRESENT = object()
1180
for attr, new_value in kwargs.items():
1181
old_values[attr] = getattr(obj, attr, NOT_PRESENT)
1182
setattr(obj, attr, new_value)
1187
for attr, old_value in old_values.items():
1188
if old_value is NOT_PRESENT:
1191
setattr(obj, attr, old_value)
1194
def service_is_up(service):
1195
"""Check whether a service is up based on last heartbeat."""
1196
last_heartbeat = service['updated_at'] or service['created_at']
1197
# Timestamps in DB are UTC.
1198
elapsed = total_seconds(timeutils.utcnow() - last_heartbeat)
1199
return abs(elapsed) <= FLAGS.service_down_time
1202
def generate_mac_address():
1203
"""Generate an Ethernet MAC address."""
1204
# NOTE(vish): We would prefer to use 0xfe here to ensure that linux
1205
# bridge mac addresses don't change, but it appears to
1206
# conflict with libvirt, so we use the next highest octet
1207
# that has the unicast and locally administered bits set
1209
# Discussion: https://bugs.launchpad.net/nova/+bug/921838
1210
mac = [0xfa, 0x16, 0x3e,
1211
random.randint(0x00, 0x7f),
1212
random.randint(0x00, 0xff),
1213
random.randint(0x00, 0xff)]
1214
return ':'.join(map(lambda x: "%02x" % x, mac))
1217
def read_file_as_root(file_path):
1218
"""Secure helper to read file as root."""
1220
out, _err = execute('cat', file_path, run_as_root=True)
1222
except exception.ProcessExecutionError:
1223
raise exception.FileNotFound(file_path=file_path)
1226
@contextlib.contextmanager
1227
def temporary_chown(path, owner_uid=None):
1228
"""Temporarily chown a path.
1230
:params owner_uid: UID of temporary owner (defaults to current user)
1232
if owner_uid is None:
1233
owner_uid = os.getuid()
1235
orig_uid = os.stat(path).st_uid
1237
if orig_uid != owner_uid:
1238
execute('chown', owner_uid, path, run_as_root=True)
1242
if orig_uid != owner_uid:
1243
execute('chown', orig_uid, path, run_as_root=True)
1246
@contextlib.contextmanager
1247
def tempdir(**kwargs):
1248
tmpdir = tempfile.mkdtemp(**kwargs)
1253
shutil.rmtree(tmpdir)
1255
LOG.error(_('Could not remove tmpdir: %s'), str(e))
1258
def strcmp_const_time(s1, s2):
1259
"""Constant-time string comparison.
1261
:params s1: the first string
1262
:params s2: the second string
1264
:return: True if the strings are equal.
1266
This function takes two strings and compares them. It is intended to be
1267
used when doing a comparison for authentication purposes to help guard
1268
against timing attacks.
1270
if len(s1) != len(s2):
1273
for (a, b) in zip(s1, s2):
1274
result |= ord(a) ^ ord(b)
1278
def walk_class_hierarchy(clazz, encountered=None):
1279
"""Walk class hierarchy, yielding most derived classes first"""
1282
for subclass in clazz.__subclasses__():
1283
if subclass not in encountered:
1284
encountered.append(subclass)
1285
# drill down to leaves first
1286
for subsubclass in walk_class_hierarchy(subclass, encountered):
1291
class UndoManager(object):
1292
"""Provides a mechanism to facilitate rolling back a series of actions
1293
when an exception is raised.
1296
self.undo_stack = []
1298
def undo_with(self, undo_func):
1299
self.undo_stack.append(undo_func)
1301
def _rollback(self):
1302
for undo_func in reversed(self.undo_stack):
1305
def rollback_and_reraise(self, msg=None, **kwargs):
1306
"""Rollback a series of actions then re-raise the exception.
1308
.. note:: (sirp) This should only be called within an
1311
with excutils.save_and_reraise_exception():
1313
LOG.exception(msg, **kwargs)
1318
def ensure_tree(path):
1319
"""Create a directory (and any ancestor directories required)
1321
:param path: Directory to create
1325
except OSError as exc:
1326
if exc.errno == errno.EEXIST:
1327
if not os.path.isdir(path):
1333
def last_bytes(file_like_object, num):
1334
"""Return num bytes from the end of the file, and remaining byte count.
1336
:param file_like_object: The file to read
1337
:param num: The number of bytes to return
1339
:returns (data, remaining)
1343
file_like_object.seek(-num, os.SEEK_END)
1346
file_like_object.seek(0, os.SEEK_SET)
1350
remaining = file_like_object.tell()
1351
return (file_like_object.read(), remaining)