Michael Pasternak has uploaded a new change for review.

Change subject: sdk: refactor invocation flow
......................................................................

sdk: refactor invocation flow

This change introduces workaround for the race conditions
on [1] and [2] which are caused by the non ability of the
former to supply tread safe environment as seen in #949187
and #949189.

[1] cookielib.DefaultCookiePolicy
[2] httplib.HTTPConnection

Change-Id: I4c50e04ef763d4f572cfb4a5e4df0bb045401453
Signed-off-by: Michael Pasternak <mpast...@redhat.com>
---
M src/ovirtsdk/infrastructure/connectionspool.py
M src/ovirtsdk/infrastructure/proxy.py
A src/ovirtsdk/utils/synchronizationhelper.py
M src/ovirtsdk/web/connection.py
A src/ovirtsdk/web/cookiejaradapter.py
5 files changed, 285 insertions(+), 172 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-engine-sdk refs/changes/94/13894/1

diff --git a/src/ovirtsdk/infrastructure/connectionspool.py 
b/src/ovirtsdk/infrastructure/connectionspool.py
index dbaf8a7..cc179cf 100644
--- a/src/ovirtsdk/infrastructure/connectionspool.py
+++ b/src/ovirtsdk/infrastructure/connectionspool.py
@@ -16,8 +16,15 @@
 
 from Queue import Queue
 import thread
+import cookielib
+import urlparse
+
+from cookielib import DefaultCookiePolicy
+
 from ovirtsdk.web.connection import Connection
 from ovirtsdk.infrastructure.errors import ImmutableError
+from ovirtsdk.utils.synchronizationhelper import synchronized
+
 
 class ConnectionsPool(object):
     '''
@@ -35,6 +42,14 @@
 
         self.__url = url
         self.__context = context
+
+        # Create the cookies policy and jar:
+        self.__cookies_jar = cookielib.CookieJar(
+                 policy=cookielib.DefaultCookiePolicy(
+                       strict_ns_domain=DefaultCookiePolicy.DomainStrictNoDots,
+                       allowed_domains=self.__getAllowedDomains(url)
+                 )
+            )
 
         for _ in range(count):
             self.__free_connections.put(item=Connection(url=url, \
@@ -64,10 +79,25 @@
 # TODO: add more connections if needed
 #        continue
 
-    def _freeResource(self, conn):
-        with self.__rlock:
-            conn = self.__busy_connections.pop(conn.get_id())
-            self.__free_connections.put_nowait(conn)
+    def getCookiesJar(self):
+        """
+        returns cookies container
+        """
+        return self.__cookies_jar
+
+    @synchronized
+    def addCookieHeaders(self, request_adapter):
+        """
+        Adds the stored cookie/s to the request adapter:
+        """
+        self.getCookiesJar().add_cookie_header(request_adapter)
+
+    @synchronized
+    def storeCookies(self, response_adapter, request_adapter):
+        """
+        Stores the cookie/s located in the response adapter in request adapter:
+        """
+        self.getCookiesJar().extract_cookies(response_adapter, request_adapter)
 
     def get_url(self):
         return self.__url
@@ -76,6 +106,26 @@
     def context(self):
         return self.__context
 
+    @synchronized
+    def __getAllowedDomains(self, url):
+        '''
+        fetches allowed domains for cookie
+        '''
+
+        LOCAL_HOST = 'localhost'
+        parsed_url = urlparse.urlparse(url)
+        domains = [parsed_url.hostname]
+
+        if parsed_url.hostname == LOCAL_HOST:
+            return domains.append(LOCAL_HOST + '.local')
+
+        return domains
+
+    def _freeResource(self, conn):
+        with self.__rlock:
+            conn = self.__busy_connections.pop(conn.get_id())
+            self.__free_connections.put_nowait(conn)
+
     def __setattr__(self, name, value):
         if name in ['__context', 'context']:
             raise ImmutableError(name)
diff --git a/src/ovirtsdk/infrastructure/proxy.py 
b/src/ovirtsdk/infrastructure/proxy.py
index 7f604cd..caef125 100644
--- a/src/ovirtsdk/infrastructure/proxy.py
+++ b/src/ovirtsdk/infrastructure/proxy.py
@@ -14,77 +14,9 @@
 # limitations under the License.
 #
 
-import cookielib
-import socket
-import urlparse
-
-from ovirtsdk.infrastructure.errors import RequestError, ConnectionError, \
-    FormatError
+from ovirtsdk.infrastructure.errors import RequestError, ConnectionError, 
FormatError
 from ovirtsdk.xml import params
-from cookielib import DefaultCookiePolicy
 from lxml import etree
-
-class CookieJarAdapter():
-    """
-    This class is an adapter that implements the methods that the CookieJar
-    expects and needs in order to add the cookie headers to an HTTP request.
-
-    From the point of view of the CookieJar it looks like a urllib2 request and
-    also like a response, but it just saves the headers to a list that will 
later
-    be retrieved by the proxy.
-    """
-    def __init__(self, url, headers):
-        # Save the URL and the headers:
-        self._url = url
-        self._headers = headers
-
-        # Extract the scheme and the host name from the URL:
-        parsed_url = urlparse.urlparse(self._url)
-        self._scheme = parsed_url.scheme
-        self._host = parsed_url.hostname
-
-    # The following methods are needed to simulate the behaviour of an urllib2
-    # request class:
-    def get_full_url(self):
-        return self._url
-
-    def get_host(self):
-        return self._host
-
-    def get_type(self):
-        return self._scheme
-
-    def is_unverifiable(self):
-        return False
-
-    def get_origin_req_host(self):
-        return self._host
-
-    def has_header(self, header):
-        return header.lower() in self._headers
-
-    def get_header(self, header_name, default=None):
-        return self._headers.get(header_name.lower(), default)
-
-    def header_items(self):
-        return self._headers.items()
-
-    def add_unredirected_header(self, key, val):
-        self._headers[key.lower()] = val
-
-    # The following method is needed to simulate the behaviour of an urllib2
-    # response class:
-    def info(self):
-        return self
-
-    # This methods simulates the object returned by the info method of the
-    # urllib2 response class:
-    def getheaders(self, name):
-        result = []
-        for key, value in self._headers.items():
-            if key.lower() == name.lower():
-                result.append(value)
-        return result
 
 class Proxy():
     '''
