Merge "Add XenAPI.py into os-xenapi repo"

This commit is contained in:
Jenkins 2016-11-14 11:38:22 +00:00 committed by Gerrit Code Review
commit 2f08e5c6e1
5 changed files with 247 additions and 12 deletions

View File

@ -1,5 +1,2 @@
# The XenAPI plugins run in a Python 2 environment, so avoid attempting
# to run their unit tests in a Python 3 environment
os_xenapi.tests.client.test_session.CallPluginTestCase
os_xenapi.tests.client.test_session.SessionTestCase

239
os_xenapi/client/XenAPI.py Normal file
View File

@ -0,0 +1,239 @@
# Copyright 2013 OpenStack Foundation
#
# 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 gettext
import socket
import sys
if sys.version_info[0] == 2:
import httplib as httpclient
import xmlrpclib as xmlrpcclient
else:
import http.client as httpclient
import xmlrpc.client as xmlrpcclient
translation = gettext.translation('xen-xm', fallback=True)
API_VERSION_1_1 = '1.1'
API_VERSION_1_2 = '1.2'
def below_python27():
if sys.version_info[0] <= 2 and sys.version_info[1] < 7:
return True
else:
return False
class Failure(Exception):
def __init__(self, details):
self.details = details
def __str__(self):
try:
return str(self.details)
except Exception:
# To support py2.4/py2.7/py3 together, extract exception via sys
# py2.4: except Exception, exn
# py2.7/py3: except Exception as exn
type, value = sys.exc_info()[:2]
sys.stderr.write("%s %s", type, value)
return "Xen-API failure: %s" % str(self.details)
def _details_map(self):
return dict([(str(i), self.details[i])
for i in range(len(self.details))])
class UDSHTTPConnection(httpclient.HTTPConnection):
"""HTTPConnection subclass to allow HTTP over Unix domain sockets. """
def connect(self):
path = self.host.replace("_", "/")
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(path)
class UDSTransport(xmlrpcclient.Transport):
def __init__(self, use_datetime=0):
if not below_python27():
xmlrpcclient.Transport.__init__(self, use_datetime)
self._use_datetime = use_datetime
self._connection = (None, None)
self._extra_headers = []
def add_extra_header(self, key, value):
self._extra_headers += [(key, value)]
def make_connection(self, host):
if below_python27():
# Python 2.4 compatibility
class UDSHTTP(httpclient.HTTP):
_connection_class = UDSHTTPConnection
return UDSHTTP(host)
else:
return UDSHTTPConnection(host)
def send_request(self, connection, handler, request_body):
connection.putrequest("POST", handler)
for key, value in self._extra_headers:
connection.putheader(key, value)
# Just a "constant" that we use to decide whether to retry the RPC
_RECONNECT_AND_RETRY = object()
class Session(xmlrpcclient.ServerProxy):
"""A server proxy and session manager for communicating with xapi.
Example:
session = Session('http://localhost/')
session.login_with_password('me', 'password')
session.xenapi.VM.start(vm_uuid)
session.xenapi.session.logout()
"""
def __init__(self, uri, transport=None, encoding=None, verbose=0,
allow_none=0):
xmlrpcclient.ServerProxy.__init__(self, uri, transport=transport,
encoding=encoding, verbose=verbose,
allow_none=allow_none)
self.transport = transport
self._session = None
self.last_login_method = None
self.last_login_params = None
self.API_version = API_VERSION_1_1
def xenapi_request(self, methodname, params):
if methodname.startswith('login'):
self._login(methodname, params)
return None
if methodname == 'logout' or methodname == 'session.logout':
self._logout()
return None
retry_count = 0
while retry_count < 3:
full_params = (self._session,) + params
result = _parse_result(getattr(self, methodname)(*full_params))
if result is _RECONNECT_AND_RETRY:
retry_count += 1
if self.last_login_method:
self._login(self.last_login_method,
self.last_login_params)
else:
raise xmlrpcclient.Fault(401, 'You must log in')
else:
return result
raise xmlrpcclient.Fault(
500, 'Tried 3 times to get a valid session, but failed')
def _login(self, method, params):
try:
result = _parse_result(
getattr(self, 'session.%s' % method)(*params))
if result is _RECONNECT_AND_RETRY:
raise xmlrpcclient.Fault(
500, 'Received SESSION_INVALID when logging in')
self._session = result
self.last_login_method = method
self.last_login_params = params
self.API_version = self._get_api_version()
except socket.error:
e = sys.exc_info()[1]
if e.errno == socket.errno.ETIMEDOUT:
raise xmlrpcclient.Fault(504, 'The connection timed out')
else:
raise e
def _logout(self):
try:
if self.last_login_method.startswith("slave_local"):
return _parse_result(self.session.local_logout(self._session))
else:
return _parse_result(self.session.logout(self._session))
finally:
self._session = None
self.last_login_method = None
self.last_login_params = None
self.API_version = API_VERSION_1_1
def _get_api_version(self):
pool = self.xenapi.pool.get_all()[0]
host = self.xenapi.pool.get_master(pool)
major = self.xenapi.host.get_API_version_major(host)
minor = self.xenapi.host.get_API_version_minor(host)
return "%s.%s" % (major, minor)
def __getattr__(self, name):
if name == 'handle':
return self._session
elif name == 'xenapi':
return _Dispatcher(self.API_version, self.xenapi_request, None)
elif name.startswith('login') or name.startswith('slave_local'):
return lambda *params: self._login(name, params)
elif name == 'logout':
return _Dispatcher(self.API_version, self.xenapi_request, "logout")
else:
return xmlrpcclient.ServerProxy.__getattr__(self, name)
def xapi_local():
return Session("http://_var_xapi_xapi/", transport=UDSTransport())
def _parse_result(result):
if type(result) != dict or 'Status' not in result:
raise xmlrpcclient.Fault(
500, 'Missing Status in response from server' + result)
if result['Status'] == 'Success':
if 'Value' in result:
return result['Value']
else:
raise xmlrpcclient.Fault(
500, 'Missing Value in response from server')
else:
if 'ErrorDescription' in result:
if result['ErrorDescription'][0] == 'SESSION_INVALID':
return _RECONNECT_AND_RETRY
else:
raise Failure(result['ErrorDescription'])
else:
raise xmlrpcclient.Fault(
500, 'Missing ErrorDescription in response from server')
# Based upon _Method from xmlrpclib.
class _Dispatcher(object):
def __init__(self, API_version, send, name):
self.__API_version = API_version
self.__send = send
self.__name = name
def __repr__(self):
if self.__name:
return '<XenAPI._Dispatcher for %s>' % self.__name
else:
return '<XenAPI._Dispatcher>'
def __getattr__(self, name):
if self.__name is None:
return _Dispatcher(self.__API_version, self.__send, name)
else:
return _Dispatcher(self.__API_version, self.__send,
"%s.%s" % (self.__name, name))
def __call__(self, *args):
return self.__send(self.__name, args)

