# -*- coding: utf8 -*-
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
from pylero._compatible import builtins,object,urlparse
import logging
import time
import suds.sax.element
import ssl
from suds.plugin import MessagePlugin
from suds.sax.attribute import Attribute
logger = logging.getLogger(__name__)
CERT_PATH = None
# the reason why this function definition is at the top is because it is
# assigned to "ssl._create_default_https_context", few lines below
[docs]def create_ssl_context():
"""this function creates a custom ssl context which is required for ssl
connection in python-version >=2.7.10. this ssl context is customize to use
certificate which is located in 'CERT_PATH'.
"""
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
context.load_verify_locations(CERT_PATH)
return context
[docs]class SoapNull(MessagePlugin):
"""suds plugin that is called before any suds message is sent to the remote
server. It adds the xsi:nil=true attribute to any element that is blank.
Without this plugin, a number of functions that were supposed to accept
null parameters did not work.
"""
[docs] def marshalled(self, context):
# Go through every node in the document and check if it is empty and
# if so set the xsi:nil tag to true
context.envelope.walk(self.add_nil)
[docs] def add_nil(self, element):
"""Used as a filter function with walk to add xsi:nil to blank attrs.
"""
if element.isempty() and not element.isnil():
element.attributes.append(Attribute('xsi:nil', 'true'))
[docs]class Session(object):
def _url_for_name(self, service_name):
"""generate the full URL for the WSDL client services"""
return '{0}/ws/services/{1}WebService?wsdl'.format(self._server.url,
service_name)
[docs] def __init__(self, server, timeout):
"""Session constructor, initialize the WSDL clients
Args:
server: server object that the session connects to
caching_policy: determines the caching policy of the SUDS conn
timeout: HTTP timeout for the connection
"""
self._server = server
self._last_request_at = None
self._session_id_header = None
self._cookies = None
self._session_client = _SudsClientWrapper(
self._url_for_name('Session'), None, timeout)
self.builder_client = _SudsClientWrapper(
self._url_for_name('Builder'), self, timeout)
self.planning_client = _SudsClientWrapper(
self._url_for_name('Planning'), self, timeout)
self.project_client = _SudsClientWrapper(
self._url_for_name('Project'), self, timeout)
self.security_client = _SudsClientWrapper(
self._url_for_name('Security'), self, timeout)
self.test_management_client = _SudsClientWrapper(
self._url_for_name('TestManagement'), self, timeout)
self.tracker_client = _SudsClientWrapper(
self._url_for_name('Tracker'), self, timeout)
# This block forces ssl certificate verification
if self._server.cert_path:
global CERT_PATH
CERT_PATH = self._server.cert_path
ssl._create_default_https_context = create_ssl_context
def _login(self):
"""login to the Polarion API"""
sc = self._session_client
sc.service.logIn(self._server.login, self._server.password)
id_element = sc.last_received(). \
childAtPath('Envelope/Header/sessionID')
session_id = id_element.text
session_ns = id_element.namespace()
self._session_id_header = suds.sax.element.Element(
'sessionID', ns=session_ns).setText(session_id)
self._cookies = sc.options.transport.cookiejar
sc.set_options(soapheaders=self._session_id_header)
self._last_request_at = time.time()
def _logout(self):
"""logout from Polarion server"""
self._session_client.service.endSession()
def _reauth(self):
"""auto relogin after timeout, set in the getattr function of each
client obj
"""
sc = self._session_client
duration = time.time() - self._last_request_at
if duration > self._server.relogin_timeout and not \
sc.service.hasSubject():
logger.debug("Session expired, trying to log in again")
self._login()
else:
self._last_request_at = time.time()
[docs] def tx_begin(self):
self._session_client.service.beginTransaction()
[docs] def tx_commit(self):
self._session_client.service.endTransaction(False)
[docs] def tx_rollback(self):
self._session_client.service.endTransaction(True)
[docs] def tx_release(self):
if self._session_client.service.transactionExists():
self.tx_rollback()
[docs] def tx_in(self):
"""Function checks if a transaction is in progress. You can not have a
transaction within another transaction. This function helps the system
determine if it should start a new transaction or if it is already in
the middle of one.
Returns:
bool
"""
return self._session_client.service.transactionExists()
class _SudsClientWrapper(object):
"""class that manages the WSDL clients"""
def __init__(self, url, enclosing_session, timeout):
"""has the actual WSDL client as a private _suds_client attribute so
that the "magic" __getattr__ function will be able to verify
functions called on it and after processing to call the WSDL function
Args:
url (str): the URL of the Polarion server.
enclosing_session: the HTTP session that the requests are sent
through
timeout (int): The HTTP timeout of the connection
"""
plugin = SoapNull()
self._suds_client = suds.client.Client(
url,
plugins=[plugin],
timeout=timeout)
self._enclosing_session = enclosing_session
def __getattr__(self, attr):
# every time a client function is called, this verifies that there is
# still an active connection and if not, it reconnects.
logger.debug("attr={0} self={1}".format(attr, self.__dict__))
if attr == "service" and self._enclosing_session and \
self._enclosing_session._session_id_header is not None:
logger.debug("Calling hook before _suds_client_wrapper.service "
"access")
self._enclosing_session._reauth()
self._suds_client.set_options(
soapheaders=self._enclosing_session._session_id_header)
# for some reason adding the cookiejar didn't work, so the
# cookie is being added to the header manually.
# self._suds_client.options.transport.cookiejar = \
# self._enclosing_session._cookies
# adding the RouteID cookie, if it exists to the headers.
hostname = urlparse(self._enclosing_session._server.url).hostname
route = self._enclosing_session._cookies._cookies \
.get(hostname, {}).get("/", {}).get("ROUTEID")
if route:
self._suds_client.options.headers["Cookie"] = \
"ROUTEID=%s" % route.value
return getattr(self._suds_client, attr)