Skip to content

Commit

Permalink
XenAPI: Add versioning for plugins
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Bob Ball authored and openstack-gerrit committed Oct 3, 2013
1 parent 026edcd commit 24fd331
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 1 deletion.
5 changes: 4 additions & 1 deletion nova/tests/virt/xenapi/stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,10 @@ def host_call_plugin(self, _1, _2, plugin, method, args):
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):
Expand Down
72 changes: 72 additions & 0 deletions nova/tests/virt/xenapi/test_xenapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3957,6 +3957,78 @@ def test_get_product_version_product_brand_xs_6(self):
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):
Expand Down
22 changes: 22 additions & 0 deletions nova/virt/xenapi/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,12 @@ def get_per_instance_usage(self):
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
Expand All @@ -668,6 +674,22 @@ def __init__(self, url, user, pw, virtapi):
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)
Expand Down
3 changes: 3 additions & 0 deletions nova/virt/xenapi/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,9 @@ def _plugin_console_get_console_log(self, method, args):
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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
33 changes: 33 additions & 0 deletions plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 24fd331

Please sign in to comment.