Use oslo log and oslo concurrency

To make patches small and easy to review, the task (using oslo) is
implemented in a series of patches.

In this patch, some codes under tacker/openstack/common are changed
temporarily, but they will be removed totally in later patches.

Change-Id: I5396e8e569ff8b26b387e99f2442ccfa3f27d684
Partial-Bug: #1552282
This commit is contained in:
gong yong sheng 2016-06-02 17:39:51 +08:00
parent 617f28a93e
commit 82772a0ecd
55 changed files with 67 additions and 1158 deletions

View File

@ -23,7 +23,9 @@ python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0
alembic>=0.8.4 # MIT
six>=1.9.0 # MIT
stevedore>=1.10.0 # Apache-2.0
oslo.concurrency>=3.8.0 # Apache-2.0
oslo.config>=3.9.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0
oslo.messaging>=4.5.0 # Apache-2.0
oslo.rootwrap>=2.0.0 # Apache-2.0
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0

View File

@ -22,10 +22,10 @@ import tempfile
from eventlet.green import subprocess
from eventlet import greenthread
from oslo_log import log as logging
from tacker.common import utils
from tacker.openstack.common import excutils
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)

View File

@ -14,13 +14,13 @@
# under the License.
from oslo_config import cfg
from oslo_log import log as logging
from six import iteritems
from six.moves.urllib import parse as urllib_parse
from webob import exc
from tacker.common import constants
from tacker.common import exceptions
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)

View File

@ -19,6 +19,7 @@ import imp
import os
from oslo_config import cfg
from oslo_log import log as logging
import routes
import six
import webob.dec
@ -27,7 +28,6 @@ import webob.exc
from tacker.api.v1 import attributes
from tacker.common import exceptions
import tacker.extensions
from tacker.openstack.common import log as logging
from tacker import policy
from tacker import wsgi

View File

@ -16,11 +16,11 @@
import re
import netaddr
from oslo_log import log as logging
from six import iteritems
from tacker.common import constants
from tacker.common import exceptions as n_exc
from tacker.openstack.common import log as logging
from tacker.openstack.common import uuidutils

View File

@ -17,12 +17,13 @@ import netaddr
from six import iteritems
import webob.exc
from oslo_log import log as logging
from tacker.api import api_common
from tacker.api.v1 import attributes
from tacker.api.v1 import resource as wsgi_resource
from tacker.common import exceptions
from tacker.common import rpc as n_rpc
from tacker.openstack.common import log as logging
from tacker import policy

View File

@ -20,6 +20,7 @@ Utility methods for working with WSGI servers redux
import sys
import netaddr
from oslo_log import log as logging
import six
import webob.dec
import webob.exc
@ -27,7 +28,6 @@ import webob.exc
from tacker.api.v1 import attributes
from tacker.common import exceptions
from tacker.openstack.common import gettextutils
from tacker.openstack.common import log as logging
from tacker import wsgi

View File

@ -13,11 +13,11 @@
# under the License.
from oslo_config import cfg
from oslo_log import log as logging
import webob.dec
import webob.exc
from tacker import context
from tacker.openstack.common import log as logging
from tacker.openstack.common.middleware import request_id
from tacker import wsgi

View File

