summaryrefslogtreecommitdiffstats
path: root/odl-pipeline/lib/utils
diff options
context:
space:
mode:
Diffstat (limited to 'odl-pipeline/lib/utils')
-rwxr-xr-xodl-pipeline/lib/utils/__init__.py0
-rwxr-xr-xodl-pipeline/lib/utils/node.py87
-rwxr-xr-xodl-pipeline/lib/utils/node_manager.py43
-rwxr-xr-xodl-pipeline/lib/utils/processutils.py233
-rwxr-xr-xodl-pipeline/lib/utils/service.py67
-rwxr-xr-xodl-pipeline/lib/utils/shutil.py66
-rwxr-xr-xodl-pipeline/lib/utils/ssh_client.py88
-rwxr-xr-xodl-pipeline/lib/utils/ssh_util.py36
-rwxr-xr-xodl-pipeline/lib/utils/utils_log.py67
-rwxr-xr-xodl-pipeline/lib/utils/utils_yaml.py20
10 files changed, 707 insertions, 0 deletions
diff --git a/odl-pipeline/lib/utils/__init__.py b/odl-pipeline/lib/utils/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/odl-pipeline/lib/utils/__init__.py
diff --git a/odl-pipeline/lib/utils/node.py b/odl-pipeline/lib/utils/node.py
new file mode 100755
index 0000000..c3c2005
--- /dev/null
+++ b/odl-pipeline/lib/utils/node.py
@@ -0,0 +1,87 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+from ssh_client import SSHClient
+from ssh_util import SshUtil
+from utils_log import log_enter_exit, for_all_methods
+
+
+@for_all_methods(log_enter_exit)
+class Node(object):
+
+ def __init__(self, name, address=None, port=None,
+ user=None, password=None, jump=None, dict=None):
+ self.name = name
+ self.address = address
+ self.jump = jump
+ self.user = user
+ self.port = port
+ self.password = password
+ if dict:
+ self.read_from_dic(dict)
+ self.sshc = SSHClient(self)
+ self.has_access = False
+ self.config = dict
+
+ def read_from_dic(self, dic):
+ allowed_keys = ['address', 'user', 'jump', 'password', 'port']
+ for (key, value) in dic.iteritems():
+ if key in allowed_keys:
+ setattr(self, key, value)
+
+ def ping(self, ip):
+ self.execute(['ping', '-c', '1', ip])
+
+ def execute(self, cmd, **kwargs):
+ return self.sshc.execute(cmd, **kwargs)
+
+ def chown(self, user, path):
+ self.execute('chown -R %(user)s:%(user)s %(path)s' % {'user': user,
+ 'path': path},
+ as_root=True)
+
+ def is_dir(self, path):
+ rv, _ = self.execute('test -d %s && echo yes' % path,
+ check_exit_code=[0, 1])
+ if rv == 'yes\n':
+ return True
+ else:
+ return False
+
+ def is_file(self, path):
+ rv, _ = self.execute('test -f %s && echo yes' % path,
+ check_exit_code=[0, 1])
+ if rv == 'yes\n':
+ return True
+ else:
+ return False
+
+ def reboot(self):
+ self.execute('reboot', as_root=True, check_exit_code=[255])
+
+ def create_path_if_not_exsist(self, path, **kwargs):
+ return self.sshc.execute('mkdir -p %s' % path, **kwargs)
+
+ def copy(self, direction, local_path, remote_path, **kwargs):
+ return self.sshc.copy(direction, local_path, remote_path, **kwargs)
+
+ def to_ssh_config(self):
+ config = ["Host %s" % self.name,
+ " Hostname %s" %
+ (self.address if self.address else self.name)]
+ if self.jump:
+ config.append(" ProxyCommand ssh -F %(config_path)s "
+ "-W %%h:%%p %(name)s"
+ % {'config_path': SshUtil.get_config_file_path(),
+ 'name': self.jump.name})
+ if self.user:
+ config.append(" user %s" % self.user)
+ if self.port:
+ config.append(" port %s" % self.port)
+ return '\n'.join(config)
diff --git a/odl-pipeline/lib/utils/node_manager.py b/odl-pipeline/lib/utils/node_manager.py
new file mode 100755
index 0000000..d11065f
--- /dev/null
+++ b/odl-pipeline/lib/utils/node_manager.py
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+from ssh_util import SshUtil
+
+
+class NodeManager(object):
+
+ env_nodes = []
+ env_node_dict = {}
+ primary_controller = None
+
+ def __init__(self, config=None):
+ if config:
+ for (node_name, node_config) in config.iteritems():
+ self.add_node(node_name, node_config)
+
+ def add_node(self, node_name, node_config):
+ from node import Node
+ if not node_config.get('address'):
+ node_config['address'] = self.get_address_of_node(node_name)
+ node = Node(node_name, dict=node_config)
+ self.env_nodes.append(node)
+ self.env_node_dict[node_name] = node
+ return node
+
+ def get_nodes(self):
+ return self.env_nodes
+
+ def get_node(self, name):
+ return self.env_node_dict[name]
+
+ @classmethod
+ def gen_ssh_config(cls, node):
+ if node not in cls.env_nodes:
+ cls.env_nodes.append(node)
+ SshUtil.gen_ssh_config(cls.env_nodes)
diff --git a/odl-pipeline/lib/utils/processutils.py b/odl-pipeline/lib/utils/processutils.py
new file mode 100755
index 0000000..b5aecb3
--- /dev/null
+++ b/odl-pipeline/lib/utils/processutils.py
@@ -0,0 +1,233 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+import utils_log as log
+import os
+import six
+import re
+import signal
+import subprocess
+from time import sleep
+from threading import Thread
+try:
+ from Queue import Queue
+except ImportError:
+ from queue import Queue # python 3.x
+
+LOG = log.LOG
+LOG_LEVEL = log.LOG_LEVEL
+
+
+def _subprocess_setup():
+ # Python installs a SIGPIPE handler by default. This is usually not what
+ # non-Python subprocesses expect.
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+# NOTE(flaper87): The following globals are used by `mask_password`
+_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_2 = []
+_SANITIZE_PATTERNS_1 = []
+
+
+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'***'"
+ """
+ try:
+ message = six.text_type(message)
+ except UnicodeDecodeError:
+ # NOTE(jecarey): Temporary fix to handle cases where message is a
+ # byte string. A better solution will be provided in Kilo.
+ pass
+
+ # 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
+
+ substitute = r'\g<1>' + secret + r'\g<2>'
+ for pattern in _SANITIZE_PATTERNS_2:
+ message = re.sub(pattern, substitute, message)
+
+ substitute = r'\g<1>' + secret
+ for pattern in _SANITIZE_PATTERNS_1:
+ message = re.sub(pattern, substitute, message)
+
+ return message
+
+
+class ProcessExecutionError(Exception):
+ def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
+ description=None):
+ self.exit_code = exit_code
+ self.stderr = stderr
+ self.stdout = stdout
+ self.cmd = cmd
+ self.description = description
+
+ if description is None:
+ description = "Unexpected error while running command."
+ if exit_code is None:
+ exit_code = '-'
+ message = ("%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r"
+ % (description, cmd, exit_code, stdout, stderr))
+ super(ProcessExecutionError, self).__init__(message)
+
+
+def enqueue_output(out, queue):
+ for line in iter(out.readline, b''):
+ queue.put(line)
+ queue.put("##Finished##")
+ out.close()
+
+
+def execute(cmd, **kwargs):
+ """Helper method to shell out and execute a command through subprocess.
+
+ Allows optional retry.
+
+ :param cmd: Passed to subprocess.Popen.
+ :type cmd: list - will be converted if needed
+ :param process_input: Send to opened process.
+ :type proces_input: string
+ :param check_exit_code: Single bool, int, or list of allowed exit
+ codes. Defaults to [0]. Raise
+ :class:`ProcessExecutionError` unless
+ program exits with one of these code.
+ :type check_exit_code: boolean, int, or [int]
+ :param delay_on_retry: True | False. Defaults to True. If set to True,
+ wait a short amount of time before retrying.
+ :type delay_on_retry: boolean
+ :param attempts: How many times to retry cmd.
+ :type attempts: int
+ :param run_as_root: True | False. Defaults to False. If set to True,
+ or as_root the command is prefixed by the command specified
+ in the root_helper kwarg.
+ execute this command. Defaults to false.
+ :param shell: whether or not there should be a shell used to
+ :type shell: boolean
+ :param loglevel: log level for execute commands.
+ :type loglevel: int. (Should be logging.DEBUG or logging.INFO)
+ :param non_blocking Execute in background.
+ :type non_blockig: boolean
+ :returns: (stdout, (stderr, returncode)) from process
+ execution
+ :raises: :class:`UnknownArgumentError` on
+ receiving unknown arguments
+ :raises: :class:`ProcessExecutionError`
+ """
+ process_input = kwargs.pop('process_input', None)
+ check_exit_code = kwargs.pop('check_exit_code', [0])
+ ignore_exit_code = False
+ attempts = kwargs.pop('attempts', 1)
+ run_as_root = kwargs.pop('run_as_root', False) or kwargs.pop('as_root',
+ False)
+ shell = kwargs.pop('shell', False)
+ loglevel = kwargs.pop('loglevel', LOG_LEVEL)
+ non_blocking = kwargs.pop('non_blocking', False)
+
+ if not isinstance(cmd, list):
+ cmd = cmd.split(' ')
+
+ if run_as_root:
+ cmd = ['sudo'] + cmd
+ if shell:
+ cmd = ' '.join(cmd)
+ if isinstance(check_exit_code, bool):
+ ignore_exit_code = not check_exit_code
+ check_exit_code = [0]
+ elif isinstance(check_exit_code, int):
+ check_exit_code = [check_exit_code]
+
+ if kwargs:
+ raise Exception(('Got unknown keyword args '
+ 'to utils.execute: %r') % kwargs)
+
+ while attempts > 0:
+ attempts -= 1
+ try:
+ LOG.log(loglevel, ('Running cmd (subprocess): %s'), cmd)
+ _PIPE = subprocess.PIPE # pylint: disable=E1101
+
+ if os.name == 'nt':
+ preexec_fn = None
+ close_fds = False
+ else:
+ preexec_fn = _subprocess_setup
+ close_fds = True
+
+ obj = subprocess.Popen(cmd,
+ stdin=_PIPE,
+ stdout=_PIPE,
+ stderr=_PIPE,
+ close_fds=close_fds,
+ preexec_fn=preexec_fn,
+ shell=shell)
+ result = None
+ if process_input is not None:
+ result = obj.communicate(process_input)
+ else:
+ if non_blocking:
+ queue = Queue()
+ thread = Thread(target=enqueue_output, args=(obj.stdout,
+ queue))
+ thread.deamon = True
+ thread.start()
+ # If you want to read this output later:
+ # try:
+ # from Queue import Queue, Empty
+ # except ImportError:
+ # from queue import Queue, Empty # python 3.x
+ # try: line = q.get_nowait() # or q.get(timeout=.1)
+ # except Empty:
+ # print('no output yet')
+ # else: # got line
+ # ... do something with line
+ return queue
+ result = obj.communicate()
+ obj.stdin.close() # pylint: disable=E1101
+ _returncode = obj.returncode # pylint: disable=E1101
+ LOG.log(loglevel, ('Result was %s') % _returncode)
+ if not ignore_exit_code and _returncode not in check_exit_code:
+ (stdout, stderr) = result
+ sanitized_stdout = mask_password(stdout)
+ sanitized_stderr = mask_password(stderr)
+ raise ProcessExecutionError(
+ exit_code=_returncode,
+ stdout=sanitized_stdout,
+ stderr=sanitized_stderr,
+ cmd=(' '.join(cmd)) if isinstance(cmd, list) else cmd)
+ (stdout, stderr) = result
+ return (stdout, (stderr, _returncode))
+ except ProcessExecutionError:
+ raise
+ finally:
+ sleep(0)
diff --git a/odl-pipeline/lib/utils/service.py b/odl-pipeline/lib/utils/service.py
new file mode 100755
index 0000000..39cdce5
--- /dev/null
+++ b/odl-pipeline/lib/utils/service.py
@@ -0,0 +1,67 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+import sys
+import yaml
+import argparse
+import traceback
+from utils_log import LOG, LOG_PATH
+from abc import abstractmethod
+
+
+class Service(object):
+
+ def start(self):
+ try:
+ self._run()
+ except Exception as ex:
+ LOG.error(ex.message)
+ LOG.error(traceback.format_exc())
+ LOG.error("For more logs check: %(log_path)s"
+ % {'log_path': LOG_PATH})
+ sys.exit(1)
+
+ def _run(self):
+ parser = self._create_cli_parser()
+ sys_args = parser.parse_args()
+ config = self.read_config(sys_args)
+ self.run(sys_args, config)
+
+ @abstractmethod
+ def run(self, sys_args, config):
+ # Do something
+ return
+
+ @abstractmethod
+ def create_cli_parser(self, parser):
+ # Read in own sys args
+ return parser
+
+ def _create_cli_parser(self):
+ parser = argparse.ArgumentParser(description='OVS Debugger')
+ # parser.add_argument('-c', '--config', help="Path to config.yaml",
+ # required=False)
+ # parser.add_argument('--boolean', help="",
+ # required=False, action='store_true')
+ return self.create_cli_parser(parser)
+
+ def read_config(self, sys_args):
+ if not hasattr(sys_args, 'config'):
+ return None
+ if not sys_args.config:
+ config_path = './etc/config.yaml'
+ else:
+ config_path = sys_args.config
+ try:
+ with open(config_path) as f:
+ return yaml.load(f)
+ except yaml.scanner.ScannerError as ex:
+ LOG.error("Yaml file corrupt. Try putting spaces after the "
+ "colons.")
+ raise ex
diff --git a/odl-pipeline/lib/utils/shutil.py b/odl-pipeline/lib/utils/shutil.py
new file mode 100755
index 0000000..40e2aba
--- /dev/null
+++ b/odl-pipeline/lib/utils/shutil.py
@@ -0,0 +1,66 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+import os
+import glob
+import processutils as putils
+
+
+class shutil():
+ '''
+ classdocs
+ '''
+ @staticmethod
+ def mkdir_if_not_exsist(path):
+ if not path:
+ raise Exception('Path should not be empty.')
+ putils.execute(["mkdir", "-p", path])
+
+ @staticmethod
+ def copy(direction, src, dst, **kwargs):
+ if direction == 'from':
+ dst_tmp = dst
+ dst = src
+ src = dst_tmp
+ if src[-1:] == '*':
+ files = glob.glob(src)
+ for file in files:
+ shutil._copy(file, dst, **kwargs)
+ else:
+ shutil._copy(src, dst, **kwargs)
+
+ @staticmethod
+ def _copy(src, dst, **kwargs):
+ if os.path.isfile(src):
+ if dst[-1:] == '/':
+ shutil.mkdir_if_not_exsist(dst)
+ putils.execute(['cp', src, dst], **kwargs)
+ else:
+ putils.execute(['cp', '-R', src, dst], **kwargs)
+
+ @staticmethod
+ def rm(path, **kwargs):
+ putils.execute(['rm', '-rf', path], **kwargs)
+
+ @staticmethod
+ def mv(src, dst):
+ putils.execute(["mv", src, dst])
+
+ @staticmethod
+ def get_all_files_in_path(path):
+ if os.path.exists(path):
+ return putils.execute(['l', path])
+
+ @staticmethod
+ def replace_string_in_file(file, str, replace):
+ with open(file, 'r') as f:
+ string = f.read()
+ string = string.replace(str, replace)
+ with open(file, 'w+') as f:
+ f.write(string)
diff --git a/odl-pipeline/lib/utils/ssh_client.py b/odl-pipeline/lib/utils/ssh_client.py
new file mode 100755
index 0000000..464a74e
--- /dev/null
+++ b/odl-pipeline/lib/utils/ssh_client.py
@@ -0,0 +1,88 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+from processutils import execute
+from ssh_util import SshUtil
+from node_manager import NodeManager
+import os
+from utils_log import LOG
+import glob
+
+
+class SSHClient(object):
+
+ def __init__(self, node):
+ self.node = node
+
+ def execute(self, cmd, **kwargs):
+ if 'log_true' in kwargs:
+ if kwargs['log_true']:
+ LOG.info('Node: %s Executing: %s' % (self.node.name, cmd))
+ kwargs.pop('log_true')
+ NodeManager.gen_ssh_config(self.node)
+ if not isinstance(cmd, str):
+ cmd = ' '.join(cmd)
+ cmd_addition = ['ssh', '-i', SshUtil.get_id_rsa(), '-F',
+ SshUtil.get_config_file_path(),
+ self.node.name]
+ if self.node.password:
+ cmd_addition = ['sshpass', '-p', self.node.password] + cmd_addition
+ if 'as_root' in kwargs:
+ kwargs.pop('as_root')
+ cmd = 'sudo ' + cmd
+ cmd_addition.append(cmd)
+ return execute(cmd_addition, **kwargs)
+
+ def copy(self, direction, local_path, remote_path, **kwargs):
+ all_files = None
+ if direction is 'to':
+ msg = ('Copying file %s to %s:%s' % (local_path, self.node.name,
+ remote_path))
+ if self.node.is_dir(remote_path):
+ pass
+ elif remote_path[-1:] == '/':
+ self.node.create_path_if_not_exsist(remote_path)
+ else:
+ # Remove the file
+ self.execute('rm -f %s' % remote_path, as_root=True)
+ self.node.create_path_if_not_exsist(
+ os.path.dirname(remote_path))
+ if '*' in local_path:
+ all_files = glob.glob(local_path)
+ else:
+ if local_path[-1:] == '/':
+ execute('mkdir -p %s' % local_path)
+ msg = ('Copying file from %s:%s to %s' % (self.node.name,
+ remote_path,
+ local_path))
+ LOG.info(msg)
+ if all_files:
+ for one_file in all_files:
+ return self._copy(direction, one_file, remote_path, **kwargs)
+ else:
+ return self._copy(direction, local_path, remote_path, **kwargs)
+
+ def _copy(self, direction, local_path, remote_path, **kwargs):
+ # TODO create dir is not existing
+ NodeManager.gen_ssh_config(self.node)
+ cmd = ['scp', '-i', SshUtil.get_id_rsa(), '-F',
+ SshUtil.get_config_file_path()]
+ if direction == 'to':
+ if os.path.isdir(local_path):
+ cmd.append('-r')
+ cmd = cmd + [local_path,
+ ('%s:%s') % (self.node.name, remote_path)]
+ if direction == 'from':
+ if self.node.is_dir(remote_path):
+ cmd.append('-r')
+ cmd = cmd + [('%s:%s') % (self.node.name, remote_path),
+ local_path]
+ if self.node.password:
+ cmd = ['sshpass', '-p', self.node.password] + cmd
+ return execute(cmd, **kwargs)
diff --git a/odl-pipeline/lib/utils/ssh_util.py b/odl-pipeline/lib/utils/ssh_util.py
new file mode 100755
index 0000000..e70aed3
--- /dev/null
+++ b/odl-pipeline/lib/utils/ssh_util.py
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+import os
+home = os.getenv("HOME")
+SSH_CONFIG = {'TMP_SSH_CONFIG': "./tmp/ssh_config",
+ 'ID_RSA_PATH': "%s/.ssh/id_rsa" % home}
+
+
+class SshUtil(object):
+
+ @staticmethod
+ def gen_ssh_config(node_list):
+ config = ["UserKnownHostsFile=/dev/null",
+ "StrictHostKeyChecking=no",
+ "ForwardAgent yes",
+ "GSSAPIAuthentication=no",
+ "LogLevel ERROR"]
+ for node in node_list:
+ config.append(node.to_ssh_config())
+ with open(SSH_CONFIG['TMP_SSH_CONFIG'], 'w') as f:
+ f.write('\n'.join(config))
+
+ @staticmethod
+ def get_config_file_path():
+ return SSH_CONFIG['TMP_SSH_CONFIG']
+
+ @staticmethod
+ def get_id_rsa():
+ return (SSH_CONFIG['ID_RSA_PATH'])
diff --git a/odl-pipeline/lib/utils/utils_log.py b/odl-pipeline/lib/utils/utils_log.py
new file mode 100755
index 0000000..e49434c
--- /dev/null
+++ b/odl-pipeline/lib/utils/utils_log.py
@@ -0,0 +1,67 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+import logging
+import datetime
+import os
+import sys
+
+LOG = logging.getLogger(__name__)
+LOG_LEVEL = logging.DEBUG
+LOG_PATH = "./tmp/%s.log" % os.path.basename(sys.argv[0])
+logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s',
+ filename=LOG_PATH, level=LOG_LEVEL)
+# datefmt='%Y-%m-%dT:%H:%M:%s', level=LOG_LEVEL)
+console = logging.StreamHandler()
+console.setLevel(logging.INFO)
+formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
+console.setFormatter(formatter)
+LOG.addHandler(console)
+
+
+def log_enter_exit(func):
+
+ def inner(self, *args, **kwargs):
+ LOG.debug(("Entering %(cls)s.%(method)s "
+ "args: %(args)s, kwargs: %(kwargs)s") %
+ {'cls': self.__class__.__name__,
+ 'method': func.__name__,
+ 'args': args,
+ 'kwargs': kwargs})
+ start = datetime.datetime.now()
+ ret = func(self, *args, **kwargs)
+ end = datetime.datetime.now()
+ LOG.debug(("Exiting %(cls)s.%(method)s. "
+ "Spent %(duration)s sec. "
+ "Return %(return)s") %
+ {'cls': self.__class__.__name__,
+ 'duration': end - start,
+ 'method': func.__name__,
+ 'return': ret})
+ return ret
+ return inner
+
+
+def for_all_methods(decorator):
+ # @for_all_methods(log_enter_exit)
+ # class ...
+
+ def decorate(cls):
+ for attr in cls.__dict__:
+ if callable(getattr(cls, attr)):
+ setattr(cls, attr, decorator(getattr(cls, attr)))
+ return cls
+ return decorate
+
+
+def dict_to_nice_string(dict):
+ return_string = []
+ for key, value in dict.iteritems():
+ return_string.append('%s: %s' % (key, value))
+ return ', '.join(return_string)
diff --git a/odl-pipeline/lib/utils/utils_yaml.py b/odl-pipeline/lib/utils/utils_yaml.py
new file mode 100755
index 0000000..f9513b8
--- /dev/null
+++ b/odl-pipeline/lib/utils/utils_yaml.py
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+import yaml
+
+
+def write_dict_to_yaml(config, path):
+ with open(path, 'w+') as f:
+ yaml.dump(config, f, default_flow_style=False)
+
+
+def read_dict_from_yaml(path):
+ with open(path, 'r') as f:
+ return yaml.load(f)