summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorPaul C. Buetow <paul@buetow.org>2014-04-15 22:31:14 +0200
committerPaul C. Buetow <paul@buetow.org>2014-04-15 22:31:14 +0200
commit0331deca1387a428a8ffb880e1261899842a6513 (patch)
tree3e439936a7459e34c397ed9ad4f4f84f4b53f060 /contrib
parentc6aae329b95f00ff6d20c203ca8e86d727ab5772 (diff)
add contrib dir
Diffstat (limited to 'contrib')
-rw-r--r--contrib/README.md4
-rw-r--r--contrib/bigsuds-1.0/CHANGELOG7
-rw-r--r--contrib/bigsuds-1.0/MANIFEST.in1
-rw-r--r--contrib/bigsuds-1.0/PKG-INFO18
-rw-r--r--contrib/bigsuds-1.0/bigsuds.egg-info/PKG-INFO18
-rw-r--r--contrib/bigsuds-1.0/bigsuds.egg-info/SOURCES.txt9
-rw-r--r--contrib/bigsuds-1.0/bigsuds.egg-info/dependency_links.txt1
-rw-r--r--contrib/bigsuds-1.0/bigsuds.egg-info/requires.txt1
-rw-r--r--contrib/bigsuds-1.0/bigsuds.egg-info/top_level.txt1
-rw-r--r--contrib/bigsuds-1.0/bigsuds.py619
-rw-r--r--contrib/bigsuds-1.0/setup.cfg5
-rw-r--r--contrib/bigsuds-1.0/setup.py34
12 files changed, 718 insertions, 0 deletions
diff --git a/contrib/README.md b/contrib/README.md
new file mode 100644
index 0000000..cd8e447
--- /dev/null
+++ b/contrib/README.md
@@ -0,0 +1,4 @@
+bigsuds
+=======
+
+See also https://devcentral.f5.com/d/bigsuds-python-icontrol-library
diff --git a/contrib/bigsuds-1.0/CHANGELOG b/contrib/bigsuds-1.0/CHANGELOG
new file mode 100644
index 0000000..05d770d
--- /dev/null
+++ b/contrib/bigsuds-1.0/CHANGELOG
@@ -0,0 +1,7 @@
+1.0.1 - 2013-01
+ - Enhanced debug/interactive capabilities (when BIGIP is instantiated with debug=True).
+ - Official iControl help documenation is available in the method docstrings.
+ - When bigsuds is used interactively in ipython, namespaces and methods will tab-complete.
+
+1.0.0 - 2012-11
+ - Initial release of bigsuds
diff --git a/contrib/bigsuds-1.0/MANIFEST.in b/contrib/bigsuds-1.0/MANIFEST.in
new file mode 100644
index 0000000..f7aec42
--- /dev/null
+++ b/contrib/bigsuds-1.0/MANIFEST.in
@@ -0,0 +1 @@
+include CHANGELOG
diff --git a/contrib/bigsuds-1.0/PKG-INFO b/contrib/bigsuds-1.0/PKG-INFO
new file mode 100644
index 0000000..a0ce4dc
--- /dev/null
+++ b/contrib/bigsuds-1.0/PKG-INFO
@@ -0,0 +1,18 @@
+Metadata-Version: 1.1
+Name: bigsuds
+Version: 1.0.1
+Summary: Library for F5 Networks iControl API
+Home-page: http://devcentral.f5.com
+Author: F5 Networks, Inc.
+Author-email: info@f5.com
+License: https://devcentral.f5.com/resources/devcentral-eula
+Description: UNKNOWN
+Keywords: f5 icontrol
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.4
+Classifier: Programming Language :: Python :: 2.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
diff --git a/contrib/bigsuds-1.0/bigsuds.egg-info/PKG-INFO b/contrib/bigsuds-1.0/bigsuds.egg-info/PKG-INFO
new file mode 100644
index 0000000..a0ce4dc
--- /dev/null
+++ b/contrib/bigsuds-1.0/bigsuds.egg-info/PKG-INFO
@@ -0,0 +1,18 @@
+Metadata-Version: 1.1
+Name: bigsuds
+Version: 1.0.1
+Summary: Library for F5 Networks iControl API
+Home-page: http://devcentral.f5.com
+Author: F5 Networks, Inc.
+Author-email: info@f5.com
+License: https://devcentral.f5.com/resources/devcentral-eula
+Description: UNKNOWN
+Keywords: f5 icontrol
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.4
+Classifier: Programming Language :: Python :: 2.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
diff --git a/contrib/bigsuds-1.0/bigsuds.egg-info/SOURCES.txt b/contrib/bigsuds-1.0/bigsuds.egg-info/SOURCES.txt
new file mode 100644
index 0000000..eedcdb7
--- /dev/null
+++ b/contrib/bigsuds-1.0/bigsuds.egg-info/SOURCES.txt
@@ -0,0 +1,9 @@
+CHANGELOG
+MANIFEST.in
+bigsuds.py
+setup.py
+bigsuds.egg-info/PKG-INFO
+bigsuds.egg-info/SOURCES.txt
+bigsuds.egg-info/dependency_links.txt
+bigsuds.egg-info/requires.txt
+bigsuds.egg-info/top_level.txt \ No newline at end of file
diff --git a/contrib/bigsuds-1.0/bigsuds.egg-info/dependency_links.txt b/contrib/bigsuds-1.0/bigsuds.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/contrib/bigsuds-1.0/bigsuds.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/contrib/bigsuds-1.0/bigsuds.egg-info/requires.txt b/contrib/bigsuds-1.0/bigsuds.egg-info/requires.txt
new file mode 100644
index 0000000..4ceef77
--- /dev/null
+++ b/contrib/bigsuds-1.0/bigsuds.egg-info/requires.txt
@@ -0,0 +1 @@
+suds>=0.4 \ No newline at end of file
diff --git a/contrib/bigsuds-1.0/bigsuds.egg-info/top_level.txt b/contrib/bigsuds-1.0/bigsuds.egg-info/top_level.txt
new file mode 100644
index 0000000..75f50ac
--- /dev/null
+++ b/contrib/bigsuds-1.0/bigsuds.egg-info/top_level.txt
@@ -0,0 +1 @@
+bigsuds
diff --git a/contrib/bigsuds-1.0/bigsuds.py b/contrib/bigsuds-1.0/bigsuds.py
new file mode 100644
index 0000000..68ed947
--- /dev/null
+++ b/contrib/bigsuds-1.0/bigsuds.py
@@ -0,0 +1,619 @@
+#!/usr/bin/env python
+"""An iControl client library.
+
+See the documentation for the L{BIGIP} class for usage examples.
+"""
+import httplib
+import logging
+import os
+import re
+import urllib2
+from xml.sax import SAXParseException
+
+import suds.client
+from suds.cache import ObjectCache
+from suds.sudsobject import Object as SudsObject
+from suds.client import Client
+from suds.xsd.doctor import ImportDoctor, Import
+from suds.transport import TransportError
+from suds import WebFault, TypeNotFound, MethodNotFound as _MethodNotFound
+
+__version__ = '1.0.1'
+
+
+# We need to monkey-patch the Client's ObjectCache due to a suds bug:
+# https://fedorahosted.org/suds/ticket/376
+suds.client.ObjectCache = lambda **kwargs: None
+
+
+log = logging.getLogger('bigsuds')
+
+
+class OperationFailed(Exception):
+ """Base class for bigsuds exceptions."""
+
+class ServerError(OperationFailed, WebFault):
+ """Raised when the BIGIP returns an error via the iControl interface."""
+
+class ConnectionError(OperationFailed):
+ """Raised when the connection to the BIGIP fails."""
+
+class ParseError(OperationFailed):
+ """Raised when parsing data from the BIGIP as a soap message fails.
+
+ This is also raised when an invalid iControl namespace
+ is looked up on the BIGIP (e.g. <bigip>.LocalLB.Bad).
+ """
+
+class MethodNotFound(OperationFailed, _MethodNotFound):
+ """Raised when a particular iControl method does not exist."""
+
+class ArgumentError(OperationFailed):
+ """Raised when too many arguments or incorrect keyword arguments
+ are passed to an iControl method."""
+
+
+class BIGIP(object):
+ """This class exposes the BIGIP's iControl interface.
+
+ Example usage:
+ >>> b = BIGIP('bigip-hostname')
+ >>> print b.LocalLB.Pool.get_list()
+ ['/Common/test_pool']
+ >>> b.LocalLB.Pool.add_member(['/Common/test_pool'], \
+ [[{'address': '10.10.10.10', 'port': 20030}]])
+ >>> print b.LocalLB.Pool.get_member(['/Common/test_pool'])
+ [[{'port': 20020, 'address': '10.10.10.10'},
+ {'port': 20030, 'address': '10.10.10.10'}]]
+
+ Some notes on Exceptions:
+ * The looking up of iControl namespaces on the L{BIGIP} instance can raise
+ L{ParseError} and L{ServerError}.
+ * The looking up of an iControl method can raise L{MethodNotFound}.
+ * Calling an iControl method can raise L{ServerError} when the BIGIP
+ reports an error via iControl, L{ConnectionError}, or L{MethodNotFound},
+ or L{ParseError} when the BIGIP return non-SOAP data, or
+ L{ArgumentError} when too many arguments are passed or invalid
+ keyword arguments are passed.
+ * All of these exceptions derive from L{OperationFailed}.
+ """
+ def __init__(self, hostname, username='admin', password='admin',
+ debug=False, cachedir=None):
+ """init
+
+ @param hostname: The IP address or hostname of the BIGIP.
+ @param username: The admin username on the BIGIP.
+ @param password: The admin password on the BIGIP.
+ @param debug: When True sets up additional interactive features
+ like the ability to introspect/tab-complete the list of method
+ names.
+ @param cachedir: The directory to cache wsdls in. None indicates
+ that caching should be disabled.
+ """
+ self._hostname = hostname
+ self._username = username
+ self._password = password
+ self._debug = debug
+ self._cachedir = cachedir
+ if debug:
+ self._instantiate_namespaces()
+
+ def with_session_id(self, session_id=None):
+ """Returns a new instance of L{BIGIP} that uses a unique session id.
+
+ @param session_id: The integer session id to use. If None, a new
+ session id will be requested from the BIGIP.
+ @return: A new instance of L{BIGIP}. All iControl calls made through
+ this new instance will use the unique session id. All calls made
+ through the L{BIGIP} that with_session_id() was called on will
+ continue to use that instances session id (or no session id if
+ it did not have one).
+
+ @raise: MethodNotFound: When no session_id is specified and the BIGIP
+ does not support sessions. Sessions are new in 11.0.0.
+ @raise: OperationFaled: When getting the session_id from the BIGIP
+ fails for some other reason.
+ """
+ if session_id is None:
+ session_id = self.System.Session.get_session_identifier()
+ return _BIGIPSession(self._hostname, session_id, self._username,
+ self._password, self._debug, self._cachedir)
+
+ def __getattr__(self, attr):
+ if attr.startswith('__'):
+ return getattr(super(BIGIP, self), attr)
+ if '_' in attr:
+ # Backwards compatability with pycontrol:
+ first, second = attr.split('_', 1)
+ return getattr(getattr(self, first), second)
+ ns = _Namespace(attr, self._create_client)
+ setattr(self, attr, ns)
+ return ns
+
+ def _create_client(self, wsdl_name):
+ try:
+ client = get_client(self._hostname, wsdl_name, self._username,
+ self._password, self._cachedir)
+ except SAXParseException, e:
+ raise ParseError('%s\nFailed to parse wsdl. Is "%s" a valid '
+ 'namespace?' % (e, wsdl_name))
+ # One situation that raises TransportError is when credentials are bad.
+ except (urllib2.URLError, TransportError), e:
+ raise ConnectionError(str(e))
+ return self._create_client_wrapper(client, wsdl_name)
+
+ def _create_client_wrapper(self, client, wsdl_name):
+ return _ClientWrapper(client,
+ self._arg_processor_factory,
+ _NativeResultProcessor,
+ wsdl_name,
+ self._debug)
+
+ def _arg_processor_factory(self, client, method):
+ return _DefaultArgProcessor(method, client.factory)
+
+ def _instantiate_namespaces(self):
+ wsdl_hierarchy = get_wsdls(self._hostname, self._username,
+ self._password)
+ for namespace, attr_list in wsdl_hierarchy.iteritems():
+ ns = getattr(self, namespace)
+ ns.set_attr_list(attr_list)
+
+class Transaction(object):
+ """This class is a context manager for iControl transactions.
+
+ Upon successful exit of the with statement, the transaction will be
+ submitted, otherwise it will be rolled back.
+
+ NOTE: This feature was added to BIGIP in version 11.0.0.
+
+ Example:
+ > bigip = BIGIP(<args>)
+ > with Transaction(bigip):
+ > <perform actions inside a transaction>
+
+ Example which creates a new session id for the transaction:
+ > bigip = BIGIP(<args>)
+ > with Transaction(bigip.use_session_id()) as bigip:
+ > <perform actions inside a transaction>
+ """
+ def __init__(self, bigip):
+ self.bigip = bigip
+
+ def __enter__(self):
+ self.bigip.System.Session.start_transaction()
+ return self.bigip
+
+ def __exit__(self, excy_type, exc_value, exc_tb):
+ if exc_tb is None:
+ self.bigip.System.Session.submit_transaction()
+ else:
+ try:
+ self.bigip.System.Session.rollback_transaction()
+ # Ignore ServerError. This happens if the transaction is already
+ # timed out. We don't want to ignore other errors, like
+ # ConnectionErrors.
+ except ServerError:
+ pass
+
+
+def get_client(hostname, wsdl_name, username='admin', password='admin',
+ cachedir=None):
+ """Returns and instance of suds.client.Client.
+
+ A separate client is used for each iControl WSDL/Namespace (e.g.
+ "LocalLB.Pool").
+
+ This function allows any suds exceptions to propagate up to the caller.
+
+ @param hostname: The IP address or hostname of the BIGIP.
+ @param wsdl_name: The iControl namespace (e.g. "LocalLB.Pool")
+ @param username: The admin username on the BIGIP.
+ @param password: The admin password on the BIGIP.
+ @param cachedir: The directory to cache wsdls in. None indicates
+ that caching should be disabled.
+ """
+ url = 'https://%s/iControl/iControlPortal.cgi?WSDL=%s' % (
+ hostname, wsdl_name)
+ imp = Import('http://schemas.xmlsoap.org/soap/encoding/')
+ imp.filter.add('urn:iControl')
+
+ if cachedir is not None:
+ cachedir = ObjectCache(location=os.path.expanduser(cachedir), days=1)
+
+ doctor = ImportDoctor(imp)
+ client = Client(url, doctor=doctor, username=username, password=password,
+ cache=cachedir)
+
+ # Without this, subsequent requests will use the actual hostname of the
+ # BIGIP, which is often times invalid.
+ client.set_options(location=url.split('?')[0])
+ client.factory.separator('_')
+ return client
+
+
+def get_wsdls(hostname, username='admin', password='admin'):
+ """Returns the set of all available WSDLs on this server
+
+ Used for providing introspection into the available namespaces and WSDLs
+ dynamically (e.g. when using iPython)
+
+ @param hostname: The IP address or hostname of the BIGIP.
+ @param username: The admin username on the BIGIP.
+ @param password: The admin password on the BIGIP.
+ """
+ url = 'https://%s/iControl/iControlPortal.cgi' % (hostname)
+ regex = re.compile(r'/iControl/iControlPortal.cgi\?WSDL=([^"]+)"')
+
+ auth_handler = urllib2.HTTPBasicAuthHandler()
+ # 10.1.0 has a realm of "BIG-IP"
+ auth_handler.add_password(uri='https://%s/' % (hostname), user=username, passwd=password,
+ realm="BIG-IP")
+ # 11.3.0 has a realm of "BIG-\IP". I'm not sure exactly when it changed.
+ auth_handler.add_password(uri='https://%s/' % (hostname), user=username, passwd=password,
+ realm="BIG\-IP")
+ opener = urllib2.build_opener(auth_handler)
+ try:
+ result = opener.open(url)
+ except urllib2.URLError, e:
+ raise ConnectionError(str(e))
+
+ wsdls = {}
+ for line in result.readlines():
+ result = regex.search(line)
+ if result:
+ namespace, rest = result.groups()[0].split(".", 1)
+ if namespace not in wsdls:
+ wsdls[namespace] = []
+ wsdls[namespace].append(rest)
+ return wsdls
+
+
+class _BIGIPSession(BIGIP):
+ def __init__(self, hostname, session_id, username='admin', password='admin',
+ debug=False, cachedir=None):
+ super(_BIGIPSession, self).__init__(hostname, username=username,
+ password=password, debug=debug, cachedir=cachedir)
+ self._headers = {'X-iControl-Session': str(session_id)}
+
+ def _create_client_wrapper(self, client, wsdl_name):
+ client.set_options(headers=self._headers)
+ return super(_BIGIPSession, self)._create_client_wrapper(client, wsdl_name)
+
+
+class _Namespace(object):
+ """Represents a top level iControl namespace.
+
+ Examples of this are "LocalLB", "System", etc.
+
+ The purpose of this class is to store context allowing iControl clients
+ to be looked up using only the remaining part of the namespace.
+ Example:
+ <LocalLB namespace>.Pool returns the iControl client for "LocalLB.Pool"
+ """
+ def __init__(self, name, client_creator):
+ """init
+
+ @param name: The high-level namespace (e.g "LocalLB").
+ @param client_creator: A function that will be passed the full
+ namespace string (e.g. "LocalLB.Pool") and should return
+ some type of iControl client.
+ """
+ self._name = name
+ self._client_creator = client_creator
+ self._attrs = []
+
+ def __dir__(self):
+ return sorted(set(dir(type(self)) + list(self.__dict__) +
+ self._attrs))
+
+ def __getattr__(self, attr):
+ if attr.startswith('__'):
+ return getattr(super(_Namespace, self), attr)
+ client = self._client_creator('%s.%s' % (self._name, attr))
+ setattr(self, attr, client)
+ return client
+
+ def set_attr_list(self, attr_list):
+ self._attrs = attr_list
+
+
+class _ClientWrapper(object):
+ """A wrapper class that abstracts/extends the suds client API.
+ """
+ def __init__(self, client, arg_processor_factory, result_processor_factory,
+ wsdl_name, debug=False):
+ """init
+
+ @param client: An instance of suds.client.Client.
+ @param arg_processor_factory: This will be called to create processors
+ for arguments before they are passed to suds methods. This callable
+ will be passed the suds method and factory and should return an
+ instance of L{_ArgProcessor}.
+ @param result_processor_factory: This will be called to create
+ processors for results returned from suds methods. This callable
+ will be passed no arguments and should return an instance of
+ L{_ResultProcessor}.
+ """
+ self._client = client
+ self._arg_factory = arg_processor_factory
+ self._result_factory = result_processor_factory
+ self._wsdl_name = wsdl_name
+ self._usage = {}
+
+ # This populates self.__dict__. Helpful for tab completion.
+ # I'm not sure if this slows things down much. Maybe we should just
+ # always do it.
+ if debug:
+ # Extract the documentation from the WSDL (before populating
+ # self.__dict__)
+ binding_el = client.wsdl.services[0].ports[0].binding[0]
+ for op in binding_el.getChildren("operation"):
+ usage = None
+ doc = op.getChild("documentation")
+ if doc is not None:
+ usage = doc.getText().strip()
+ self._usage[op.get("name")] = usage
+
+ for method in client.sd[0].ports[0][1]:
+ getattr(self, method[0])
+
+ def __getattr__(self, attr):
+ # Looks up the corresponding suds method and returns a wrapped version.
+ try:
+ method = getattr(self._client.service, attr)
+ except _MethodNotFound, e:
+ e.__class__ = MethodNotFound
+ raise
+
+ wrapper = _wrap_method(method,
+ self._wsdl_name,
+ self._arg_factory(self._client, method),
+ self._result_factory(),
+ attr in self._usage and self._usage[attr] or None)
+ setattr(self, attr, wrapper)
+ return wrapper
+
+ def __str__(self):
+ # The suds clients strings contain the entire soap API. This is really
+ # useful, so lets expose it.
+ return str(self._client)
+
+
+def _wrap_method(method, wsdl_name, arg_processor, result_processor, usage):
+ """
+ This function wraps a suds method and returns a new function which
+ provides argument/result processing.
+
+ Each time a method is called, the incoming args will be passed to the
+ specified arg_processor before being passed to the suds method.
+
+ The return value from the underlying suds method will be passed to the
+ specified result_processor prior to being returned to the caller.
+
+ @param method: A suds method (can be obtained via
+ client.service.<method_name>).
+ @param arg_processor: An instance of L{_ArgProcessor}.
+ @param result_processor: An instance of L{_ResultProcessor}.
+
+ """
+
+ icontrol_sig = "iControl signature: %s" % _method_string(method)
+
+ if usage:
+ usage += "\n\n%s" % icontrol_sig
+ else:
+ usage = "Wrapper for %s.%s\n\n%s" % (
+ wsdl_name, method.method.name, icontrol_sig)
+
+ def wrapped_method(*args, **kwargs):
+ log.debug('Executing iControl method: %s.%s(%s, %s)',
+ wsdl_name, method.method.name, args, kwargs)
+ args, kwargs = arg_processor.process(args, kwargs)
+ # This exception wrapping is purely for pycontrol compatability.
+ # Maybe we want to make this optional and put it in a separate class?
+ try:
+ result = method(*args, **kwargs)
+ except AttributeError:
+ # Oddly, his seems to happen when the wrong password is used.
+ raise ConnectionError('iControl call failed, possibly invalid '
+ 'credentials.')
+ except _MethodNotFound, e:
+ e.__class__ = MethodNotFound
+ raise
+ except WebFault, e:
+ e.__class__ = ServerError
+ raise
+ except urllib2.URLError, e:
+ raise ConnectionError('URLError: %s' % str(e))
+ except httplib.BadStatusLine, e:
+ raise ConnectionError('BadStatusLine: %s' % e)
+ except SAXParseException, e:
+ raise ParseError("Failed to parse the BIGIP's response. This "
+ "was likely caused by a 500 error message.")
+ return result_processor.process(result)
+
+ wrapped_method.__doc__ = usage
+ wrapped_method.__name__ = method.method.name.encode("utf-8")
+ # It's occasionally convenient to be able to grab the suds object directly
+ wrapped_method._method = method
+ return wrapped_method
+
+
+class _ArgProcessor(object):
+ """Base class for suds argument processors."""
+
+ def process(self, args, kwargs):
+ """This method is passed the user-specified args and kwargs.
+
+ @param args: The user specified positional arguements.
+ @param kwargs: The user specified keyword arguements.
+ @return: A tuple of (args, kwargs).
+ """
+ raise NotImplementedError('process')
+
+
+class _DefaultArgProcessor(_ArgProcessor):
+
+ def __init__(self, method, factory):
+ self._factory = factory
+ self._method = method
+ self._argspec = self._make_argspec(method)
+
+ def _make_argspec(self, method):
+ # Returns a list of tuples indicating the arg names and types.
+ # E.g., [('pool_names', 'Common.StringSequence')]
+ spec = []
+ for part in method.method.soap.input.body.parts:
+ spec.append((part.name, part.type[0]))
+ return spec
+
+ def process(self, args, kwargs):
+ return (self._process_args(args), self._process_kwargs(kwargs))
+
+ def _process_args(self, args):
+ newargs = []
+ for i, arg in enumerate(args):
+ try:
+ newargs.append(self._process_arg(self._argspec[i][1], arg))
+ except IndexError:
+ raise ArgumentError(
+ 'Too many arguments passed to method: %s' % (
+ _method_string(self._method)))
+ return newargs
+
+ def _process_kwargs(self, kwargs):
+ newkwargs = {}
+ for name, value in kwargs.items():
+ try:
+ argtype = [x[1] for x in self._argspec if x[0] == name][0]
+ newkwargs[name] = self._process_arg(argtype, value)
+ except IndexError:
+ raise ArgumentError(
+ 'Invalid keyword argument "%s" passed to method: %s' % (
+ name, _method_string(self._method)))
+ return newkwargs
+
+ def _process_arg(self, arg_type, value):
+ if isinstance(value, SudsObject):
+ # If the user explicitly created suds objects to pass in,
+ # we don't want to mess with them.
+ return value
+
+ if '.' not in arg_type and ':' not in arg_type:
+ # These are not iControl namespace types, they are part of:
+ # ns0 = "http://schemas.xmlsoap.org/soap/encoding/"
+ # From what I can tell, we don't need to send these to the factory.
+ # Sending them to the factory as-is actually fails to resolve, the
+ # type names would need the "ns0:" qualifier. Some examples of
+ # these types are: ns0:string, ns0:long, ns0:unsignedInt.
+ return value
+
+ try:
+ obj = self._factory.create(arg_type)
+ except TypeNotFound:
+ log.error('Failed to create type: %s', arg_type)
+ return value
+
+ if isinstance(value, dict):
+ for name, value in value.items():
+ # The new object we created has the type of each attribute
+ # accessible via the attribute's class name.
+ try:
+ class_name = getattr(obj, name).__class__.__name__
+ except AttributeError:
+ valid_attrs = ', '.join([x[0] for x in obj])
+ raise ArgumentError(
+ '"%s" is not a valid attribute for %s, '
+ 'expecting: %s' % (name, obj.__class__.__name__,
+ valid_attrs))
+ setattr(obj, name, self._process_arg(class_name, value))
+ return obj
+
+ array_type = self._array_type(obj)
+ if array_type is not None:
+ # This is a common mistake. We might as well catch it here.
+ if isinstance(value, basestring):
+ raise ArgumentError(
+ '%s needs an iterable, but was specified as a string: '
+ '"%s"' % (obj.__class__.__name__, value))
+ obj.items = [self._process_arg(array_type, x) for x in value]
+ return obj
+
+ # If this object doesn't have any attributes, then we know it's not
+ # a complex type or enum type. We'll want to skip the next validation
+ # step.
+ if not obj:
+ return value
+
+ # The passed in value doesn't belong to an array type and wasn't a
+ # complex type (no dictionary received). At this point we know that
+ # the object type has attributes associated with it. It's likely
+ # an enum, but could be an incorrect argument to a complex type (e.g.
+ # the user specified some other type when a dictionary is expected).
+ # Either way, this error is more helpful than what the BIGIP provides.
+ if value not in obj:
+ valid_values = ', '.join([x[0] for x in obj])
+ raise ArgumentError('"%s" is not a valid value for %s, expecting: '
+ '%s' % (value, obj.__class__.__name__,
+ valid_values))
+ return value
+
+ def _array_type(self, obj):
+ # Determines if the specified type is an array.
+ # If so, the type name of the elements is returned. Otherwise None
+ # is returned.
+ try:
+ attributes = obj.__metadata__.sxtype.attributes()
+ except AttributeError:
+ return None
+ # The type contained in the array is in one of the attributes.
+ # According to a suds docstring, the "aty" is the "soap-enc:arrayType".
+ # We need to find the attribute which has it.
+ for each in attributes:
+ if each[0].name == 'arrayType':
+ try:
+ return each[0].aty[0]
+ except AttributeError:
+ pass
+ return None
+
+
+class _ResultProcessor(object):
+ """Base class for suds result processors."""
+
+ def process(self, value):
+ """Processes the suds return value for the caller.
+
+ @param value: The return value from a suds method.
+ @return: The processed value.
+ """
+ raise NotImplementedError('process')
+
+
+class _NativeResultProcessor(_ResultProcessor):
+ def process(self, value):
+ return self._convert_to_native_type(value)
+
+ def _convert_to_native_type(self, value):
+ if isinstance(value, list):
+ return [self._convert_to_native_type(x) for x in value]
+ elif isinstance(value, SudsObject):
+ d = {}
+ for attr_name, attr_value in value:
+ d[attr_name] = self._convert_to_native_type(attr_value)
+ return d
+ elif isinstance(value, unicode):
+ # This handles suds.sax.text.Text as well, as it derives from
+ # unicode.
+ return str(value)
+ elif isinstance(value, long):
+ return int(value)
+ return value
+
+
+def _method_string(method):
+ parts = []
+ for part in method.method.soap.input.body.parts:
+ parts.append("%s %s" % (part.type[0], part.name))
+ return "%s(%s)" % (method.method.name, ', '.join(parts))
diff --git a/contrib/bigsuds-1.0/setup.cfg b/contrib/bigsuds-1.0/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/contrib/bigsuds-1.0/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/contrib/bigsuds-1.0/setup.py b/contrib/bigsuds-1.0/setup.py
new file mode 100644
index 0000000..0e0af95
--- /dev/null
+++ b/contrib/bigsuds-1.0/setup.py
@@ -0,0 +1,34 @@
+from setuptools import setup
+import re
+
+
+def extract_version(filename):
+ contents = open(filename).read()
+ match = re.search('^__version__\s+=\s+[\'"](.*)[\'"]\s*$', contents, re.MULTILINE)
+ if match is not None:
+ return match.group(1)
+
+
+setup(
+ name="bigsuds",
+ version=extract_version('bigsuds.py'),
+ description='Library for F5 Networks iControl API',
+ license='https://devcentral.f5.com/resources/devcentral-eula',
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Intended Audience :: Developers',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.4',
+ 'Programming Language :: Python :: 2.5',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ ],
+ keywords='f5 icontrol',
+ author='F5 Networks, Inc.',
+ author_email='info@f5.com',
+ url='http://devcentral.f5.com',
+ install_requires=['suds>=0.4'],
+ py_modules=['bigsuds'],
+ test_suite='nose.collector',
+ tests_require=['nose', 'mock', 'mox'],
+)