diff options
author | Jordan Augé <jordan.auge+fdio@email.com> | 2017-02-24 14:58:01 +0100 |
---|---|---|
committer | Jordan Augé <jordan.auge+fdio@cisco.com> | 2017-02-24 18:36:29 +0000 |
commit | 85a341d645b57b7cd88a26ed2ea0a314704240ea (patch) | |
tree | bdda2b35003aae20103a796f86daced160b8a730 /netmodel/util | |
parent | 9b30fc10fb1cbebe651e5a107e8ca5b24de54675 (diff) |
Initial commit: vICN
Change-Id: I7ce66c4e84a6a1921c63442f858b49e083adc7a7
Signed-off-by: Jordan Augé <jordan.auge+fdio@cisco.com>
Diffstat (limited to 'netmodel/util')
-rw-r--r-- | netmodel/util/__init__.py | 0 | ||||
-rw-r--r-- | netmodel/util/argparse.py | 22 | ||||
-rw-r--r-- | netmodel/util/color.py | 113 | ||||
-rw-r--r-- | netmodel/util/daemon.py | 245 | ||||
-rw-r--r-- | netmodel/util/debug.py | 45 | ||||
-rw-r--r-- | netmodel/util/deprecated.py | 35 | ||||
-rw-r--r-- | netmodel/util/log.py | 120 | ||||
-rw-r--r-- | netmodel/util/meta.py | 30 | ||||
-rw-r--r-- | netmodel/util/misc.py | 62 | ||||
-rw-r--r-- | netmodel/util/process.py | 35 | ||||
-rw-r--r-- | netmodel/util/sa_compat.py | 265 | ||||
-rw-r--r-- | netmodel/util/singleton.py | 41 | ||||
-rw-r--r-- | netmodel/util/socket.py | 31 | ||||
-rw-r--r-- | netmodel/util/toposort.py | 82 |
14 files changed, 1126 insertions, 0 deletions
diff --git a/netmodel/util/__init__.py b/netmodel/util/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/netmodel/util/__init__.py diff --git a/netmodel/util/argparse.py b/netmodel/util/argparse.py new file mode 100644 index 00000000..c9678922 --- /dev/null +++ b/netmodel/util/argparse.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 argparse + +class ArgumentParser(argparse.ArgumentParser): + pass diff --git a/netmodel/util/color.py b/netmodel/util/color.py new file mode 100644 index 00000000..55719469 --- /dev/null +++ b/netmodel/util/color.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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. +# + +# ANSI escape codes for terminals. +# X11 xterm: always works, all platforms +# cygwin dosbox: run through |cat and then colors work +# linux: works on console & gnome-terminal +# mac: untested + +BLACK = "\033[0;30m" +BLUE = "\033[0;34m" +GREEN = "\033[0;32m" +CYAN = "\033[0;36m" +RED = "\033[0;31m" +PURPLE = "\033[0;35m" +BROWN = "\033[0;33m" +GRAY = "\033[0;37m" +BOLDGRAY = "\033[1;30m" +BOLDBLUE = "\033[1;34m" +BOLDGREEN = "\033[1;32m" +BOLDCYAN = "\033[1;36m" +BOLDRED = "\033[1;31m" +BOLDPURPLE = "\033[1;35m" +BOLDYELLOW = "\033[1;33m" +WHITE = "\033[1;37m" + +MYCYAN = "\033[96m" +MYGREEN = '\033[92m' +MYBLUE = '\033[94m' +MYWARNING = '\033[93m' +MYRED = '\033[91m' +MYHEADER = '\033[95m' +MYEND = '\033[0m' + +NORMAL = "\033[0m" + +colors = { + 'white': "\033[1;37m", + 'yellow': "\033[1;33m", + 'green': "\033[1;32m", + 'blue': "\033[1;34m", + 'cyan': "\033[1;36m", + 'red': "\033[1;31m", + 'magenta': "\033[1;35m", + 'black': "\033[1;30m", + 'darkwhite': "\033[0;37m", + 'darkyellow': "\033[0;33m", + 'darkgreen': "\033[0;32m", + 'darkblue': "\033[0;34m", + 'darkcyan': "\033[0;36m", + 'darkred': "\033[0;31m", + 'darkmagenta': "\033[0;35m", + 'darkblack': "\033[0;30m", + 'off': "\033[0;0m" +} + +def textcolor(color, string): + """ + This function is useful to output information to the stdout by exploiting + different colors, depending on the result of the last command executed. + + It is possible to chose one of the following colors: + - white + - yellow + - green + - blue + - cyan + - red + - magenta + - black + - darkwhite + - darkyellow + - darkgreen + - darkblue + - darkcyan + - darkred + - darkmagenta + - darkblack + - off + + :param color: The color of the output string, chosen from the previous + list. + :param string: The string to color + :return: The colored string if the color is valid, the original string + otherwise. + """ + + try: + return colors[color] + string + colors['off'] + except: + return string + +if __name__ == '__main__': + # Display color names in their color + for name, color in locals().items(): + if name.startswith('__'): continue + print(color, name, MYEND) + diff --git a/netmodel/util/daemon.py b/netmodel/util/daemon.py new file mode 100644 index 00000000..29683a54 --- /dev/null +++ b/netmodel/util/daemon.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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. +# + +# see also: http://www.jejik.com/files/examples/daemon3x.py + +# This is used to import the daemon package instead of the local module which is +# named identically... +from __future__ import absolute_import + +import os +import sys +import time +import logging +import atexit +import signal +import lockfile +import traceback + +log = logging.getLogger(__name__) + +class Daemon: + + #-------------------------------------------------------------------------- + # Checks + #-------------------------------------------------------------------------- + + def check_python_daemon(self): + """ + Check whether python-daemon is properly installed. + Returns: + True iiff everything is fine. + """ + # http://www.python.org/dev/peps/pep-3143/ + ret = False + try: + import daemon + getattr(daemon, "DaemonContext") + ret = True + except AttributeError as e: + # daemon and python-daemon conflict with each other + log.critical("Please install python-daemon instead of daemon." \ + "Remove daemon first.") + except ImportError: + log.critical("Please install python-daemon.") + return ret + + #-------------------------------------------------------------------------- + # Initialization + #-------------------------------------------------------------------------- + + def __init__(self, name, + uid = os.getuid(), + gid = os.getgid(), + working_directory = '/', + debug_mode = False, + no_daemon = False, + pid_filename = None + ): + self._name = name + self._uid = uid + self._gid = gid + self._working_directory = working_directory + self._debug_mode = debug_mode + self._no_daemon = no_daemon + self._pid_filename = pid_filename if pid_filename \ + else '/var/run/{}.pid'.format(name) + + # Reference which file descriptors must remain opened while + # daemonizing (for instance the file descriptor related to + # the logger, a socket file created before daemonization etc.) + self._files_to_keep = list() + self._lock_file = None + + self.initialize() + + #------------------------------------------------------------------------ + + + def remove_pid_file(self): + """ + (Internal usage) + Remove the PID file + """ + if os.path.exists(self._pid_filename) == True: + log.info("Removing %s" % self._pid_filename) + os.remove(self._pid_filename) + + if self._lock_file and self._lock_file.is_locked(): + self._lock_file.release() + + def make_pid_file(self): + """ + Create a PID file if required in which we store the PID of the daemon + if needed + """ + if self._pid_filename and not self._no_daemon: + atexit.register(self.remove_pid_file) + open(self._pid_filename, "w+").write("%s\n" % str(os.getpid())) + + def get_pid_from_pid_file(self): + """ + Retrieve the PID of the daemon thanks to the pid file. + Returns: + An integer containing the PID of this daemon. + None if the pid file is not readable or does not exists + """ + pid = None + if self._pid_filename: + try: + f_pid = file(self._pid_filename, "r") + pid = int(f_pid.read().strip()) + f_pid.close() + except IOError: + pid = None + return pid + + def make_lock_file(self): + """ + Prepare the lock file required to manage the pid file. + Initialize self.lock_file + Returns: + True iif successful. + """ + if self._pid_filename and not self._no_daemon: + log.debug("Daemonizing using pid file '%s'" % self._pid_filename) + self.lock_file = lockfile.FileLock(self._pid_filename) + if self.lock_file.is_locked() == True: + log.error("'%s' is already running ('%s' is locked)." % \ + (self._name, self._pid_filename)) + return False + self.lock_file.acquire() + else: + self.lock_file = None + return True + + def start(self): + """ + Start the daemon. + """ + # Check whether daemon module is properly installed + if self.check_python_daemon() == False: + self._terminate() + import daemon + + # Prepare self.lock_file + if not self.make_lock_file(): + sys.exit(1) + + # We might need to preserve a few files from logging handlers + files_to_keep = list() + #for handler in log.handlers: + # preserve_files + + if self._no_daemon: + self.main() + return + + # Prepare the daemon context + dcontext = daemon.DaemonContext( + detach_process = not self._no_daemon, + working_directory = self._working_directory, + pidfile = self.lock_file, + stdin = sys.stdin, + stdout = sys.stdout, + stderr = sys.stderr, + uid = self._uid, + gid = self._gid, + files_preserve = files_to_keep + ) + + # Prepare signal handling to stop properly if the daemon is killed + # Note that signal.SIGKILL can't be handled: + # http://crunchtools.com/unixlinux-signals-101/ + dcontext.signal_map = { + signal.SIGTERM : self.signal_handler, + signal.SIGQUIT : self.signal_handler, + signal.SIGINT : self.signal_handler + } + + with dcontext: + log.info("Entering daemonization") + self.make_pid_file() + + try: + self.main() + except Exception as e: + log.error("Unhandled exception in start: %s" % e) + log.error(traceback.format_exc()) + finally: + self._terminate() + + def signal_handler(self, signal_id, frame): + """ + (Internal use) + Stop the daemon (signal handler) + Args: + signal_id: The integer identifying the signal + (see also "man 7 signal") + Example: 15 if the received signal is signal.SIGTERM + frame: + """ + self._terminate() + + def _terminate(self): + """ + Stops gracefully the daemon. + Note: + The lockfile should implicitly released by the daemon package. + """ + log.info("Stopping %s" % self.__class__.__name__) + self.terminate() + self.remove_pid_file() + self.leave() + + def leave(self): + """ + Overload this method if you use twisted (see xmlrpc.py) + """ + sys.exit(0) + + # Overload these... + + def initialize(self): + pass + + def main(self): + raise NotImplementedError + + def terminate(self): + pass diff --git a/netmodel/util/debug.py b/netmodel/util/debug.py new file mode 100644 index 00000000..dfa7d127 --- /dev/null +++ b/netmodel/util/debug.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# FROM: https://gist.github.com/techtonik/2151727 +# Public Domain, i.e. feel free to copy/paste +# Considered a hack in Python 2 +# + +import inspect +import traceback + +def print_call_stack(): + for line in traceback.format_stack(): + print(line.strip()) + +def caller_name(skip=2): + """Get a name of a caller in the format module.class.method + + `skip` specifies how many levels of stack to skip while getting caller + name. skip=1 means "who calls me", skip=2 "who calls my caller" etc. + + An empty string is returned if skipped levels exceed stack height + """ + stack = inspect.stack() + start = 0 + skip + if len(stack) < start + 1: + return '' + parentframe = stack[start][0] + + name = [] + module = inspect.getmodule(parentframe) + # `modname` can be None when frame is executed directly in console + if module: + name.append(module.__name__) + # detect classname + if 'self' in parentframe.f_locals: + # there seems to be no way to detect static method call - it will + # be just a function call + name.append(parentframe.f_locals['self'].__class__.__name__) + codename = parentframe.f_code.co_name + if codename != '<module>': # top level usually + name.append( codename ) # function or a method + del parentframe + return ".".join(name) + diff --git a/netmodel/util/deprecated.py b/netmodel/util/deprecated.py new file mode 100644 index 00000000..faa80ac2 --- /dev/null +++ b/netmodel/util/deprecated.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 warnings +import functools + +def deprecated(func): + """This is a decorator which can be used to mark functions + as deprecated. It will result in a warning being emmitted + when the function is used.""" + + @functools.wraps(func) + def new_func(*args, **kwargs): + #warnings.simplefilter('always', DeprecationWarning) + warnings.warn("Call to deprecated function {}.".format(func.__name__),\ + category=DeprecationWarning, stacklevel=2) + #warnings.simplefilter('default', DeprecationWarning) + return func(*args, **kwargs) + + return new_func diff --git a/netmodel/util/log.py b/netmodel/util/log.py new file mode 100644 index 00000000..68eb9a7f --- /dev/null +++ b/netmodel/util/log.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 +import logging.config +import os +import sys + +colors = { + 'white': "\033[1;37m", + 'yellow': "\033[1;33m", + 'green': "\033[1;32m", + 'blue': "\033[1;34m", + 'cyan': "\033[1;36m", + 'red': "\033[1;31m", + 'magenta': "\033[1;35m", + 'black': "\033[1;30m", + 'darkwhite': "\033[0;37m", + 'darkyellow': "\033[0;33m", + 'darkgreen': "\033[0;32m", + 'darkblue': "\033[0;34m", + 'darkcyan': "\033[0;36m", + 'darkred': "\033[0;31m", + 'darkmagenta': "\033[0;35m", + 'darkblack': "\033[0;30m", + 'off': "\033[0;0m" +} + +def textcolor(color, string): + """ + This function is useful to output information to the stdout by exploiting + different colors, depending on the result of the last command executed. + + It is possible to chose one of the following colors: white, yellow, green, + blue, cyan, red, magenta, black, darkwhite, darkyellow, darkgreen, + darkblue, darkcyan, darkred, darkmagenta, darkblack, off + + :param color: The color of the output string, chosen from the previous + list. + :param string: The string to color + :return: The colored string if the color is valid, the original string + otherwise. + """ + try: + return colors[color] + string + colors['off'] + except: + return string + +FMT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + +MAP_LEVELNAME_COLOR = { + 'DEBUG' : 'off', + 'INFO' : 'cyan', + 'WARNING' : 'yellow', + 'ERROR' : 'red', + 'CRITICAL' : 'red' +} + +DEFAULT_COLOR = 'blue' + +DEPRECATED_REPEAT = False +DEPRECATED_DONE = list() + +DEBUG_PATHS = 'vicn' + +class DebugPathsFilter: + def __init__(self, debug_paths): + self._debug_paths = debug_paths + + def filter(self, record): + return record.levelname != 'DEBUG' or record.name in self._debug_paths + +class ColorFormatter(logging.Formatter): + def __init__(self, *args, debug_paths = None, **kwargs): + self._debug_paths = debug_paths + super().__init__(*args, **kwargs) + + def format(self, record): + formatted = super().format(record) + + try: + color = record.category + except AttributeError: + color = MAP_LEVELNAME_COLOR.get(record.levelname, DEFAULT_COLOR) + + return textcolor(color, formatted) + +def initialize_logging(): + # Use logger config + config_path = os.path.join(os.path.dirname(__file__), + os.path.pardir, os.path.pardir, 'config', 'logging.conf') + if os.path.exists(config_path): + logging.config.fileConfig(config_path, disable_existing_loggers=False) + + root = logging.getLogger() + root.setLevel(logging.DEBUG) + + # Stdout handler + ch = logging.StreamHandler(sys.stdout) + ch.setLevel(logging.INFO) + #formatter = logging.Formatter(FMT) + formatter = ColorFormatter(FMT, debug_paths = DEBUG_PATHS) + ch.setFormatter(formatter) + ch.addFilter(DebugPathsFilter(DEBUG_PATHS)) + root.addHandler(ch) diff --git a/netmodel/util/meta.py b/netmodel/util/meta.py new file mode 100644 index 00000000..355beb7e --- /dev/null +++ b/netmodel/util/meta.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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. +# + +# http://stackoverflow.com/questions/5881873/python-find-all-classes-which-inherit-from-this-one + +def inheritors(klass): + subclasses = set() + work = [klass] + while work: + parent = work.pop() + for child in parent.__subclasses__(): + if child not in subclasses: + subclasses.add(child) + work.append(child) + return subclasses diff --git a/netmodel/util/misc.py b/netmodel/util/misc.py new file mode 100644 index 00000000..315887b3 --- /dev/null +++ b/netmodel/util/misc.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 collections + +def is_iterable(x): + return isinstance(x, collections.Iterable) and not isinstance(x, str) + +#------------------------------------------------------------------------------ +from itertools import tee + +# https://docs.python.org/3/library/itertools.html#itertools-recipes +def pairwise(iterable): + "s -> (s0,s1), (s1,s2), (s2, s3), ..." + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + +#------------------------------------------------------------------------------ +# http://stackoverflow.com/questions/1630320/what-is-the-pythonic-way-to-detect-the-last-element-in-a-python-for-loop +def lookahead(iterable): + it = iter(iterable) + last = next(it) + for val in it: + yield last, False + last = val + yield last, True + +#------------------------------------------------------------------------------ +# http://stackoverflow.com/questions/10840533/most-pythonic-way-to-delete-a-file-which-may-not-exist + +import os, errno + +def silentremove(filename): + try: + os.remove(filename) + except OSError as e: # this would be "except OSError, e:" before Python 2.6 + if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory + raise # re-raise exception if a different error occured + +#------------------------------------------------------------------------------ +import socket + +def is_local_host(hostname): + return hostname in ['localhost', '127.0.0.1'] or \ + hostname == socket.gethostname() diff --git a/netmodel/util/process.py b/netmodel/util/process.py new file mode 100644 index 00000000..954c0098 --- /dev/null +++ b/netmodel/util/process.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 subprocess +import shlex + +from vicn.core.commands import ReturnValue + +def execute_local(cmd, output=True): + cmd = shlex.split(cmd) + if output: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + ret = p.returncode + return ReturnValue(ret, stdout, stderr) + else: + p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL) + ret = p.wait() + return ReturnValue(ret) + diff --git a/netmodel/util/sa_compat.py b/netmodel/util/sa_compat.py new file mode 100644 index 00000000..ee4a20f9 --- /dev/null +++ b/netmodel/util/sa_compat.py @@ -0,0 +1,265 @@ +# util/compat.py +# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors +# <see AUTHORS file> +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""Handle Python version/platform incompatibilities.""" + +import sys + +try: + import threading +except ImportError: + import dummy_threading as threading + +py36 = sys.version_info >= (3, 6) +py33 = sys.version_info >= (3, 3) +py32 = sys.version_info >= (3, 2) +py3k = sys.version_info >= (3, 0) +py2k = sys.version_info < (3, 0) +py265 = sys.version_info >= (2, 6, 5) +jython = sys.platform.startswith('java') +pypy = hasattr(sys, 'pypy_version_info') +win32 = sys.platform.startswith('win') +cpython = not pypy and not jython # TODO: something better for this ? + +import collections +next = next + +if py3k: + import pickle +else: + try: + import cPickle as pickle + except ImportError: + import pickle + +# work around http://bugs.python.org/issue2646 +if py265: + safe_kwarg = lambda arg: arg +else: + safe_kwarg = str + +ArgSpec = collections.namedtuple("ArgSpec", + ["args", "varargs", "keywords", "defaults"]) + +if py3k: + import builtins + + from inspect import getfullargspec as inspect_getfullargspec + from urllib.parse import (quote_plus, unquote_plus, + parse_qsl, quote, unquote) + import configparser + from io import StringIO + + from io import BytesIO as byte_buffer + + def inspect_getargspec(func): + return ArgSpec( + *inspect_getfullargspec(func)[0:4] + ) + + string_types = str, + binary_types = bytes, + binary_type = bytes + text_type = str + int_types = int, + iterbytes = iter + + def u(s): + return s + + def ue(s): + return s + + def b(s): + return s.encode("latin-1") + + if py32: + callable = callable + else: + def callable(fn): + return hasattr(fn, '__call__') + + def cmp(a, b): + return (a > b) - (a < b) + + from functools import reduce + + print_ = getattr(builtins, "print") + + import_ = getattr(builtins, '__import__') + + import itertools + itertools_filterfalse = itertools.filterfalse + itertools_filter = filter + itertools_imap = map + from itertools import zip_longest + + import base64 + + def b64encode(x): + return base64.b64encode(x).decode('ascii') + + def b64decode(x): + return base64.b64decode(x.encode('ascii')) + +else: + from inspect import getargspec as inspect_getfullargspec + inspect_getargspec = inspect_getfullargspec + from urllib import quote_plus, unquote_plus, quote, unquote + from urlparse import parse_qsl + import ConfigParser as configparser + from StringIO import StringIO + from cStringIO import StringIO as byte_buffer + + string_types = basestring, + binary_types = bytes, + binary_type = str + text_type = unicode + int_types = int, long + + def iterbytes(buf): + return (ord(byte) for byte in buf) + + def u(s): + # this differs from what six does, which doesn't support non-ASCII + # strings - we only use u() with + # literal source strings, and all our source files with non-ascii + # in them (all are tests) are utf-8 encoded. + return unicode(s, "utf-8") + + def ue(s): + return unicode(s, "unicode_escape") + + def b(s): + return s + + def import_(*args): + if len(args) == 4: + args = args[0:3] + ([str(arg) for arg in args[3]],) + return __import__(*args) + + callable = callable + cmp = cmp + reduce = reduce + + import base64 + b64encode = base64.b64encode + b64decode = base64.b64decode + + def print_(*args, **kwargs): + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + for arg in enumerate(args): + if not isinstance(arg, basestring): + arg = str(arg) + fp.write(arg) + + import itertools + itertools_filterfalse = itertools.ifilterfalse + itertools_filter = itertools.ifilter + itertools_imap = itertools.imap + from itertools import izip_longest as zip_longest + + +import time +if win32 or jython: + time_func = time.clock +else: + time_func = time.time + +from collections import namedtuple +from operator import attrgetter as dottedgetter + + +if py3k: + def reraise(tp, value, tb=None, cause=None): + if cause is not None: + assert cause is not value, "Same cause emitted" + value.__cause__ = cause + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + # not as nice as that of Py3K, but at least preserves + # the code line where the issue occurred + exec("def reraise(tp, value, tb=None, cause=None):\n" + " if cause is not None:\n" + " assert cause is not value, 'Same cause emitted'\n" + " raise tp, value, tb\n") + + +def raise_from_cause(exception, exc_info=None): + if exc_info is None: + exc_info = sys.exc_info() + exc_type, exc_value, exc_tb = exc_info + cause = exc_value if exc_value is not exception else None + reraise(type(exception), exception, tb=exc_tb, cause=cause) + +if py3k: + exec_ = getattr(builtins, 'exec') +else: + def exec_(func_text, globals_, lcl=None): + if lcl is None: + exec('exec func_text in globals_') + else: + exec('exec func_text in globals_, lcl') + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass. + + Drops the middle class upon creation. + + Source: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ + + """ + + class metaclass(meta): + __call__ = type.__call__ + __init__ = type.__init__ + + def __new__(cls, name, this_bases, d): + if this_bases is None: + return type.__new__(cls, name, (), d) + return meta(name, bases, d) + return metaclass('temporary_class', None, {}) + + +from contextlib import contextmanager + +try: + from contextlib import nested +except ImportError: + # removed in py3k, credit to mitsuhiko for + # workaround + + @contextmanager + def nested(*managers): + exits = [] + vars = [] + exc = (None, None, None) + try: + for mgr in managers: + exit = mgr.__exit__ + enter = mgr.__enter__ + vars.append(enter()) + exits.append(exit) + yield vars + except: + exc = sys.exc_info() + finally: + while exits: + exit = exits.pop() + try: + if exit(*exc): + exc = (None, None, None) + except: + exc = sys.exc_info() + if exc != (None, None, None): + reraise(exc[0], exc[1], exc[2]) diff --git a/netmodel/util/singleton.py b/netmodel/util/singleton.py new file mode 100644 index 00000000..4aaff9ee --- /dev/null +++ b/netmodel/util/singleton.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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. +# + +class Singleton(type): + """ + Classes that inherit from Singleton can be instanciated only once + + See also + http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python + """ + + def __init__(cls, name, bases, dic): + super(Singleton,cls).__init__(name,bases,dic) + cls.instance=None + + def __call__(cls, *args, **kw): + if cls.instance is None: + cls.instance=super(Singleton,cls).__call__(*args,**kw) + return cls.instance + + def _drop(cls): + "Drop the instance (for testing purposes)." + if cls.instance is not None: + del cls.instance + cls.instance = None + diff --git a/netmodel/util/socket.py b/netmodel/util/socket.py new file mode 100644 index 00000000..e0cab384 --- /dev/null +++ b/netmodel/util/socket.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 +import socket + +log = logging.getLogger(__name__) + +def check_port(address, port): + log.info('Check port status for {address}:{port}'.format(**locals())) + s = socket.socket() + try: + s.connect((address, port)) + return True + except socket.error as e: + return False diff --git a/netmodel/util/toposort.py b/netmodel/util/toposort.py new file mode 100644 index 00000000..64931c32 --- /dev/null +++ b/netmodel/util/toposort.py @@ -0,0 +1,82 @@ +####################################################################### +# Implements a topological sort algorithm. +# +# Copyright 2014 True Blade Systems, Inc. +# +# 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. +# +# Notes: +# Based on http://code.activestate.com/recipes/578272-topological-sort +# with these major changes: +# Added unittests. +# Deleted doctests (maybe not the best idea in the world, but it cleans +# up the docstring). +# Moved functools import to the top of the file. +# Changed assert to a ValueError. +# Changed iter[items|keys] to [items|keys], for python 3 +# compatibility. I don't think it matters for python 2 these are +# now lists instead of iterables. +# Copy the input so as to leave it unmodified. +# Renamed function from toposort2 to toposort. +# Handle empty input. +# Switch tests to use set literals. +# +######################################################################## + +from functools import reduce as _reduce + +__all__ = ['toposort', 'toposort_flatten'] + +def toposort(data): + """Dependencies are expressed as a dictionary whose keys are items +and whose values are a set of dependent items. Output is a list of +sets in topological order. The first set consists of items with no +dependences, each subsequent set consists of items that depend upon +items in the preceeding sets. +""" + + # Special case empty input. + if len(data) == 0: + return + + # Copy the input so as to leave it unmodified. + data = data.copy() + + # Ignore self dependencies. + for k, v in data.items(): + v.discard(k) + # Find all items that don't depend on anything. + extra_items_in_deps = _reduce(set.union, data.values()) - set(data.keys()) + # Add empty dependences where needed. + data.update({item:set() for item in extra_items_in_deps}) + while True: + ordered = set(item for item, dep in data.items() if len(dep) == 0) + if not ordered: + break + yield ordered + data = {item: (dep - ordered) + for item, dep in data.items() + if item not in ordered} + if len(data) != 0: + raise ValueError('Cyclic dependencies exist among these items: {}'.format(', '.join(repr(x) for x in data.items()))) + + +def toposort_flatten(data, sort=True): + """Returns a single list of dependencies. For any set returned by +toposort(), those items are sorted and appended to the result (just to +make the results deterministic).""" + + result = [] + for d in toposort(data): + result.extend((sorted if sort else list)(d)) + return result |