@ -20,12 +20,12 @@ Routines for configuring Tacker
import os
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
from paste import deploy
from tacker.common import utils
from tacker.openstack.common.db import options as db_options
from tacker.openstack.common import log as logging
from tacker import version
@ -88,6 +88,7 @@ core_cli_opts = [
"This directory must be writable by the agent.")),
]
logging.register_options(cfg.CONF)
# Register the configuration options
cfg.CONF.register_opts(core_opts)
cfg.CONF.register_cli_opts(core_cli_opts)
@ -119,7 +120,7 @@ def setup_logging(conf):
:param conf: a cfg.ConfOpts object
"""
product_name = "tacker"
logging.setup(product_name)
logging.setup(conf, product_name)
LOG.info(_("Logging enabled!"))

View File

@ -15,11 +15,11 @@
# under the License.
#
import logging as log
from oslo_log import log as logging
import stevedore.named
LOG = log.getLogger(__name__)
LOG = logging.getLogger(__name__)
class DriverManager(object):

View File

@ -15,7 +15,7 @@
"""Log helper functions."""
from tacker.openstack.common import log as logging
from oslo_log import log as logging
LOG = logging.getLogger(__name__)

View File

@ -14,10 +14,10 @@
# under the License.
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
from tacker.common import rpc as n_rpc
from tacker.openstack.common import log as logging
from tacker.openstack.common import service

View File

@ -32,15 +32,15 @@ import uuid
from eventlet.green import subprocess
import netaddr
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
from six import iteritems
from stevedore import driver
from tacker.common import constants as q_const
from tacker.i18n import _LE
from tacker.openstack.common import lockutils
from tacker.openstack.common import log as logging
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"

View File

@ -18,10 +18,11 @@
import copy
import datetime
from oslo_log import log as logging
from tacker.db import api as db_api
from tacker.openstack.common import context as common_context
from tacker.openstack.common import local
from tacker.openstack.common import log as logging
from tacker import policy

View File

@ -14,11 +14,11 @@
# under the License.
from oslo_config import cfg
from oslo_log import log as logging
import sqlalchemy as sql
from tacker.db import model_base
from tacker.openstack.common.db.sqlalchemy import session
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)

View File

@ -16,6 +16,7 @@
import uuid
from oslo_log import log as logging
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import exc as orm_exc
@ -28,7 +29,6 @@ from tacker.db import model_base
from tacker.db import models_v1
from tacker.extensions import vnfm
from tacker import manager
from tacker.openstack.common import log as logging
from tacker.openstack.common import uuidutils
from tacker.plugins.common import constants

View File

@ -15,13 +15,13 @@
import abc
from oslo_log import log as logging
import six
from tacker.api import extensions
from tacker.api.v1 import attributes as attr
from tacker.api.v1 import resource_helper
from tacker.common import exceptions
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
from tacker.services import service_base

View File

@ -14,10 +14,10 @@
# under the License.
from oslo_config import cfg
from oslo_log import log as logging
from tacker.common import rpc_compat
from tacker.common import utils
from tacker.openstack.common import log as logging
from tacker.openstack.common import periodic_task

View File

@ -18,11 +18,11 @@ import os
from keystoneclient import exceptions
from oslo_config import cfg
from oslo_log import log as logging
from tacker.common import log
from tacker.extensions import nfvo
from tacker.nfvo.drivers.vim import abstract_vim_driver
from tacker.openstack.common import log as logging
from tacker.vm import keystone

View File

@ -17,13 +17,13 @@
import uuid
from oslo_config import cfg
from oslo_log import log as logging
from tacker.common import driver_manager
from tacker.common import log
from tacker.common import utils
from tacker.db.nfvo import nfvo_db
from tacker.openstack.common import excutils
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)

View File

@ -28,9 +28,9 @@ import eventlet
import eventlet.backdoor
import greenlet
from oslo_config import cfg
from oslo_log import log as logging
from tacker.openstack.common.gettextutils import _
from tacker.openstack.common import log as logging
help_for_backdoor_port = (
"Acceptable values are 0, <port>, and <start>:<end>, where 0 results "

View File

@ -1,137 +0,0 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import contextlib
import errno
import os
import tempfile
from tacker.openstack.common import excutils
from tacker.openstack.common.gettextutils import _
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)
_FILE_CACHE = {}
def ensure_tree(path):
"""Create a directory (and any ancestor directories required)
:param path: Directory to create
"""
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST:
if not os.path.isdir(path):
raise
else:
raise
def read_cached_file(filename, force_reload=False):
"""Read from a file if it has been modified.
:param force_reload: Whether to reload the file.
:returns: A tuple with a boolean specifying if the data is fresh
or not.
"""
global _FILE_CACHE
if force_reload and filename in _FILE_CACHE:
del _FILE_CACHE[filename]
reloaded = False
mtime = os.path.getmtime(filename)
cache_info = _FILE_CACHE.setdefault(filename, {})
if not cache_info or mtime > cache_info.get('mtime', 0):
LOG.debug(_("Reloading cached file %s"), filename)
with open(filename) as fap:
cache_info['data'] = fap.read()
cache_info['mtime'] = mtime
reloaded = True
return (reloaded, cache_info['data'])
def delete_if_exists(path, remove=os.unlink):
"""Delete a file, but ignore file not found error.
:param path: File to delete
:param remove: Optional function to remove passed path
"""
try:
remove(path)
except OSError as e:
if e.errno != errno.ENOENT:
raise
@contextlib.contextmanager
def remove_path_on_error(path, remove=delete_if_exists):
"""Protect code that wants to operate on PATH atomically.
Any exception will cause PATH to be removed.
:param path: File to work with
:param remove: Optional function to remove passed path
"""
try:
yield
except Exception:
with excutils.save_and_reraise_exception():
remove(path)
def file_open(*args, **kwargs):
"""Open file
see built-in file() documentation for more details
Note: The reason this is kept in a separate module is to easily
be able to provide a stub module that doesn't alter system
state at all (for unit tests)
"""
return file(*args, **kwargs)
def write_to_tempfile(content, path=None, suffix='', prefix='tmp'):
"""Create temporary file or use existing file.
This util is needed for creating temporary file with
specified content, suffix and prefix. If path is not None,
it will be used for writing content. If the path doesn't
exist it'll be created.
:param content: content for temporary file.
:param path: same as parameter 'dir' for mkstemp
:param suffix: same as parameter 'suffix' for mkstemp
:param prefix: same as parameter 'prefix' for mkstemp
For example: it can be used in database tests for creating
configuration files.
"""
if path:
ensure_tree(path)
(fd, path) = tempfile.mkstemp(suffix=suffix, dir=path, prefix=prefix)
try:
os.write(fd, content)
finally:
os.close(fd)
return path

View File

@ -1,303 +0,0 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import contextlib
import errno
import functools
import os
import shutil
import subprocess
import sys
import tempfile
import threading
import time
import weakref
from oslo_config import cfg
from tacker.openstack.common import fileutils
from tacker.openstack.common.gettextutils import _
from tacker.openstack.common import local
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)
util_opts = [
cfg.BoolOpt('disable_process_locking', default=False,
help='Whether to disable inter-process locks'),
cfg.StrOpt('lock_path',
default=os.environ.get("TACKER_LOCK_PATH"),
help=('Directory to use for lock files.'))
]
CONF = cfg.CONF
CONF.register_opts(util_opts)
def set_defaults(lock_path):
cfg.set_defaults(util_opts, lock_path=lock_path)
class _InterProcessLock(object):
"""Lock implementation which allows multiple locks, working around
issues like bugs.debian.org/cgi-bin/bugreport.cgi?bug=632857 and does
not require any cleanup. Since the lock is always held on a file
descriptor rather than outside of the process, the lock gets dropped
automatically if the process crashes, even if __exit__ is not executed.
There are no guarantees regarding usage by multiple green threads in a
single process here. This lock works only between processes. Exclusive
access between local threads should be achieved using the semaphores
in the @synchronized decorator.
Note these locks are released when the descriptor is closed, so it's not
safe to close the file descriptor while another green thread holds the
lock. Just opening and closing the lock file can break synchronisation,
so lock files must be accessed only using this abstraction.
"""
def __init__(self, name):
self.lockfile = None
self.fname = name
def __enter__(self):
self.lockfile = open(self.fname, 'w')
while True:
try:
# Using non-blocking locks since green threads are not
# patched to deal with blocking locking calls.
# Also upon reading the MSDN docs for locking(), it seems
# to have a laughable 10 attempts "blocking" mechanism.
self.trylock()
return self
except IOError as e:
if e.errno in (errno.EACCES, errno.EAGAIN):
# external locks synchronise things like iptables
# updates - give it some time to prevent busy spinning
time.sleep(0.01)
else:
raise
def __exit__(self, exc_type, exc_val, exc_tb):
try:
self.unlock()
self.lockfile.close()
except IOError:
LOG.exception(_("Could not release the acquired lock `%s`"),
self.fname)
def trylock(self):
raise NotImplementedError()
def unlock(self):
raise NotImplementedError()
class _WindowsLock(_InterProcessLock):
def trylock(self):
msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1)
def unlock(self):
msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1)
class _PosixLock(_InterProcessLock):
def trylock(self):
fcntl.lockf(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
def unlock(self):
fcntl.lockf(self.lockfile, fcntl.LOCK_UN)
if os.name == 'nt':
import msvcrt
InterProcessLock = _WindowsLock
else:
import fcntl
InterProcessLock = _PosixLock
_semaphores = weakref.WeakValueDictionary()
_semaphores_lock = threading.Lock()
@contextlib.contextmanager
def lock(name, lock_file_prefix=None, external=False, lock_path=None):
"""Context based lock
This function yields a `threading.Semaphore` instance (if we don't use
eventlet.monkey_patch(), else `semaphore.Semaphore`) unless external is
True, in which case, it'll yield an InterProcessLock instance.
:param lock_file_prefix: The lock_file_prefix argument is used to provide
lock files on disk with a meaningful prefix.
:param external: The external keyword argument denotes whether this lock
should work across multiple processes. This means that if two different
workers both run a a method decorated with @synchronized('mylock',
external=True), only one of them will execute at a time.
:param lock_path: The lock_path keyword argument is used to specify a
special location for external lock files to live. If nothing is set, then
CONF.lock_path is used as a default.
"""
with _semaphores_lock:
try:
sem = _semaphores[name]
except KeyError:
sem = threading.Semaphore()
_semaphores[name] = sem
with sem:
LOG.debug(_('Got semaphore "%(lock)s"'), {'lock': name})
# NOTE(mikal): I know this looks odd
if not hasattr(local.strong_store, 'locks_held'):
local.strong_store.locks_held = []
local.strong_store.locks_held.append(name)
try:
if external and not CONF.disable_process_locking:
LOG.debug(_('Attempting to grab file lock "%(lock)s"'),
{'lock': name})
# We need a copy of lock_path because it is non-local
local_lock_path = lock_path or CONF.lock_path
if not local_lock_path:
raise cfg.RequiredOptError('lock_path')
if not os.path.exists(local_lock_path):
fileutils.ensure_tree(local_lock_path)
LOG.info(_('Created lock path: %s'), local_lock_path)
def add_prefix(name, prefix):
if not prefix:
return name
sep = '' if prefix.endswith('-') else '-'
return '%s%s%s' % (prefix, sep, name)
# NOTE(mikal): the lock name cannot contain directory
# separators
lock_file_name = add_prefix(name.replace(os.sep, '_'),
lock_file_prefix)
lock_file_path = os.path.join(local_lock_path, lock_file_name)
try:
lock = InterProcessLock(lock_file_path)
with lock as lock:
LOG.debug(_('Got file lock "%(lock)s" at %(path)s'),
{'lock': name, 'path': lock_file_path})
yield lock
finally:
LOG.debug(_('Released file lock "%(lock)s" at %(path)s'),
{'lock': name, 'path': lock_file_path})
else:
yield sem
finally:
local.strong_store.locks_held.remove(name)
def synchronized(name, lock_file_prefix=None, external=False, lock_path=None):
"""Synchronization decorator.
Decorating a method like so::
@synchronized('mylock')
def foo(self, *args):
...
ensures that only one thread will execute the foo method at a time.
Different methods can share the same lock::
@synchronized('mylock')
def foo(self, *args):
...
@synchronized('mylock')
def bar(self, *args):
...
This way only one of either foo or bar can be executing at a time.
"""
def wrap(f):
@functools.wraps(f)
def inner(*args, **kwargs):
try:
with lock(name, lock_file_prefix, external, lock_path):
LOG.debug(_('Got semaphore / lock "%(function)s"'),
{'function': f.__name__})
return f(*args, **kwargs)
finally:
LOG.debug(_('Semaphore / lock released "%(function)s"'),
{'function': f.__name__})
return inner
return wrap
def synchronized_with_prefix(lock_file_prefix):
"""Partial object generator for the synchronization decorator.
Redefine @synchronized in each project like so::
(in nova/utils.py)
from nova.openstack.common import lockutils
synchronized = lockutils.synchronized_with_prefix('nova-')
(in nova/foo.py)
from nova import utils
@utils.synchronized('mylock')
def bar(self, *args):
...
The lock_file_prefix argument is used to provide lock files on disk with a
meaningful prefix.
"""
return functools.partial(synchronized, lock_file_prefix=lock_file_prefix)
def main(argv):
"""Create a dir for locks and pass it to command from arguments
If you run this:
python -m openstack.common.lockutils python setup.py testr <etc>
a temporary directory will be created for all your locks and passed to all
your tests in an environment variable. The temporary dir will be deleted
afterwards and the return value will be preserved.
"""
lock_dir = tempfile.mkdtemp()
os.environ["TACKER_LOCK_PATH"] = lock_dir
try:
ret_val = subprocess.call(argv[1:])
finally:
shutil.rmtree(lock_dir, ignore_errors=True)
return ret_val
if __name__ == '__main__':
sys.exit(main(sys.argv))

View File

@ -1,626 +0,0 @@
# Copyright 2011 OpenStack Foundation.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Openstack logging handler.
This module adds to logging functionality by adding the option to specify
a context object when calling the various log methods. If the context object
is not specified, default formatting is used. Additionally, an instance uuid
may be passed as part of the log message, which is intended to make it easier
for admins to find messages related to a specific instance.
It also allows setting of formatting information through conf.
"""
import inspect
import itertools
import logging
import logging.config
import logging.handlers
import os
import re
import sys
import traceback
from oslo_config import cfg
import six
from six import moves
from tacker.openstack.common.gettextutils import _
from tacker.openstack.common import importutils
from tacker.openstack.common import jsonutils
from tacker.openstack.common import local
_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
# NOTE(ldbragst): Let's build a list of regex objects using the list of
# _SANITIZE_KEYS we already have. This way, we only have to add the new key
# to the list of _SANITIZE_KEYS and we can generate regular expressions
# for XML and JSON automatically.
_SANITIZE_PATTERNS = []
_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
r'(<%(key)s>).*?(</%(key)s>)',
r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])']
for key in _SANITIZE_KEYS:
for pattern in _FORMAT_PATTERNS:
reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
_SANITIZE_PATTERNS.append(reg_ex)
common_cli_opts = [
cfg.BoolOpt('debug',
short='d',
default=False,
help='Print debugging output (set logging level to '
'DEBUG instead of default WARNING level).'),
cfg.BoolOpt('verbose',
short='v',
default=False,
help='Print more verbose output (set logging level to '
'INFO instead of default WARNING level).'),
]
logging_cli_opts = [
cfg.StrOpt('log-config-append',
metavar='PATH',
deprecated_name='log-config',
help='The name of logging configuration file. It does not '
'disable existing loggers, but just appends specified '
'logging configuration to any other existing logging '
'options. Please see the Python logging module '
'documentation for details on logging configuration '
'files.'),
cfg.StrOpt('log-format',
default=None,
metavar='FORMAT',
help='DEPRECATED. '
'A logging.Formatter log message format string which may '
'use any of the available logging.LogRecord attributes. '
'This option is deprecated. Please use '
'logging_context_format_string and '
'logging_default_format_string instead.'),
cfg.StrOpt('log-date-format',
default=_DEFAULT_LOG_DATE_FORMAT,
metavar='DATE_FORMAT',
help='Format string for %%(asctime)s in log records. '
'Default: %(default)s'),
cfg.StrOpt('log-file',
metavar='PATH',
deprecated_name='logfile',
help='(Optional) Name of log file to output to. '
'If no default is set, logging will go to stdout.'),
cfg.StrOpt('log-dir',
deprecated_name='logdir',
help='(Optional) The base directory used for relative '
'--log-file paths'),
cfg.BoolOpt('use-syslog',
default=False,
help='Use syslog for logging.'),
cfg.StrOpt('syslog-log-facility',
default='LOG_USER',
help='syslog facility to receive log lines')
]
generic_log_opts = [
cfg.BoolOpt('use_stderr',
default=True,
help='Log output to standard error')
]
log_opts = [
cfg.StrOpt('logging_context_format_string',
default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [%(request_id)s %(user_identity)s] '
'%(instance)s%(message)s',
help='format string to use for log messages with context'),
cfg.StrOpt('logging_default_format_string',
default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [-] %(instance)s%(message)s',
help='format string to use for log messages without context'),
cfg.StrOpt('logging_debug_format_suffix',
default='%(funcName)s %(pathname)s:%(lineno)d',
help='data to append to log format when level is DEBUG'),
cfg.StrOpt('logging_exception_prefix',
default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s '
'%(instance)s',
help='prefix each line of exception output with this format'),
cfg.ListOpt('default_log_levels',
default=[
'amqp=WARN',
'amqplib=WARN',
'boto=WARN',
'qpid=WARN',
'sqlalchemy=WARN',
'suds=INFO',
'iso8601=WARN',
],
help='list of logger=LEVEL pairs'),
cfg.BoolOpt('publish_errors',
default=False,
help='publish error events'),
cfg.BoolOpt('fatal_deprecations',
default=False,
help='make deprecations fatal'),
# NOTE(mikal): there are two options here because sometimes we are handed
# a full instance (and could include more information), and other times we
# are just handed a UUID for the instance.
cfg.StrOpt('instance_format',
default='[instance: %(uuid)s] ',
help='If an instance is passed with the log message, format '
'it like this'),
cfg.StrOpt('instance_uuid_format',
default='[instance: %(uuid)s] ',
help='If an instance UUID is passed with the log message, '
'format it like this'),
]
CONF = cfg.CONF
CONF.register_cli_opts(common_cli_opts)
CONF.register_cli_opts(logging_cli_opts)
CONF.register_opts(generic_log_opts)
CONF.register_opts(log_opts)
# our new audit level
# NOTE(jkoelker) Since we synthesized an audit level, make the logging
# module aware of it so it acts like other levels.
logging.AUDIT = logging.INFO + 1
logging.addLevelName(logging.AUDIT, 'AUDIT')
try:
NullHandler = logging.NullHandler
except AttributeError: # NOTE(jkoelker) NullHandler added in Python 2.7
class NullHandler(logging.Handler):
def handle(self, record):
pass
def emit(self, record):
pass
def createLock(self):
self.lock = None
def _dictify_context(context):
if context is None:
return None
if not isinstance(context, dict) and getattr(context, 'to_dict', None):
context = context.to_dict()
return context
def _get_binary_name():
return os.path.basename(inspect.stack()[-1][1])
def _get_log_file_path(binary=None):
logfile = CONF.log_file
logdir = CONF.log_dir
if logfile and not logdir:
return logfile
if logfile and logdir:
return os.path.join(logdir, logfile)
if logdir:
binary = binary or _get_binary_name()
return '%s.log' % (os.path.join(logdir, binary),)
return None
def mask_password(message, secret="***"):
"""Replace password with 'secret' in message.
:param message: The string which includes security information.
:param secret: value with which to replace passwords.
:returns: The unicode value of message with the password fields masked.
For example:
>>> mask_password("'adminPass' : 'aaaaa'")
"'adminPass' : '***'"
>>> mask_password("'admin_pass' : 'aaaaa'")
"'admin_pass' : '***'"
>>> mask_password('"password" : "aaaaa"')
'"password" : "***"'
>>> mask_password("'original_password' : 'aaaaa'")
"'original_password' : '***'"
>>> mask_password("u'original_password' : u'aaaaa'")
"u'original_password' : u'***'"
"""
message = six.text_type(message)
# NOTE(ldbragst): Check to see if anything in message contains any key
# specified in _SANITIZE_KEYS, if not then just return the message since
# we don't have to mask any passwords.
if not any(key in message for key in _SANITIZE_KEYS):
return message
secret = r'\g<1>' + secret + r'\g<2>'
for pattern in _SANITIZE_PATTERNS:
message = re.sub(pattern, secret, message)
return message
class BaseLoggerAdapter(logging.LoggerAdapter):
def audit(self, msg, *args, **kwargs):
self.log(logging.AUDIT, msg, *args, **kwargs)
class LazyAdapter(BaseLoggerAdapter):
def __init__(self, name='unknown', version='unknown'):
self._logger = None
self.extra = {}
self.name = name
self.version = version
@property
def logger(self):
if not self._logger:
self._logger = getLogger(self.name, self.version)
return self._logger
class ContextAdapter(BaseLoggerAdapter):
warn = logging.LoggerAdapter.warning
def __init__(self, logger, project_name, version_string):
self.logger = logger
self.project = project_name
self.version = version_string
@property
def handlers(self):
return self.logger.handlers
def deprecated(self, msg, *args, **kwargs):
stdmsg = _("Deprecated: %s") % msg
if CONF.fatal_deprecations:
self.critical(stdmsg, *args, **kwargs)
raise DeprecatedConfig(msg=stdmsg)
else:
self.warn(stdmsg, *args, **kwargs)
def process(self, msg, kwargs):
# NOTE(mrodden): catch any Message/other object and
# coerce to unicode before they can get
# to the python logging and possibly
# cause string encoding trouble
if not isinstance(msg, six.string_types):
msg = six.text_type(msg)
if 'extra' not in kwargs:
kwargs['extra'] = {}
extra = kwargs['extra']
context = kwargs.pop('context', None)
if not context:
context = getattr(local.store, 'context', None)
if context:
extra.update(_dictify_context(context))
instance = kwargs.pop('instance', None)
instance_uuid = (extra.get('instance_uuid', None) or
kwargs.pop('instance_uuid', None))
instance_extra = ''
if instance:
instance_extra = CONF.instance_format % instance
elif instance_uuid:
instance_extra = (CONF.instance_uuid_format
% {'uuid': instance_uuid})
extra['instance'] = instance_extra
extra.setdefault('user_identity', kwargs.pop('user_identity', None))
extra['project'] = self.project
extra['version'] = self.version
extra['extra'] = extra.copy()
return msg, kwargs
class JSONFormatter(logging.Formatter):
def __init__(self, fmt=None, datefmt=None):
# NOTE(jkoelker) we ignore the fmt argument, but its still there
# since logging.config.fileConfig passes it.
self.datefmt = datefmt
def formatException(self, ei, strip_newlines=True):
lines = traceback.format_exception(*ei)
if strip_newlines:
lines = [moves.filter(
lambda x: x,
line.rstrip().splitlines()) for line in lines]
lines = list(itertools.chain(*lines))
return lines
def format(self, record):
message = {'message': record.getMessage(),
'asctime': self.formatTime(record, self.datefmt),
'name': record.name,
'msg': record.msg,
'args': record.args,
'levelname': record.levelname,
'levelno': record.levelno,
'pathname': record.pathname,
'filename': record.filename,
'module': record.module,
'lineno': record.lineno,
'funcname': record.funcName,
'created': record.created,
'msecs': record.msecs,
'relative_created': record.relativeCreated,
'thread': record.thread,
'thread_name': record.threadName,
'process_name': record.processName,
'process': record.process,
'traceback': None}
if hasattr(record, 'extra'):
message['extra'] = record.extra
if record.exc_info:
message['traceback'] = self.formatException(record.exc_info)
return jsonutils.dumps(message)
def _create_logging_excepthook(product_name):
def logging_excepthook(exc_type, value, tb):
extra = {}
if CONF.verbose:
extra['exc_info'] = (exc_type, value, tb)
getLogger(product_name).critical(str(value), **extra)
return logging_excepthook
class LogConfigError(Exception):
message = _('Error loading logging config %(log_config)s: %(err_msg)s')
def __init__(self, log_config, err_msg):
self.log_config = log_config
self.err_msg = err_msg
def __str__(self):
return self.message % dict(log_config=self.log_config,
err_msg=self.err_msg)
def _load_log_config(log_config_append):
try:
logging.config.fileConfig(log_config_append,
disable_existing_loggers=False)
except moves.configparser.Error as exc:
raise LogConfigError(log_config_append, str(exc))
def setup(product_name):
"""Setup logging."""
if CONF.log_config_append:
_load_log_config(CONF.log_config_append)
else:
_setup_logging_from_conf()
sys.excepthook = _create_logging_excepthook(product_name)
def set_defaults(logging_context_format_string):
cfg.set_defaults(log_opts,
logging_context_format_string=
logging_context_format_string)
def _find_facility_from_conf():
facility_names = logging.handlers.SysLogHandler.facility_names
facility = getattr(logging.handlers.SysLogHandler,
CONF.syslog_log_facility,
None)
if facility is None and CONF.syslog_log_facility in facility_names:
facility = facility_names.get(CONF.syslog_log_facility)
if facility is None:
valid_facilities = facility_names.keys()
consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON',
'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS',
'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP',
'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3',
'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7']
valid_facilities.extend(consts)
raise TypeError(_('syslog facility must be one of: %s') %
', '.join("'%s'" % fac
for fac in valid_facilities))
return facility
def _setup_logging_from_conf():
log_root = getLogger(None).logger
for handler in log_root.handlers:
log_root.removeHandler(handler)
if CONF.use_syslog:
facility = _find_facility_from_conf()
syslog = logging.handlers.SysLogHandler(address='/dev/log',
facility=facility)
log_root.addHandler(syslog)
logpath = _get_log_file_path()
if logpath:
filelog = logging.handlers.WatchedFileHandler(logpath)
log_root.addHandler(filelog)
if CONF.use_stderr:
streamlog = ColorHandler()
log_root.addHandler(streamlog)
elif not logpath:
# pass sys.stdout as a positional argument
# python2.6 calls the argument strm, in 2.7 it's stream
streamlog = logging.StreamHandler(sys.stdout)
log_root.addHandler(streamlog)
if CONF.publish_errors:
handler = importutils.import_object(
"tacker.openstack.common.log_handler.PublishErrorsHandler",
logging.ERROR)
log_root.addHandler(handler)
datefmt = CONF.log_date_format
for handler in log_root.handlers:
# NOTE(alaski): CONF.log_format overrides everything currently. This
# should be deprecated in favor of context aware formatting.
if CONF.log_format:
handler.setFormatter(logging.Formatter(fmt=CONF.log_format,
datefmt=datefmt))
log_root.info('Deprecated: log_format is now deprecated and will '
'be removed in the next release')
else:
handler.setFormatter(ContextFormatter(datefmt=datefmt))
if CONF.debug:
log_root.setLevel(logging.DEBUG)
elif CONF.verbose:
log_root.setLevel(logging.INFO)
else:
log_root.setLevel(logging.WARNING)
for pair in CONF.default_log_levels:
mod, _sep, level_name = pair.partition('=')
level = logging.getLevelName(level_name)
logger = logging.getLogger(mod)
logger.setLevel(level)
_loggers = {}
def getLogger(name='unknown', version='unknown'):
if name not in _loggers:
_loggers[name] = ContextAdapter(logging.getLogger(name),
name,
version)
return _loggers[name]
def getLazyLogger(name='unknown', version='unknown'):
"""Returns lazy logger.
Creates a pass-through logger that does not create the real logger
until it is really needed and delegates all calls to the real logger
once it is created.
"""
return LazyAdapter(name, version)
class WritableLogger(object):
"""A thin wrapper that responds to `write` and logs."""
def __init__(self, logger, level=logging.INFO):
self.logger = logger
self.level = level
def write(self, msg):
self.logger.log(self.level, msg)
class ContextFormatter(logging.Formatter):
"""A context.RequestContext aware formatter configured through flags.
The flags used to set format strings are: logging_context_format_string
and logging_default_format_string. You can also specify
logging_debug_format_suffix to append extra formatting if the log level is
debug.
For information about what variables are available for the formatter see:
http://docs.python.org/library/logging.html#formatter
"""
def format(self, record):
"""Uses contextstring if request_id is set, otherwise default."""
# NOTE(sdague): default the fancier formating params
# to an empty string so we don't throw an exception if
# they get used
for key in ('instance', 'color'):
if key not in record.__dict__:
record.__dict__[key] = ''
if record.__dict__.get('request_id'):
self._fmt = CONF.logging_context_format_string
else:
self._fmt = CONF.logging_default_format_string
if (record.levelno == logging.DEBUG and
CONF.logging_debug_format_suffix):
self._fmt += " " + CONF.logging_debug_format_suffix
# Cache this on the record, Logger will respect our formated copy
if record.exc_info:
record.exc_text = self.formatException(record.exc_info, record)
return logging.Formatter.format(self, record)
def formatException(self, exc_info, record=None):
"""Format exception output with CONF.logging_exception_prefix."""
if not record:
return logging.Formatter.formatException(self, exc_info)
stringbuffer = moves.StringIO()
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2],
None, stringbuffer)
lines = stringbuffer.getvalue().split('\n')
stringbuffer.close()
if CONF.logging_exception_prefix.find('%(asctime)') != -1:
record.asctime = self.formatTime(record, self.datefmt)
formatted_lines = []
for line in lines:
pl = CONF.logging_exception_prefix % record.__dict__
fl = '%s%s' % (pl, line)
formatted_lines.append(fl)
return '\n'.join(formatted_lines)
class ColorHandler(logging.StreamHandler):
LEVEL_COLORS = {
logging.DEBUG: '\033[00;32m', # GREEN
logging.INFO: '\033[00;36m', # CYAN
logging.AUDIT: '\033[01;36m', # BOLD CYAN
logging.WARN: '\033[01;33m', # BOLD YELLOW
logging.ERROR: '\033[01;31m', # BOLD RED
logging.CRITICAL: '\033[01;31m', # BOLD RED
}
def format(self, record):
record.color = self.LEVEL_COLORS[record.levelno]
return logging.StreamHandler.format(self, record)
class DeprecatedConfig(Exception):
message = _("Fatal call to deprecated config: %(msg)s")
def __init__(self, msg):
super(Exception, self).__init__(self.message % dict(msg=msg))