View File

@ -41,6 +41,7 @@ from os_xenapi.client import exception
from os_xenapi.client.i18n import _
from os_xenapi.client.i18n import _LW
from os_xenapi.client import objects as cli_objects
from os_xenapi.client import XenAPI
LOG = logging.getLogger(__name__)
@ -78,7 +79,6 @@ class XenAPISession(object):
:param timeout: Timeout in seconds for XenAPI login
:param concurrent: Maximum concurrent XenAPI connections
"""
import XenAPI
self.XenAPI = XenAPI
self.originator = originator
self.timeout = timeout
@ -242,7 +242,7 @@ class XenAPISession(object):
'attempts': attempts,
'callback_result': callback_result})
return self.call_plugin_serialized(plugin, fn, *args, **kwargs)
except self.XenAPI.Failure as exc:
except XenAPI.Failure as exc:
if self._is_retryable_exception(exc, fn):
LOG.warning(_LW('%(plugin)s.%(fn)s failed. '
'Retrying call.'),
@ -280,8 +280,8 @@ class XenAPISession(object):
"""Stubout point. This can be replaced with a mock session."""
self.is_local_connection = url == "unix://local"
if self.is_local_connection:
return self.XenAPI.xapi_local()
return self.XenAPI.Session(url)
return XenAPI.xapi_local()
return XenAPI.Session(url)
def _create_session_and_login(self, url, user, pw):
session = self._create_session(url)
@ -292,7 +292,7 @@ class XenAPISession(object):
"""Parse exception details."""
try:
return func(*args, **kwargs)
except self.XenAPI.Failure as exc:
except XenAPI.Failure as exc:
LOG.debug("Got exception: %s", exc)
if (len(exc.details) == 4 and
exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and
@ -302,7 +302,7 @@ class XenAPISession(object):
params = ast.literal_eval(exc.details[3])
except Exception:
raise exc
raise self.XenAPI.Failure(params)
raise XenAPI.Failure(params)
else:
raise
except xmlrpclib.ProtocolError as exc:
@ -312,7 +312,7 @@ class XenAPISession(object):
def get_rec(self, record_type, ref):
try:
return self.call_xenapi('%s.get_record' % record_type, ref)
except self.XenAPI.Failure as e:
except XenAPI.Failure as e:
if e.details[0] != 'HANDLE_INVALID':
raise

View File

@ -11,4 +11,3 @@ oslo.log>=3.11.0 # Apache-2.0
oslo.utils>=3.18.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
six>=1.9.0 # MIT
XenAPI>=1.2 # LGPL

View File

@ -15,7 +15,7 @@ whitelist_externals = find
commands =
find . -type f -name "*.pyc" -delete
py27: python setup.py test --slowest --testr-args='{posargs}'
py35: ostestr --blacklist_file exclusion_py3.txt
py35: ostestr --color --slowest --blacklist_file exclusion_py3.txt
[testenv:pep8]