From 68c5be4b5741f0a6acd94976098b3016301dc1d7 Mon Sep 17 00:00:00 2001 From: Huan Xie Date: Wed, 7 Dec 2016 22:09:56 -0800 Subject: [PATCH] Minor fix for letfovers This patch is to fix the faults and leftovers we found during test together with nova 1. Add pool back to session 2. Deal with master/slave hosts when creating session 3. Delete version setting in setup.cfg 4. Minor debug log format fix 5. Move XenAPISessionTestCase from nova to os-xenapi, in file test_session.py Change-Id: I7d4fabb00fc594f768323cd61e3d2cd83b25141c --- os_xenapi/client/session.py | 40 ++++--- os_xenapi/tests/client/test_session.py | 142 +++++++++++++++++++++++-- setup.cfg | 1 - tox.ini | 1 - 4 files changed, 162 insertions(+), 22 deletions(-) diff --git a/os_xenapi/client/session.py b/os_xenapi/client/session.py index 94f699c..df83085 100644 --- a/os_xenapi/client/session.py +++ b/os_xenapi/client/session.py @@ -29,7 +29,6 @@ from eventlet import timeout from oslo_log import log as logging from oslo_utils import versionutils from six.moves import http_client -from six.moves import range from six.moves import urllib try: @@ -57,6 +56,7 @@ def apply_session_helpers(session): session.VLAN = cli_objects.VLAN(session) session.host = cli_objects.Host(session) session.network = cli_objects.Network(session) + session.pool = cli_objects.Pool(session) class XenAPISession(object): @@ -86,22 +86,23 @@ class XenAPISession(object): self._sessions = queue.Queue() self.host_checked = False self.url = self._create_first_session(url, user, pw) - self._populate_session_pool(url, user, pw) + self._populate_session_pool(self.url, user, pw) self.host_uuid = self._get_host_uuid() self.host_ref = self._get_host_ref() self.product_version, self.product_brand = \ self._get_product_version_and_brand() - # TODO(huanxie) Uncomment _verify_plugin_version() in the future - # self._verify_plugin_version() + self._verify_plugin_version() self.platform_version = self._get_platform_version() self._cached_xsm_sr_relaxed = None apply_session_helpers(self) def _login_with_password(self, user, pw, session): - login_exception = exception.SessionLoginTimeout + login_exception = XenAPI.Failure(_("Unable to log in to XenAPI " + "(is the Dom0 disk full?)")) with timeout.Timeout(self.timeout, login_exception): - session.login_with_password(user, pw, self.originator) + session.login_with_password(user, pw, self.PLUGIN_REQUIRED_VERSION, + self.originator) def _verify_plugin_version(self): requested_version = self.PLUGIN_REQUIRED_VERSION @@ -114,18 +115,29 @@ class XenAPISession(object): return if not versionutils.is_compatible(requested_version, current_version): - raise exception.OsXenApiException( + raise XenAPI.Failure( _("Plugin version mismatch (Expected %(exp)s, got %(got)s)") % {'exp': requested_version, 'got': current_version}) def _create_first_session(self, url, user, pw): try: session = self._create_session_and_login(url, user, pw) - except exception.SessionLoginTimeout: - raise + except XenAPI.Failure as e: + # if user and pw of the master are different, we're doomed! + if e.details[0] == 'HOST_IS_SLAVE': + master = e.details[1] + url = self.swap_xapi_host(url, master) + session = self._create_session_and_login(url, user, pw) + else: + raise self._sessions.put(session) return url + def swap_xapi_host(self, url, host_addr): + """Replace the XenServer address present in 'url' with 'host_addr'.""" + temp_url = urllib.parse.urlparse.urlparse(url) + return url.replace(temp_url.hostname, '%s' % host_addr) + def _populate_session_pool(self, url, user, pw): for i in range(self.concurrent - 1): session = self._create_session_and_login(url, user, pw) @@ -325,18 +337,18 @@ class XenAPISession(object): name = '%s-%s' % (self.originator, label) task_ref = self.call_xenapi("task.create", name, desc) try: - LOG.debug('Created task %s with ref %s' % (name, task_ref)) + LOG.debug('Created task %s with ref %s', name, task_ref) yield task_ref finally: self.call_xenapi("task.destroy", task_ref) - LOG.debug('Destroyed task ref %s' % task_ref) + LOG.debug('Destroyed task ref %s', task_ref) @contextlib.contextmanager - def http_connection(self, session): + def http_connection(self): conn = None - xs_url = urllib.parse.urlparse(session.url) - LOG.debug("Creating http(s) connection to %s" % session.url) + xs_url = urllib.parse.urlparse(self.url) + LOG.debug("Creating http(s) connection to %s", self.url) if xs_url.scheme == 'http': conn = http_client.HTTPConnection(xs_url.netloc) elif xs_url.scheme == 'https': diff --git a/os_xenapi/tests/client/test_session.py b/os_xenapi/tests/client/test_session.py index 62ef4bf..02725d7 100644 --- a/os_xenapi/tests/client/test_session.py +++ b/os_xenapi/tests/client/test_session.py @@ -13,24 +13,29 @@ # under the License. import errno +import os import socket import mock from os_xenapi.client import exception from os_xenapi.client import session +from os_xenapi.client import XenAPI from os_xenapi.tests import base class SessionTestCase(base.TestCase): + @mock.patch.object(session.XenAPISession, '_verify_plugin_version') @mock.patch.object(session.XenAPISession, '_get_platform_version') @mock.patch.object(session.XenAPISession, '_create_session') @mock.patch.object(session.XenAPISession, '_get_product_version_and_brand') def test_session_nova_originator(self, mock_version_and_brand, mock_create_session, - mock_platform_version): + mock_platform_version, + mock_verify_plugin_version): concurrent = 2 originator = 'os-xenapi-nova' + version = '2.0' timeout = 10 sess = mock.Mock() mock_create_session.return_value = sess @@ -42,15 +47,17 @@ class SessionTestCase(base.TestCase): timeout=timeout) sess.login_with_password.assert_called_with('username', 'password', - originator) + version, originator) + @mock.patch.object(session.XenAPISession, '_verify_plugin_version') @mock.patch.object(session.XenAPISession, '_get_platform_version') @mock.patch('eventlet.timeout.Timeout') @mock.patch.object(session.XenAPISession, '_create_session') @mock.patch.object(session.XenAPISession, '_get_product_version_and_brand') def test_session_login_with_timeout(self, mock_version, create_session, mock_timeout, - mock_platform_version): + mock_platform_version, + mock_verify_plugin_version): concurrent = 2 originator = 'os-xenapi-nova' sess = mock.Mock() @@ -63,12 +70,14 @@ class SessionTestCase(base.TestCase): self.assertEqual(concurrent, sess.login_with_password.call_count) self.assertEqual(concurrent, mock_timeout.call_count) + @mock.patch.object(session.XenAPISession, '_verify_plugin_version') @mock.patch.object(session.XenAPISession, 'call_plugin') @mock.patch.object(session.XenAPISession, '_get_software_version') @mock.patch.object(session.XenAPISession, '_create_session') def test_relax_xsm_sr_check_true(self, mock_create_session, mock_get_software_version, - mock_call_plugin): + mock_call_plugin, + mock_verify_plugin_version): sess = mock.Mock() mock_create_session.return_value = sess mock_get_software_version.return_value = {'product_version': '6.5.0', @@ -80,12 +89,14 @@ class SessionTestCase(base.TestCase): 'http://someserver', 'username', 'password') self.assertTrue(xenapi_sess.is_xsm_sr_check_relaxed()) + @mock.patch.object(session.XenAPISession, '_verify_plugin_version') @mock.patch.object(session.XenAPISession, 'call_plugin') @mock.patch.object(session.XenAPISession, '_get_software_version') @mock.patch.object(session.XenAPISession, '_create_session') def test_relax_xsm_sr_check_XS65_missing(self, mock_create_session, mock_get_software_version, - mock_call_plugin): + mock_call_plugin, + mock_verify_plugin_version): sess = mock.Mock() mock_create_session.return_value = sess mock_get_software_version.return_value = {'product_version': '6.5.0', @@ -97,12 +108,14 @@ class SessionTestCase(base.TestCase): 'http://someserver', 'username', 'password') self.assertFalse(xenapi_sess.is_xsm_sr_check_relaxed()) + @mock.patch.object(session.XenAPISession, '_verify_plugin_version') @mock.patch.object(session.XenAPISession, 'call_plugin') @mock.patch.object(session.XenAPISession, '_get_software_version') @mock.patch.object(session.XenAPISession, '_create_session') def test_relax_xsm_sr_check_XS7_missing(self, mock_create_session, mock_get_software_version, - mock_call_plugin): + mock_call_plugin, + mock_verify_plugin_version): sess = mock.Mock() mock_create_session.return_value = sess mock_get_software_version.return_value = {'product_version': '7.0.0', @@ -230,3 +243,120 @@ class CallPluginTestCase(base.TestCase): num_retries, callback, retry_cb) call_plugin_serialized.assert_called_with(plugin, fn) self.assertEqual(2, call_plugin_serialized.call_count) + + +class XenAPISessionTestCase(base.TestCase): + def _get_mock_xapisession(self, software_version): + class MockXapiSession(session.XenAPISession): + def __init__(_ignore): + pass + + def _get_software_version(_ignore): + return software_version + + return MockXapiSession() + + @mock.patch.object(XenAPI, 'xapi_local') + def test_local_session(self, mock_xapi_local): + session = self._get_mock_xapisession({}) + session.is_local_connection = True + mock_xapi_local.return_value = "local_connection" + self.assertEqual("local_connection", + session._create_session("unix://local")) + + @mock.patch.object(XenAPI, 'Session') + def test_remote_session(self, mock_session): + session = self._get_mock_xapisession({}) + session.is_local_connection = False + mock_session.return_value = "remote_connection" + self.assertEqual("remote_connection", session._create_session("url")) + + def test_get_product_version_product_brand_does_not_fail(self): + session = self._get_mock_xapisession( + {'build_number': '0', + 'date': '2012-08-03', + 'hostname': 'komainu', + 'linux': '3.2.0-27-generic', + 'network_backend': 'bridge', + 'platform_name': 'XCP_Kronos', + 'platform_version': '1.6.0', + 'xapi': '1.3', + 'xen': '4.1.2', + 'xencenter_max': '1.10', + 'xencenter_min': '1.10'}) + + self.assertEqual( + ((1, 6, 0), None), + session._get_product_version_and_brand() + ) + + def test_get_product_version_product_brand_xs_6(self): + session = self._get_mock_xapisession( + {'product_brand': 'XenServer', + 'product_version': '6.0.50', + 'platform_version': '0.0.1'}) + + self.assertEqual( + ((6, 0, 50), 'XenServer'), + session._get_product_version_and_brand() + ) + + def test_verify_plugin_version_same(self): + session = self._get_mock_xapisession({}) + session.PLUGIN_REQUIRED_VERSION = '2.4' + with mock.patch.object(session, 'call_plugin_serialized', + spec=True) as call_plugin_serialized: + call_plugin_serialized.return_value = "2.4" + session._verify_plugin_version() + + def test_verify_plugin_version_compatible(self): + session = self._get_mock_xapisession({}) + session.PLUGIN_REQUIRED_VERSION = '2.4' + with mock.patch.object(session, 'call_plugin_serialized', + spec=True) as call_plugin_serialized: + call_plugin_serialized.return_value = "2.5" + session._verify_plugin_version() + + def test_verify_plugin_version_python_extensions(self): + # Validate that 2.0 is equivalent to 1.8 + session = self._get_mock_xapisession({}) + session.PLUGIN_REQUIRED_VERSION = '2.0' + with mock.patch.object(session, 'call_plugin_serialized', + spec=True) as call_plugin_serialized: + call_plugin_serialized.return_value = "1.8" + session._verify_plugin_version() + + def test_verify_plugin_version_bad_maj(self): + session = self._get_mock_xapisession({}) + session.PLUGIN_REQUIRED_VERSION = '2.4' + with mock.patch.object(session, 'call_plugin_serialized', + spec=True) as call_plugin_serialized: + call_plugin_serialized.return_value = "3.0" + self.assertRaises(XenAPI.Failure, session._verify_plugin_version) + + def test_verify_plugin_version_bad_min(self): + session = self._get_mock_xapisession({}) + session.PLUGIN_REQUIRED_VERSION = '2.4' + with mock.patch.object(session, 'call_plugin_serialized', + spec=True) as call_plugin_serialized: + call_plugin_serialized.return_value = "2.3" + self.assertRaises(XenAPI.Failure, session._verify_plugin_version) + + def test_verify_current_version_matches(self): + session = self._get_mock_xapisession({}) + + # Import the plugin to extract its version + path = os.path.dirname(__file__) + rel_path_elem = "../../dom0/etc/xapi.d/plugins/dom0_plugin_version.py" + for elem in rel_path_elem.split('/'): + path = os.path.join(path, elem) + path = os.path.realpath(path) + + plugin_version = None + with open(path) as plugin_file: + for line in plugin_file: + if "PLUGIN_VERSION = " in line: + plugin_version = line.strip()[17:].strip('"') + + self.assertEqual(session.PLUGIN_REQUIRED_VERSION, + plugin_version) diff --git a/setup.cfg b/setup.cfg index 8740c56..7685cdf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,6 @@ [metadata] name = os-xenapi summary = XenAPI library for OpenStack projects -version = 0.0.1 description-file = README.rst author = Citrix diff --git a/tox.ini b/tox.ini index efdb45e..a0b31ea 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,6 @@ skipsdist = True [testenv] usedevelop = True -install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning