From aa48f8223af92b4b06cb748a80b7272aac69f47b Mon Sep 17 00:00:00 2001 From: Jianghua Wang Date: Tue, 26 Dec 2017 10:13:12 +0000 Subject: [PATCH] Adding utils to install XAPI plugins to dom0 When deploy OpenStack on XenServer, we need install some XAPI plugins to dom0. This commit is to add utils for this purpose. If the os-xenapi version is different from the os-xenapi which contains the utils. Users can specify the version to the utils. So that the utils will download the right version of os-xenapi and copy plugins from there. Otherwise the utils will by default copy plugins from current installed packages. Change-Id: I269a444b952f63fd73b3825b23dc95d6e825ce8f --- os_xenapi/tests/utils/test_xapi_plugin.py | 100 ++++++++++++++++++++++ os_xenapi/utils/xapi_plugin.py | 83 ++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 os_xenapi/tests/utils/test_xapi_plugin.py create mode 100644 os_xenapi/utils/xapi_plugin.py diff --git a/os_xenapi/tests/utils/test_xapi_plugin.py b/os_xenapi/tests/utils/test_xapi_plugin.py new file mode 100644 index 0000000..a5071c6 --- /dev/null +++ b/os_xenapi/tests/utils/test_xapi_plugin.py @@ -0,0 +1,100 @@ +# 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 mock +import os +import shutil +import tempfile + +from os_xenapi.tests import base +from os_xenapi.utils import common_function +from os_xenapi.utils import xapi_plugin + + +class XenapiPluginTestCase(base.TestCase): + @mock.patch.object(tempfile, 'mkdtemp', + return_value='/tmp/tmp1VyeJn') + @mock.patch.object(common_function, 'execute') + def test_get_os_xenapi_dir_with_version(self, mock_exec, mock_tmp): + VERSION = '0.3.1' + is_tmp, dir = xapi_plugin.get_os_xenapi_dir(VERSION) + + self.assertEqual(2, mock_exec.call_count) + self.assertEqual(dir, '/tmp/tmp1VyeJn') + self.assertEqual(is_tmp, True) + + @mock.patch.object(tempfile, 'mkdtemp') + @mock.patch.object(common_function, 'execute') + def test_get_os_xenapi_dir_no_version(self, mock_exec, mock_tmp): + fake_loc = '/fake/install/loc' + mock_exec.return_value = 'Location: %s\n' % fake_loc + + is_tmp, dir = xapi_plugin.get_os_xenapi_dir() + + self.assertEqual(dir, fake_loc) + self.assertEqual(is_tmp, False) + mock_exec.assert_called_with('pip', 'show', + xapi_plugin.OS_XENAPI_PKG) + mock_tmp.assert_not_called() + + @mock.patch.object(xapi_plugin, 'get_os_xenapi_dir') + @mock.patch.object(os, 'listdir') + @mock.patch.object(shutil, 'rmtree') + def test_install_plugins_to_dom0_no_version(self, mock_rm, mock_dir, + mock_get): + FAKE_PKG_PATH = '/fake/pkg/path' + ssh_client = mock.Mock() + mock_get.return_value = (False, FAKE_PKG_PATH) + mock_dir.return_value = ['file1', 'file2'] + + xapi_plugin.install_plugins_to_dom0(ssh_client) + + dom0_path = xapi_plugin.DOM0_PLUGIN_PATH + local_path = '%s/%s' % (FAKE_PKG_PATH, + xapi_plugin.PKG_PLUGIN_PATH) + scp_expect = [mock.call('%s/file1' % local_path, + '%s/file1' % dom0_path), + mock.call('%s/file2' % local_path, + '%s/file2' % dom0_path)] + ssh_expect = [mock.call('chmod +x %s/file1' % dom0_path), + mock.call('chmod +x %s/file2' % dom0_path)] + self.assertEqual(scp_expect, ssh_client.scp.call_args_list) + self.assertEqual(ssh_expect, ssh_client.ssh.call_args_list) + # Shouldn't invoke mock_rm to remove the package dir. + mock_rm.assert_not_called() + + @mock.patch.object(xapi_plugin, 'get_os_xenapi_dir') + @mock.patch.object(os, 'listdir') + @mock.patch.object(shutil, 'rmtree') + def test_install_plugins_to_dom0_with_version(self, mock_rm, mock_dir, + mock_get): + VERSION = '0.3.1' + FAKE_PKG_PATH = '/tmp/tmp1VyeJn' + ssh_client = mock.Mock() + mock_get.return_value = (True, FAKE_PKG_PATH) + mock_dir.return_value = ['file1', 'file2'] + + xapi_plugin.install_plugins_to_dom0(ssh_client, VERSION) + + dom0_path = xapi_plugin.DOM0_PLUGIN_PATH + local_path = '%s/%s' % (FAKE_PKG_PATH, + xapi_plugin.PKG_PLUGIN_PATH) + scp_expect = [mock.call('%s/file1' % local_path, + '%s/file1' % dom0_path), + mock.call('%s/file2' % local_path, + '%s/file2' % dom0_path)] + ssh_expect = [mock.call('chmod +x %s/file1' % dom0_path), + mock.call('chmod +x %s/file2' % dom0_path)] + self.assertEqual(scp_expect, ssh_client.scp.call_args_list) + self.assertEqual(ssh_expect, ssh_client.ssh.call_args_list) + # Should invoke mock_rm to remove the package dir. + mock_rm.assert_called_with(FAKE_PKG_PATH) diff --git a/os_xenapi/utils/xapi_plugin.py b/os_xenapi/utils/xapi_plugin.py new file mode 100644 index 0000000..ee8b6ec --- /dev/null +++ b/os_xenapi/utils/xapi_plugin.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# Copyright 2017 Citrix Systems +# +# 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. +"""XAPI plugin utils + +It contains the utilities relative to XAPI plugins.""" + +import os +import shutil +import sys +import tempfile + +from os_xenapi.utils import common_function as fun +from os_xenapi.utils.sshclient import SSHClient + + +DOM0_PLUGIN_PATH = '/etc/xapi.d/plugins' +PKG_PLUGIN_PATH = 'os_xenapi/dom0/etc/xapi.d/plugins' +OS_XENAPI_PKG = 'os-xenapi' + + +def get_os_xenapi_dir(version=None): + # Get os-xenapi's directory. + # return (is_tmp_dir, os_xenapi_dir), where is_tmp_dir indicates + # if the os_xenapi_dir is a temporary directory. + is_tmp_dir = False + os_xenapi_dir = None + if version: + # If version is specified, then download the specified package. + # And unpack the package. + temp_dir = tempfile.mkdtemp() + fun.execute('pip', 'download', '--no-deps', '-d', temp_dir, + '%s==%s' % (OS_XENAPI_PKG, version)) + fun.execute('unzip', '-d', temp_dir, '%s/*.whl' % temp_dir) + is_tmp_dir = True + os_xenapi_dir = temp_dir + else: + # Check current installed os-xenapi package's location + LOCATION_KEY = 'Location: ' + pkg_info = fun.execute('pip', 'show', OS_XENAPI_PKG).split('\n') + for line in pkg_info: + if line.startswith(LOCATION_KEY): + os_xenapi_dir = line[len(LOCATION_KEY):] + break + return (is_tmp_dir, os_xenapi_dir) + + +def install_plugins_to_dom0(ssh_client, version=None): + is_tmp_dir, dir = get_os_xenapi_dir(version) + plugin_location = '%s/%s' % (dir, PKG_PLUGIN_PATH) + try: + for file in os.listdir(plugin_location): + src_file = '%s/%s' % (plugin_location, file) + dst_file = '%s/%s' % (DOM0_PLUGIN_PATH, file) + ssh_client.scp(src_file, dst_file) + ssh_client.ssh('chmod +x %s' % dst_file) + finally: + if is_tmp_dir: + # delete the temp directory. + shutil.rmtree(dir) + + +if __name__ == '__main__': + # argv[1]: dom0's IP address + # argv[2]: user name + # argv[3]: user passwd + # argv[4]: os-xenapi version (default None) + ssh_client = SSHClient(sys.argv[1], sys.argv[2], sys.argv[3]) + version = None + if len(sys.argv) > 4: + version = sys.argv[4] + install_plugins_to_dom0(ssh_client, version)