View File

@ -1,30 +0,0 @@
# Copyright 2013 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from oslo_config import cfg
from tacker.openstack.common import notifier
class PublishErrorsHandler(logging.Handler):
def emit(self, record):
if ('tacker.openstack.common.notifier.log_notifier' in
cfg.CONF.notification_driver):
return
notifier.api.notify(None, 'error.publisher',
'error_notification',
notifier.api.ERROR,
dict(error=record.getMessage()))

View File

@ -19,9 +19,9 @@ import sys
from eventlet import event
from eventlet import greenthread
from oslo_log import log as logging
from tacker.openstack.common.gettextutils import _
from tacker.openstack.common import log as logging
from tacker.openstack.common import timeutils
LOG = logging.getLogger(__name__)

View File

@ -19,11 +19,11 @@ It catches all exceptions from subsequent applications in WSGI pipeline
to hide internal errors from API response.
"""
from oslo_log import log as logging
import webob.dec
import webob.exc
from tacker.openstack.common.gettextutils import _ # noqa
from tacker.openstack.common import log as logging
from tacker.openstack.common.middleware import base

View File

@ -20,12 +20,12 @@ import os.path
import sys
import traceback as tb
from oslo_log import log as logging
import six
import webob.dec
from tacker.openstack.common import context
from tacker.openstack.common.gettextutils import _LE
from tacker.openstack.common import log as logging
from tacker.openstack.common.middleware import base
from tacker.openstack.common.notifier import api

View File

@ -14,11 +14,10 @@
import time
from oslo_config import cfg
from oslo_log import log as logging
import six
from tacker.openstack.common.gettextutils import _, _LE, _LI
from tacker.openstack.common import log as logging
periodic_opts = [
cfg.BoolOpt('run_external_periodic_tasks',

View File

@ -57,14 +57,13 @@ as it allows particular rules to be explicitly disabled.
import abc
import re
from oslo_log import log as logging
import six
from six.moves.urllib import parse as urllib_parse
from six.moves.urllib import request as urlrequest
from tacker.openstack.common.gettextutils import _
from tacker.openstack.common import jsonutils
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)

View File

@ -25,9 +25,9 @@ import signal
from eventlet.green import subprocess
from eventlet import greenthread
from oslo_log import log as logging
from tacker.openstack.common.gettextutils import _
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)

View File

@ -36,11 +36,11 @@ except ImportError:
import eventlet
from eventlet import event
from oslo_config import cfg
from oslo_log import log as logging
from tacker.openstack.common import eventlet_backdoor
from tacker.openstack.common.gettextutils import _LE, _LI, _LW
from tacker.openstack.common import importutils
from tacker.openstack.common import log as logging
from tacker.openstack.common import systemd
from tacker.openstack.common import threadgroup

View File

@ -20,7 +20,7 @@ import os
import socket
import sys
from tacker.openstack.common import log as logging
from oslo_log import log as logging
LOG = logging.getLogger(__name__)

View File

@ -15,8 +15,8 @@
import eventlet
from eventlet import greenpool
from eventlet import greenthread
from oslo_log import log as logging
from tacker.openstack.common import log as logging
from tacker.openstack.common import loopingcall

View File

@ -18,10 +18,10 @@ Helpers for comparing version strings.
"""
import functools
from oslo_log import log as logging
import pkg_resources
from tacker.openstack.common.gettextutils import _
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)