@@ -102,26 +34,6 @@
         # In order to create the cookies adapter we need to extract from the
         # URL the host name, so that we can accept cookies only from that host:
         self._url = self.__connections_pool.get_url()
-
-        # Create the cookies policy and jar:
-        cookies_policy = cookielib.DefaultCookiePolicy(
-                   strict_ns_domain=DefaultCookiePolicy.DomainStrictNoDots,
-                   allowed_domains=self.__getAllowedDomains(self._url))
-        self._cookies_jar = cookielib.CookieJar(policy=cookies_policy)
-
-    def __getAllowedDomains(self, url):
-        '''
-        fetches allowed domains for cookie
-        '''
-
-        LOCAL_HOST = 'localhost'
-        parsed_url = urlparse.urlparse(url)
-        domains = [parsed_url.hostname]
-
-        if parsed_url.hostname == LOCAL_HOST:
-            return domains.append(LOCAL_HOST + '.local')
-
-        return domains
 
     def getConnectionsPool(self):
         '''
@@ -196,9 +108,10 @@
                                 headers=headers, \
                                 
conn=self.getConnectionsPool().getConnection(), \
                                 last=last,
-                                noParse=noParse)
+                                noParse=noParse,
+                                persistent_auth=self._persistent_auth)
 
-    def __doRequest(self, method, url, conn, body=None, headers={}, 
last=False, noParse=False):
+    def __doRequest(self, method, url, conn, body=None, headers={}, 
last=False, noParse=False, persistent_auth=True):
         '''
         Performs HTTP request
         
@@ -209,75 +122,21 @@
         @param headers: request headers
         @param last: disables persistence authentication
         @param noParse: disables xml2py conversion
+        @param persistent_auth: session based auth
         '''
-        try:
-            # Copy request headers to avoid by-ref lookup after
-            # JSESSIONID has been injected
-            request_headers = headers.copy()
 
-            # Add cookie headers as needed:
-            request_adapter = CookieJarAdapter(self._url + url, 
request_headers)
-            self._cookies_jar.add_cookie_header(request_adapter)
-
-            # Every request except the last one should indicate that we prefer
-            # to use persistent authentication:
-            if self._persistent_auth and not last:
-                request_headers["Prefer"] = "persistent-auth"
-
-            # Send the request and wait for the response:
-            conn.doRequest(
+        response = conn.doRequest(
                    method=method,
                    url=url,
                    body=body,
-                   headers=request_headers,
-                   no_auth=self._persistent_auth and \
-                        self.__isSetJsessionCookie(self._cookies_jar)
+                   headers=headers,
+                   last=last,
+                   persistent_auth=persistent_auth
             )
 
-            response = conn.getResponse()
-
-            # Read the response headers (there is always a response,
-            # even for error responses):
-            response_headers = dict(response.getheaders())
-
-            # Parse the received body only if there are no errors reported by
-            # the server (this needs review, as less than 400 doesn't garantee
-            # a correct response, it could be a redirect, and many other
-            # things):
-            if response.status >= 400:
-                raise RequestError, response
-
-            # Copy the cookies from the response:
-            response_adapter = CookieJarAdapter(self._url, response_headers)
-            self._cookies_jar.extract_cookies(response_adapter, 
request_adapter)
-
-            # Parse the body:
-            response_body = response.read()
-
-            # Print response body (if in debug mode)
-            self.__do_debug(conn, response_body)
-
-            if not noParse:
-                return self.__xml2py(response_body)
-            return response_body
-
-        except socket.error, e:
-            raise ConnectionError, str(e)
-        finally:
-            conn.close()
-
-    def __isSetJsessionCookie(self, cookies_jar):
-        '''
-        Checks if JSESSIONID cookie is set
-
-        @param cookies_jar: cookies container
-        '''
-        if cookies_jar and len(cookies_jar._cookies) > 0:
-            for key in cookies_jar._cookies.keys():
-                if cookies_jar._cookies[key].has_key('/api') and \
-                    cookies_jar._cookies[key]['/api'].has_key('JSESSIONID'):
-                    return True
-        return False
+        if not noParse:
+            return self.__xml2py(response)
+        return response
 
     def __xml2py(self, obj):
         '''
