From 24fd331b8cd5aca66362e6f76deec9c437875933 Mon Sep 17 00:00:00 2001 From: Bob Ball Date: Tue, 17 Sep 2013 15:29:30 +0100 Subject: [PATCH] XenAPI: Add versioning for plugins Because the plugins live on a host seperate to Nova we need an interface to test whether they are the expected version. We can't use pip or other requirement systems as they are cross-machine. Closes bug 1226622 Change-Id: I58ab669061f51bd87071e2cf0d93d33021001309 --- nova/tests/virt/xenapi/stubs.py | 5 +- nova/tests/virt/xenapi/test_xenapi.py | 72 +++++++++++++++++++ nova/virt/xenapi/driver.py | 22 ++++++ nova/virt/xenapi/fake.py | 3 + .../rpmbuild/SPECS/openstack-xen-plugins.spec | 1 + .../etc/xapi.d/plugins/nova_plugin_version | 33 +++++++++ 6 files changed, 135 insertions(+), 1 deletion(-) create mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version diff --git a/nova/tests/virt/xenapi/stubs.py b/nova/tests/virt/xenapi/stubs.py index 01823638b260..904571018ddd 100644 --- a/nova/tests/virt/xenapi/stubs.py +++ b/nova/tests/virt/xenapi/stubs.py @@ -239,7 +239,10 @@ class FakeSessionForFirewallTests(FakeSessionForVMTests): if '*filter' in lines: output = '\n'.join(lines) ret_str = fake.as_json(out=output, err='') - return ret_str + return ret_str + else: + return (super(FakeSessionForVMTests, self). + host_call_plugin(_1, _2, plugin, method, args)) def stub_out_vm_methods(stubs): diff --git a/nova/tests/virt/xenapi/test_xenapi.py b/nova/tests/virt/xenapi/test_xenapi.py index ca6a126dfc68..9a45bf0a43df 100644 --- a/nova/tests/virt/xenapi/test_xenapi.py +++ b/nova/tests/virt/xenapi/test_xenapi.py @@ -3957,6 +3957,78 @@ class XenAPISessionTestCase(test.NoDBTestCase): session._get_product_version_and_brand() ) + def test_verify_plugin_version_same(self): + session = self._get_mock_xapisession({}) + + session.PLUGIN_REQUIRED_VERSION = '2.4' + + self.mox.StubOutWithMock(session, 'call_plugin_serialized') + session.call_plugin_serialized('nova_plugin_version', 'get_version', + ).AndReturn("2.4") + + self.mox.ReplayAll() + session._verify_plugin_version() + + def test_verify_plugin_version_compatible(self): + session = self._get_mock_xapisession({}) + session.XenAPI = xenapi_fake.FakeXenAPI() + + session.PLUGIN_REQUIRED_VERSION = '2.4' + + self.mox.StubOutWithMock(session, 'call_plugin_serialized') + session.call_plugin_serialized('nova_plugin_version', 'get_version', + ).AndReturn("2.5") + + self.mox.ReplayAll() + session._verify_plugin_version() + + def test_verify_plugin_version_bad_maj(self): + session = self._get_mock_xapisession({}) + session.XenAPI = xenapi_fake.FakeXenAPI() + + session.PLUGIN_REQUIRED_VERSION = '2.4' + + self.mox.StubOutWithMock(session, 'call_plugin_serialized') + session.call_plugin_serialized('nova_plugin_version', 'get_version', + ).AndReturn("3.0") + + self.mox.ReplayAll() + self.assertRaises(xenapi_fake.Failure, session._verify_plugin_version) + + def test_verify_plugin_version_bad_min(self): + session = self._get_mock_xapisession({}) + session.XenAPI = xenapi_fake.FakeXenAPI() + + session.PLUGIN_REQUIRED_VERSION = '2.4' + + self.mox.StubOutWithMock(session, 'call_plugin_serialized') + session.call_plugin_serialized('nova_plugin_version', 'get_version', + ).AndReturn("2.3") + + self.mox.ReplayAll() + self.assertRaises(xenapi_fake.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 = "../../../../plugins/xenserver/xenapi/etc/xapi.d/" \ + "plugins/nova_plugin_version" + 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: + print line + plugin_version = line.strip()[17:].strip('"') + + self.assertEquals(session.PLUGIN_REQUIRED_VERSION, + plugin_version) + class XenAPIFakeTestCase(test.NoDBTestCase): def test_query_matches(self): diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py index 4e2e67700e16..25594c3ab95f 100644 --- a/nova/virt/xenapi/driver.py +++ b/nova/virt/xenapi/driver.py @@ -654,6 +654,12 @@ class XenAPIDriver(driver.ComputeDriver): class XenAPISession(object): """The session to invoke XenAPI SDK calls.""" + # This is not a config option as it should only ever be + # changed in development environments. + # MAJOR VERSION: Incompatible changes with the plugins + # MINOR VERSION: Compatible changes, new plguins, etc + PLUGIN_REQUIRED_VERSION = '1.0' + def __init__(self, url, user, pw, virtapi): import XenAPI self.XenAPI = XenAPI @@ -668,6 +674,22 @@ class XenAPISession(object): self._get_product_version_and_brand() self._virtapi = virtapi + self._verify_plugin_version() + + def _verify_plugin_version(self): + # Verify that we're using the right version of the plugins + returned_version = self.call_plugin_serialized( + 'nova_plugin_version', 'get_version') + + # Can't use vmops.cmp_version because that tolerates differences in + # major version + req_maj, req_min = self.PLUGIN_REQUIRED_VERSION.split('.') + got_maj, got_min = returned_version.split('.') + if req_maj != got_maj or req_min > got_min: + raise self.XenAPI.Failure( + _("Plugin version mismatch (Expected %(exp)s, got %(got)s)") % + {'exp': self.PLUGIN_REQUIRED_VERSION, 'got': returned_version}) + def _create_first_session(self, url, user, pw, exception): try: session = self._create_session(url) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 475bd7d28cd0..103dd9bdc2d6 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -673,6 +673,9 @@ class SessionBase(object): raise Failure('Guest does not have a console') return base64.b64encode(zlib.compress("dom_id: %s" % dom_id)) + def _plugin_nova_plugin_version_get_version(self, method, args): + return pickle.dumps("1.0") + def host_call_plugin(self, _1, _2, plugin, method, args): func = getattr(self, '_plugin_%s_%s' % (plugin, method), None) if not func: diff --git a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec index 84578cf54048..10b3b127e2e9 100644 --- a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec +++ b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec @@ -42,3 +42,4 @@ rm -rf $RPM_BUILD_ROOT /etc/xapi.d/plugins/xenhost /etc/xapi.d/plugins/xenstore.py /etc/xapi.d/plugins/utils.py +/etc/xapi.d/plugins/nova_plugin_version diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version b/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version new file mode 100755 index 000000000000..6c7a4b774b96 --- /dev/null +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +# Copyright (c) 2013 OpenStack Foundation +# Copyright (c) 2013 Citrix Systems, 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. + +"""Returns the version of the nova plugins""" + +import utils + +# MAJOR VERSION: Incompatible changes +# MINOR VERSION: Compatible changes, new plugins, etc + +# 1.0 - Initial version. +PLUGIN_VERSION = "1.0" + +def get_version(session): + return PLUGIN_VERSION + + +if __name__ == '__main__': + utils.register_plugin_calls(get_version)