View File

@ -20,6 +20,7 @@ import itertools
import re
from oslo_config import cfg
from oslo_log import log as logging
import six
from tacker.api.v1 import attributes
@ -27,7 +28,6 @@ from tacker.common import exceptions
import tacker.common.utils as utils
from tacker.openstack.common import excutils
from tacker.openstack.common import importutils
from tacker.openstack.common import log as logging
from tacker.openstack.common import policy

View File

@ -16,10 +16,10 @@
import logging as std_logging
from oslo_config import cfg
from oslo_log import log as logging
from tacker.common import config
from tacker.openstack.common import excutils
from tacker.openstack.common import log as logging
from tacker import wsgi

View File

@ -16,9 +16,9 @@
import os
import fixtures
from oslo_log import log as logging
from tacker.agent.linux import utils
from tacker.openstack.common import log as logging
from tacker.tests import base

View File

@ -15,9 +15,9 @@
"""stubs.py provides interface methods for the database test cases"""
from tacker.db import api as db
from tacker.openstack.common import log as logging
from oslo_log import log as logging
from tacker.db import api as db
LOG = logging.getLogger(__name__)

View File

@ -16,6 +16,7 @@
import abc
import mock
from oslo_log import log as logging
import routes
import webob
import webtest
@ -24,7 +25,6 @@ from tacker.api import extensions
from tacker.common import config
from tacker.common import exceptions
from tacker.openstack.common import jsonutils
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
from tacker.tests import base
from tacker.tests.unit import extension_stubs as ext_stubs