@@ -291,13 +150,6 @@
                 # the motivation for this error is #915036
                 raise FormatError
         return obj
-
-    def __do_debug(self, conn, body):
-        '''
-        Prints request body (when in debug) to STDIO
-        '''
-        if conn.getConnection().debuglevel:
-                print 'body:\n' + body if body else ''
 
     def get_url(self):
         '''
diff --git a/src/ovirtsdk/utils/synchronizationhelper.py 
b/src/ovirtsdk/utils/synchronizationhelper.py
new file mode 100644
index 0000000..7e6ad27
--- /dev/null
+++ b/src/ovirtsdk/utils/synchronizationhelper.py
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2013 Red Hat 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.
+#
+
+
+########################################
+############ GENERATED CODE ############
+########################################
+
+
+import threading
+
+def synchronized(function):
+    """
+    Synchronizes the function
+    """
+    return __usinglock(threading.RLock())(function)
+
+def __usinglock(lock):
+    def decorator(function):
+        body = __tryexecute(lock.release)(function)
+        def execute(*args, **kwargs):
+            lock.acquire()
+            return body(*args, **kwargs)
+        return execute
+    return decorator
+
+def __tryexecute(finallyf):
+    def decorator(invokeable):
+        def execute(*args, **kwargs):
+            try: result = invokeable(*args, **kwargs)
+            finally: finallyf()
+            return result
+        return execute
+    return decorator
diff --git a/src/ovirtsdk/web/connection.py b/src/ovirtsdk/web/connection.py
index fa30fc4..b21cc22 100644
--- a/src/ovirtsdk/web/connection.py
+++ b/src/ovirtsdk/web/connection.py
@@ -14,14 +14,18 @@
 # limitations under the License.
 #
 
-import base64
-from httplib import HTTPConnection
+import types
 import urllib
 import urlparse
-from ovirtsdk.web.httpsconnection import HTTPSConnection
-from ovirtsdk.infrastructure.errors import NoCertificatesError, ImmutableError
-import types
+import base64
+import socket
+
+from httplib import HTTPConnection
+
+from ovirtsdk.web.cookiejaradapter import CookieJarAdapter
 from ovirtsdk.infrastructure.context import context
+from ovirtsdk.web.httpsconnection import HTTPSConnection
+from ovirtsdk.infrastructure.errors import NoCertificatesError, 
ImmutableError, RequestError, ConnectionError
 
 class Connection(object):
     '''
@@ -40,6 +44,7 @@
                                                     strict=strict,
                                                     timeout=timeout)
 
+        self.__url = url
         self.__connection.set_debuglevel(int(debug))
         self.__headers = self.__createStaticHeaders(username, password)
         self.__manager = manager
@@ -72,7 +77,7 @@
 
         return headers
 
-    def doRequest(self, method, url, body=urllib.urlencode({}), headers={}, 
no_auth=False):
+    def doRequest(self, method, url, body=urllib.urlencode({}), headers={}, 
last=False, persistent_auth=True):
         '''
         Performs HTTP request
 
@@ -80,9 +85,87 @@
         @param url: URL to invoke the request on
         @param body: request body
         @param headers: request headers
-        @param no_auth: do not authorize request (authorization is done via 
cookie)
+        @param last: disables persistence authentication
+        @param persistent_auth: session based auth
         '''
