aboutsummaryrefslogtreecommitdiffstats
path: root/netmodel/util
diff options
context:
space:
mode:
authorJordan Augé <jordan.auge+fdio@email.com>2017-02-24 14:58:01 +0100
committerJordan Augé <jordan.auge+fdio@cisco.com>2017-02-24 18:36:29 +0000
commit85a341d645b57b7cd88a26ed2ea0a314704240ea (patch)
treebdda2b35003aae20103a796f86daced160b8a730 /netmodel/util
parent9b30fc10fb1cbebe651e5a107e8ca5b24de54675 (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__.py0
-rw-r--r--netmodel/util/argparse.py22
-rw-r--r--netmodel/util/color.py113
-rw-r--r--netmodel/util/daemon.py245
-rw-r--r--netmodel/util/debug.py45
-rw-r--r--netmodel/util/deprecated.py35
-rw-r--r--netmodel/util/log.py120
-rw-r--r--netmodel/util/meta.py30
-rw-r--r--netmodel/util/misc.py62
-rw-r--r--netmodel/util/process.py35
-rw-r--r--netmodel/util/sa_compat.py265
-rw-r--r--netmodel/util/singleton.py41
-rw-r--r--netmodel/util/socket.py31
-rw-r--r--netmodel/util/toposort.py82
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