View File

@ -16,10 +16,10 @@
import random
from oslo_log import log as logging
import sqlalchemy as sa
from tacker.db.vm import vm_db
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants

View File

@ -19,6 +19,7 @@ import time
from heatclient import exc as heatException
from oslo_config import cfg
from oslo_log import log as logging
from six import iteritems
from toscaparser.tosca_template import ToscaTemplate
from toscaparser.utils import yamlparser
@ -29,7 +30,6 @@ from tacker.common import clients
from tacker.common import log
from tacker.extensions import vnfm
from tacker.openstack.common import jsonutils
from tacker.openstack.common import log as logging
from tacker.vm.infra_drivers import abstract_driver
from tacker.vm.tosca import utils as toscautils

View File

@ -17,8 +17,9 @@
# TODO(yamahata): once unittests are impletemted, move this there
import uuid
from oslo_log import log as logging
from tacker.common import log
from tacker.openstack.common import log as logging
from tacker.vm.infra_drivers import abstract_driver

View File

@ -21,11 +21,11 @@ from keystoneclient import auth as ks_auth
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient import session as ks_session
from oslo_config import cfg
from oslo_log import log as logging
from six import iteritems
from tacker.api.v1 import attributes
from tacker.i18n import _LE, _LW
from tacker.openstack.common import log as logging
from tacker.openstack.common import versionutils
from tacker.vm.infra_drivers import abstract_driver

