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
(cherry picked from commit 24fd331b8c)
This commit is contained in:
Bob Ball 2013-09-17 15:29:30 +01:00 committed by Russell Bryant
parent 96d828fb69
commit cad1c865c6
6 changed files with 135 additions and 1 deletions

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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)