Merge "Add XenAPI.py into os-xenapi repo"
This commit is contained in:
commit
2f08e5c6e1
@ -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
239
os_xenapi/client/XenAPI.py
Normal 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)
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
2
tox.ini
2
tox.ini
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user