View File

@ -22,8 +22,8 @@ from keystoneclient import client
from keystoneclient import exceptions
from keystoneclient import session
from oslo_config import cfg
from oslo_log import log as logging
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF

View File

@ -14,7 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from tacker.openstack.common import log as logging
from oslo_log import log as logging
from tacker.vm.mgmt_drivers import abstract_driver

View File

@ -15,12 +15,12 @@
# under the License.
from oslo_config import cfg
from oslo_log import log as logging
import yaml
from tacker.agent.linux import utils
from tacker.common import log
from tacker.openstack.common import jsonutils
from tacker.openstack.common import log as logging
from tacker.vm.mgmt_drivers import abstract_driver
from tacker.vm.mgmt_drivers import constants as mgmt_constants

View File

@ -20,6 +20,7 @@ import threading
import time
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import timeutils
import six
@ -27,7 +28,6 @@ from tacker.common import clients
from tacker.common import driver_manager
from tacker import context as t_context
from tacker.openstack.common import jsonutils
from tacker.openstack.common import log as logging
from tacker.vm.infra_drivers.heat import heat

View File

@ -13,12 +13,12 @@
#
from oslo_config import cfg
from oslo_log import log as logging
import six.moves.urllib.error as urlerr
import six.moves.urllib.request as urlreq
from tacker.common import log
from tacker.i18n import _LW
from tacker.openstack.common import log as logging
from tacker.vm.monitor_drivers import abstract_driver