-        return self.__connection.request(method, url, body, 
self.getHeaders(headers, no_auth))
+
+        try:
+            # Copy request headers to avoid by-ref lookup after
+            # JSESSIONID has been injected
+            request_headers = headers.copy()
+
+            # Add cookie headers as needed:
+            request_adapter = CookieJarAdapter(self.__url + url, 
request_headers)
+            self.__manager.addCookieHeaders(request_adapter)
+
+            # Every request except the last one should indicate that we prefer
+            # to use persistent authentication:
+            if persistent_auth and not last:
+                request_headers["Prefer"] = "persistent-auth"
+
+            # Send the request and wait for the response:
+            response = self.__connection.request(
+                         method,
+                         url,
+                         body,
+                         self.getHeaders(request_headers,
+                                         no_auth=
+                                            persistent_auth and \
+                                            self.__isSetJsessionCookie(
+                                                   
self.__manager.getCookiesJar()
+                                            ),
+                         )
+                       )
+
+            response = self.getResponse()
+
+            # Read the response headers (there is always a response,
+            # even for error responses):
+            response_headers = dict(response.getheaders())
+
+            # Parse the received body only if there are no errors reported by
+            # the server (this needs review, as less than 400 doesn't garantee
+            # a correct response, it could be a redirect, and many other
+            # things):
+            if response.status >= 400:
+                raise RequestError, response
+
+            # Copy the cookies from the response:
+            response_adapter = CookieJarAdapter(self.__url, response_headers)
+            self.__manager.storeCookies(response_adapter, request_adapter)
+
+            # Parse the body:
+            response_body = response.read()
+
+            # Print response body (if in debug mode)
+            self.__do_debug(self, response_body)
+
+            return response_body
+
+        except socket.error, e:
+            raise ConnectionError, str(e)
+        finally:
+            self.close()
+
+    def __do_debug(self, conn, body):
+        '''
+        Prints request body (when in debug) to STDIO
+        '''
+        if conn.getConnection().debuglevel:
+                print 'body:\n' + body if body else ''
+
+    def __isSetJsessionCookie(self, cookies_jar):
+        '''
+        Checks if JSESSIONID cookie is set
+
+        @param cookies_jar: cookies container
+        '''
+        if cookies_jar and len(cookies_jar._cookies) > 0:
+            for key in cookies_jar._cookies.keys():
+                if cookies_jar._cookies[key].has_key('/api') and \
+                    cookies_jar._cookies[key]['/api'].has_key('JSESSIONID'):
+                    return True
+        return False
 
     def getHeaders(self, headers, no_auth=False):
         extended_headers = self.getDefaultHeaders(no_auth)
diff --git a/src/ovirtsdk/web/cookiejaradapter.py 
b/src/ovirtsdk/web/cookiejaradapter.py
new file mode 100644
index 0000000..0d2f203
--- /dev/null
+++ b/src/ovirtsdk/web/cookiejaradapter.py
@@ -0,0 +1,81 @@
+
+#
+# Copyright (c) 2010 Red Hat, 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.
+#
+
+import urlparse
+
+
+class CookieJarAdapter():
+    """
+    This class is an adapter that implements the methods that the CookieJar
+    expects and needs in order to add the cookie headers to an HTTP request.
+
+    From the point of view of the CookieJar it looks like a urllib2 request and
+    also like a response, but it just saves the headers to a list that will 
later
+    be retrieved by the proxy.
+    """
+    def __init__(self, url, headers):
+        # Save the URL and the headers:
+        self._url = url
+        self._headers = headers
+
+        # Extract the scheme and the host name from the URL:
+        parsed_url = urlparse.urlparse(self._url)
+        self._scheme = parsed_url.scheme
+        self._host = parsed_url.hostname
+
+    # The following methods are needed to simulate the behaviour of an urllib2
+    # request class:
+    def get_full_url(self):
+        return self._url
+
+    def get_host(self):
+        return self._host
+
+    def get_type(self):
+        return self._scheme
+
+    def is_unverifiable(self):
+        return False
+
+    def get_origin_req_host(self):
+        return self._host
+
+    def has_header(self, header):
+        return header.lower() in self._headers
+
+    def get_header(self, header_name, default=None):
+        return self._headers.get(header_name.lower(), default)
+
+    def header_items(self):
+        return self._headers.items()
+
+    def add_unredirected_header(self, key, val):
+        self._headers[key.lower()] = val
+
+    # The following method is needed to simulate the behaviour of an urllib2
+    # response class:
+    def info(self):
+        return self
+
+    # This methods simulates the object returned by the info method of the
+    # urllib2 response class:
+    def getheaders(self, name):
+        result = []
+        for key, value in self._headers.items():
+            if key.lower() == name.lower():
+                result.append(value)
+        return result


--
To view, visit http://gerrit.ovirt.org/13894
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I4c50e04ef763d4f572cfb4a5e4df0bb045401453
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine-sdk
Gerrit-Branch: master
Gerrit-Owner: Michael Pasternak <mpast...@redhat.com>
_______________________________________________
Engine-patches mailing list
Engine-patches@ovirt.org
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to