View File

@ -13,11 +13,11 @@
#
from oslo_config import cfg
from oslo_log import log as logging
from tacker.agent.linux import utils as linux_utils
from tacker.common import log
from tacker.i18n import _LW
from tacker.openstack.common import log as logging
from tacker.vm.monitor_drivers import abstract_driver

View File

@ -20,6 +20,7 @@ import six
import eventlet
from oslo_config import cfg
from oslo_log import log as logging
from tacker.api.v1 import attributes
from tacker.common import driver_manager
@ -27,7 +28,6 @@ from tacker.db.vm import vm_db
from tacker.extensions import vnfm
from tacker.i18n import _LE
from tacker.openstack.common import excutils
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
from tacker.vm.mgmt_drivers import constants as mgmt_constants
from tacker.vm import monitor

View File

@ -16,15 +16,15 @@ import re
import sys
import yaml
from tacker.common import log
from tacker.common import utils
from tacker.extensions import vnfm
from tacker.openstack.common import log as logging
from oslo_log import log as logging
from six import iteritems
from toscaparser.properties import Property
from toscaparser.utils import yamlparser
from tacker.common import log
from tacker.common import utils
from tacker.extensions import vnfm
FAILURE = 'tosca.policies.tacker.Failure'
LOG = logging.getLogger(__name__)

View File

@ -17,10 +17,10 @@ import os
from cryptography.fernet import Fernet
from oslo_config import cfg
from oslo_log import log as logging
from tacker.extensions import nfvo
from tacker import manager
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
LOG = logging.getLogger(__name__)

View File

@ -30,6 +30,7 @@ from xml.parsers import expat
import eventlet.wsgi
# eventlet.patcher.monkey_patch(all=False, socket=True, thread=True)
from oslo_config import cfg
from oslo_log import log as logging
import routes.middleware
import six
import webob.dec
@ -42,7 +43,6 @@ from tacker.db import api
from tacker.openstack.common import excutils
from tacker.openstack.common import gettextutils
from tacker.openstack.common import jsonutils
from tacker.openstack.common import log as logging
from tacker.openstack.common import service as common_service
from tacker.openstack.common import systemd
@ -247,7 +247,7 @@ class Server(object):
def _run(self, application, socket):
"""Start a WSGI server in a new green thread."""
eventlet.wsgi.server(socket, application, custom_pool=self.pool,
log=logging.WritableLogger(LOG))
log=LOG)
class Middleware(object):

View File

@ -19,7 +19,7 @@ deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
setuptools-git>=0.4
commands =
python -m tacker.openstack.common.lockutils python setup.py testr --slowest --testr-args='{posargs}'
{toxinidir}/tools/ostestr_compat_shim.sh {posargs}
[testenv:functional]
setenv = OS_TEST_PATH=./tacker/tests/functional
@ -28,7 +28,7 @@ deps =
[testenv:dsvm-functional]
basepython = python2.7
setenv = {[testenv]setenv}
setenv = {[testenv]setenv}
{[testenv:functional]setenv}
deps =
{[testenv:functional]deps}