482 lines
19 KiB
Python
482 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright 2015 Mirantis, 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.
|
|
|
|
import fixtures
|
|
import mock
|
|
from mock import MagicMock
|
|
from mock import patch
|
|
|
|
from fuelclient.cli import error
|
|
from fuelclient.objects import plugins
|
|
from fuelclient.objects.plugins import Plugins
|
|
from fuelclient.objects.plugins import PluginV1
|
|
from fuelclient.objects.plugins import PluginV2
|
|
from fuelclient.tests.unit.v1 import base
|
|
|
|
|
|
@patch('fuelclient.objects.plugins.raise_error_if_not_master')
|
|
class TestPluginV1(base.UnitTestCase):
|
|
|
|
fake_meta = """
|
|
name: 'plugin_name'
|
|
version: 'version'
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(TestPluginV1, self).setUp()
|
|
self.plugin = PluginV1
|
|
self.path = '/tmp/plugin/path'
|
|
self.name = 'plugin_name'
|
|
self.version = 'version'
|
|
|
|
@patch('fuelclient.objects.plugins.tarfile')
|
|
def test_install(self, tar_mock, master_only_mock):
|
|
tar_obj = MagicMock()
|
|
tar_mock.open.return_value = tar_obj
|
|
|
|
self.plugin.install(self.path)
|
|
|
|
master_only_mock.assert_called_once_with()
|
|
tar_obj.extractall.assert_called_once_with('/var/www/nailgun/plugins/')
|
|
tar_obj.close.assert_called_once_with()
|
|
|
|
@patch('fuelclient.objects.plugins.shutil.rmtree')
|
|
def test_remove(self, rmtree_mock, master_only_mock):
|
|
self.plugin.remove(self.name, self.version)
|
|
|
|
master_only_mock.assert_called_once_with()
|
|
rmtree_mock.assert_called_once_with(
|
|
'/var/www/nailgun/plugins/plugin_name-version')
|
|
|
|
def test_update(self, _):
|
|
self.assertRaisesRegexp(
|
|
error.BadDataException,
|
|
'Update action is not supported for old plugins with '
|
|
'package version "1.0.0", you can install your plugin '
|
|
'or use newer plugin format.',
|
|
self.plugin.update, 'some_string')
|
|
|
|
def test_downgrade(self, _):
|
|
self.assertRaisesRegexp(
|
|
error.BadDataException,
|
|
'Downgrade action is not supported for old plugins with '
|
|
'package version "1.0.0", you can install your plugin '
|
|
'or use newer plugin format.',
|
|
self.plugin.downgrade, 'some_string')
|
|
|
|
def mock_tar(self, tar_mock):
|
|
tar_obj = MagicMock()
|
|
tar_mock.open.return_value = tar_obj
|
|
tar_file = MagicMock()
|
|
tar_obj.getnames.return_value = ['metadata.yaml']
|
|
tar_obj.extractfile.return_value = tar_file
|
|
tar_file.read.return_value = self.fake_meta
|
|
|
|
@patch('fuelclient.objects.plugins.tarfile')
|
|
def test_name_from_file(self, tar_mock, _):
|
|
self.mock_tar(tar_mock)
|
|
|
|
self.assertEqual(
|
|
self.plugin.name_from_file(self.path),
|
|
self.name)
|
|
|
|
@patch('fuelclient.objects.plugins.tarfile')
|
|
def test_version_from_file(self, tar_mock, _):
|
|
self.mock_tar(tar_mock)
|
|
|
|
self.assertEqual(
|
|
self.plugin.version_from_file(self.path),
|
|
self.version)
|
|
|
|
|
|
@patch('fuelclient.objects.plugins.raise_error_if_not_master')
|
|
class TestPluginV2(base.UnitTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestPluginV2, self).setUp()
|
|
self.plugin = PluginV2
|
|
self.path = '/tmp/plugin/path'
|
|
self.name = 'plugin_name'
|
|
self.version = '1.2.3'
|
|
|
|
@patch('fuelclient.objects.plugins.utils.exec_cmd')
|
|
def test_install(self, exec_mock, master_only_mock):
|
|
self.plugin.install(self.path)
|
|
|
|
exec_mock.assert_called_once_with(
|
|
'yum -y install --disablerepo=\'*\' /tmp/plugin/path')
|
|
master_only_mock.assert_called_once_with()
|
|
|
|
@patch('fuelclient.objects.plugins.utils.exec_cmd')
|
|
def test_install_w_force(self, exec_mock, master_only_mock):
|
|
self.plugin.install(self.path, force=True)
|
|
|
|
exec_mock.assert_called_once_with(
|
|
'yum -y install --disablerepo=\'*\' /tmp/plugin/path'
|
|
' || yum -y reinstall --disablerepo=\'*\' /tmp/plugin/path')
|
|
master_only_mock.assert_called_once_with()
|
|
|
|
@patch('fuelclient.objects.plugins.utils.exec_cmd')
|
|
def test_remove(self, exec_mock, master_only_mock):
|
|
self.plugin.remove(self.name, self.version)
|
|
|
|
exec_mock.assert_called_once_with('yum -y remove plugin_name-1.2')
|
|
master_only_mock.assert_called_once_with()
|
|
|
|
@patch('fuelclient.objects.plugins.utils.exec_cmd')
|
|
def test_update(self, exec_mock, master_only_mock):
|
|
self.plugin.update(self.path)
|
|
|
|
exec_mock.assert_called_once_with('yum -y update /tmp/plugin/path')
|
|
master_only_mock.assert_called_once_with()
|
|
|
|
@patch('fuelclient.objects.plugins.utils.exec_cmd')
|
|
def test_downgrade(self, exec_mock, master_only_mock):
|
|
self.plugin.downgrade(self.path)
|
|
|
|
exec_mock.assert_called_once_with('yum -y downgrade /tmp/plugin/path')
|
|
master_only_mock.assert_called_once_with()
|
|
|
|
@patch('fuelclient.objects.plugins.utils.exec_cmd_iterator',
|
|
return_value=['plugin_name-1.2'])
|
|
def test_name_from_file(self, exec_mock, _):
|
|
self.assertEqual(
|
|
self.plugin.name_from_file(self.path),
|
|
self.name)
|
|
|
|
exec_mock.assert_called_once_with(
|
|
"rpm -qp --queryformat '%{name}' /tmp/plugin/path")
|
|
|
|
@patch('fuelclient.objects.plugins.utils.exec_cmd_iterator',
|
|
return_value=['1.2.3'])
|
|
def test_version_from_file(self, exec_mock, _):
|
|
self.assertEqual(
|
|
self.plugin.version_from_file(self.path),
|
|
self.version)
|
|
|
|
exec_mock.assert_called_once_with(
|
|
"rpm -qp --queryformat '%{version}' /tmp/plugin/path")
|
|
|
|
|
|
class TestPluginsObject(base.UnitTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestPluginsObject, self).setUp()
|
|
self.plugin = Plugins
|
|
self.path = '/tmp/plugin/path'
|
|
self.name = 'plugin_name'
|
|
self.version = 'version'
|
|
|
|
def mock_make_obj_by_file(self, make_obj_by_file_mock):
|
|
plugin_obj = MagicMock()
|
|
plugin_obj.name_from_file.return_value = 'retrieved_name'
|
|
plugin_obj.version_from_file.return_value = 'retrieved_version'
|
|
make_obj_by_file_mock.return_value = plugin_obj
|
|
|
|
return plugin_obj
|
|
|
|
@patch('fuelclient.utils.glob_and_parse_yaml',
|
|
return_value=[
|
|
{'name': 'name1', 'version': 'version1'},
|
|
{'name': 'name2', 'version': 'version2'},
|
|
{'name': 'name3', 'version': 'version3'}])
|
|
@patch.object(Plugins, 'update_or_create')
|
|
def test_register(self, up_or_create_mock, glob_parse_mock):
|
|
self.plugin.register('name3', 'version3')
|
|
glob_parse_mock.assert_called_once_with(
|
|
'/var/www/nailgun/plugins/*/metadata.yaml')
|
|
up_or_create_mock.assert_called_once_with(
|
|
{'name': 'name3', 'version': 'version3'},
|
|
force=False)
|
|
|
|
@patch('fuelclient.utils.glob_and_parse_yaml', return_value=[])
|
|
def test_register_raises_error(self, glob_parse_mock):
|
|
self.assertRaisesRegexp(
|
|
error.BadDataException,
|
|
'Plugin name3 with version version3 does '
|
|
'not exist, install it and try again',
|
|
self.plugin.register, 'name3', 'version3')
|
|
|
|
glob_parse_mock.assert_called_once_with(
|
|
'/var/www/nailgun/plugins/*/metadata.yaml')
|
|
|
|
@patch.object(Plugins, 'get_plugin', return_value={'id': 123})
|
|
@patch.object(Plugins.connection, 'delete_request')
|
|
def test_unregister(self, del_mock, get_mock):
|
|
self.plugin.unregister(self.name, self.version)
|
|
get_mock.assert_called_once_with(self.name, self.version)
|
|
del_mock.assert_called_once_with('plugins/123')
|
|
|
|
@patch.object(Plugins, 'register')
|
|
@patch.object(Plugins, 'make_obj_by_file')
|
|
def test_install(self, make_obj_by_file_mock, register_mock):
|
|
plugin_obj = self.mock_make_obj_by_file(make_obj_by_file_mock)
|
|
register_mock.return_value = {'id': 1}
|
|
self.plugin.install(self.path)
|
|
|
|
plugin_obj.install.assert_called_once_with(self.path, force=False)
|
|
register_mock.assert_called_once_with(
|
|
'retrieved_name', 'retrieved_version', force=False)
|
|
|
|
@patch.object(Plugins, 'unregister')
|
|
@patch.object(Plugins, 'make_obj_by_name')
|
|
def test_remove(self, make_obj_by_name_mock, unregister_mock):
|
|
plugin_obj = MagicMock()
|
|
make_obj_by_name_mock.return_value = plugin_obj
|
|
|
|
self.plugin.remove(self.name, self.version)
|
|
|
|
plugin_obj.remove.assert_called_once_with(self.name, self.version)
|
|
unregister_mock.assert_called_once_with(self.name, self.version)
|
|
|
|
@patch.object(Plugins.connection, 'post_request')
|
|
def test_sync(self, post_mock):
|
|
self.plugin.sync()
|
|
post_mock.assert_called_once_with(
|
|
api='plugins/sync/', data=None)
|
|
|
|
@patch.object(Plugins.connection, 'post_request')
|
|
def test_sync_with_specific_plugins(self, post_mock):
|
|
self.plugin.sync(plugin_ids=[1, 2])
|
|
data = {'ids': [1, 2]}
|
|
post_mock.assert_called_once_with(
|
|
api='plugins/sync/', data=data)
|
|
|
|
@patch.object(Plugins, 'register')
|
|
@patch.object(Plugins, 'make_obj_by_file')
|
|
def test_update(self, make_obj_by_file_mock, register_mock):
|
|
plugin_obj = self.mock_make_obj_by_file(make_obj_by_file_mock)
|
|
|
|
self.plugin.update(self.path)
|
|
|
|
plugin_obj.update.assert_called_once_with(self.path)
|
|
register_mock.assert_called_once_with(
|
|
'retrieved_name', 'retrieved_version')
|
|
|
|
@patch.object(Plugins, 'register')
|
|
@patch.object(Plugins, 'make_obj_by_file')
|
|
def test_downgrade(self, make_obj_by_file_mock, register_mock):
|
|
plugin_obj = self.mock_make_obj_by_file(make_obj_by_file_mock)
|
|
|
|
self.plugin.downgrade(self.path)
|
|
|
|
plugin_obj.downgrade.assert_called_once_with(self.path)
|
|
register_mock.assert_called_once_with(
|
|
'retrieved_name', 'retrieved_version')
|
|
|
|
@patch.object(Plugins, 'get_plugin')
|
|
def test_make_obj_by_name_v1(self, get_mock):
|
|
plugins = [{'package_version': '1.0.0'},
|
|
{'package_version': '1.0.1'},
|
|
{'package_version': '1.99.99'}]
|
|
|
|
for plugin in plugins:
|
|
get_mock.return_value = plugin
|
|
self.assertEqual(
|
|
self.plugin.make_obj_by_name(self.name, self.version),
|
|
PluginV1)
|
|
|
|
@patch.object(Plugins, 'get_plugin')
|
|
def test_make_obj_by_name_v2(self, get_mock):
|
|
plugins = [{'package_version': '2.0.0'},
|
|
{'package_version': '2.0.1'},
|
|
{'package_version': '3.0.0'}]
|
|
|
|
for plugin in plugins:
|
|
get_mock.return_value = plugin
|
|
self.assertEqual(
|
|
self.plugin.make_obj_by_name(self.name, self.version),
|
|
PluginV2)
|
|
|
|
@patch.object(Plugins, 'get_plugin')
|
|
def test_make_obj_by_name_v2_raises_error(self, get_mock):
|
|
get_mock.return_value = {'package_version': '0.0.1'}
|
|
|
|
self.assertRaisesRegexp(
|
|
error.BadDataException,
|
|
'Plugin plugin_name==version has '
|
|
'unsupported package version 0.0.1',
|
|
self.plugin.make_obj_by_name, self.name, self.version)
|
|
|
|
def test_make_obj_by_file_v1(self):
|
|
self.assertEqual(
|
|
self.plugin.make_obj_by_file('file-name-1.2.3.fp'),
|
|
PluginV1)
|
|
|
|
def test_make_obj_by_file_v2(self):
|
|
self.assertEqual(
|
|
self.plugin.make_obj_by_file('file-name-1.2-1.2.3-0.noarch.rpm'),
|
|
PluginV2)
|
|
|
|
def test_make_obj_by_file_raises_error(self):
|
|
self.assertRaisesRegexp(
|
|
error.BadDataException,
|
|
'Plugin file-name.ext has unsupported format .ext',
|
|
self.plugin.make_obj_by_file, 'file-name.ext')
|
|
|
|
@patch.object(Plugins, 'get_plugin_for_update', return_value={'id': 99})
|
|
@patch.object(Plugins.connection, 'put_request', return_value={'id': 99})
|
|
def test_update_or_create_updates(self, put_mock, get_for_update_mock):
|
|
meta = {'id': 99, 'version': '1.0.0', 'package_version': '2.0.0'}
|
|
self.plugin.update_or_create(meta)
|
|
put_mock.assert_called_once_with('plugins/99', meta)
|
|
|
|
@patch.object(Plugins, 'get_plugin_for_update', return_value=None)
|
|
@patch.object(Plugins.connection, 'post_request_raw',
|
|
return_value=MagicMock(status_code=201))
|
|
@patch.object(Plugins.connection, 'put_request')
|
|
def test_update_or_create_creates(
|
|
self, put_mock, post_mock, get_for_update_mock):
|
|
meta = {'id': 99, 'version': '1.0.0', 'package_version': '2.0.0'}
|
|
self.plugin.update_or_create(meta)
|
|
post_mock.assert_called_once_with('plugins/', meta)
|
|
get_for_update_mock.assert_called_once_with(meta)
|
|
self.assertFalse(put_mock.called)
|
|
|
|
@patch.object(Plugins, 'get_plugin_for_update', return_value=None)
|
|
@patch.object(Plugins.connection, 'post_request_raw',
|
|
return_value=MagicMock(
|
|
status_code=409,
|
|
**{'json.return_value': {'id': 99}}))
|
|
@patch.object(Plugins.connection, 'put_request', return_value='put_return')
|
|
def test_update_or_create_updates_without_force(
|
|
self, put_mock, post_mock, get_for_update_mock):
|
|
meta = {'id': 99, 'version': '1.0.0', 'package_version': '2.0.0',
|
|
'title': 'Plugin title'}
|
|
self.assertRaises(SystemExit,
|
|
self.plugin.update_or_create,
|
|
meta,
|
|
force=False)
|
|
|
|
@patch.object(Plugins, 'get_plugin_for_update', return_value=None)
|
|
@patch.object(Plugins.connection, 'post_request_raw',
|
|
return_value=MagicMock(
|
|
status_code=409,
|
|
**{'json.return_value': {'id': 99}}))
|
|
@patch.object(Plugins.connection, 'put_request', return_value='put_return')
|
|
def test_update_or_create_updates_with_force(
|
|
self, put_mock, post_mock, get_for_update_mock):
|
|
meta = {'id': 99, 'version': '1.0.0', 'package_version': '2.0.0'}
|
|
self.assertEqual(
|
|
self.plugin.update_or_create(meta, force=True),
|
|
'put_return')
|
|
post_mock.assert_called_once_with('plugins/', meta)
|
|
get_for_update_mock.assert_called_once_with(meta)
|
|
put_mock.assert_called_once_with('plugins/99', meta)
|
|
|
|
@patch.object(Plugins, 'get_all_data')
|
|
def test_get_plugin_for_update(self, get_mock):
|
|
plugin_to_be_found = {'name': 'name', 'version': '2.2.0',
|
|
'package_version': '2.0.0'}
|
|
|
|
get_mock.return_value = [
|
|
# Different major version
|
|
{'name': 'name', 'version': '2.3.0',
|
|
'package_version': '2.0.0'},
|
|
{'name': 'name', 'version': '2.1.0',
|
|
'package_version': '2.0.0'},
|
|
# Different name
|
|
{'name': 'different_name', 'version': '2.2.99',
|
|
'package_version': '2.0.0'},
|
|
# Package version is not updatable
|
|
{'name': 'name', 'version': '2.2.100',
|
|
'package_version': '1.0.0'},
|
|
plugin_to_be_found]
|
|
|
|
self.assertEqual(
|
|
self.plugin.get_plugin_for_update(
|
|
{'name': 'name',
|
|
'version': '2.2.99',
|
|
'package_version': '2.0.0'}),
|
|
plugin_to_be_found)
|
|
|
|
# Required plugin has not updatable package version
|
|
self.assertIsNone(self.plugin.get_plugin_for_update(
|
|
{'name': 'name', 'version': '2.2.99', 'package_version': '1.0.0'}))
|
|
|
|
# Plugin does not exist
|
|
self.assertIsNone(self.plugin.get_plugin_for_update(
|
|
{'name': 'name2', 'version': '2.2.9', 'package_version': '2.0.0'}))
|
|
|
|
def test_is_updatable(self):
|
|
for updatable in ['2.0.0', '2.0.1', '99.99.99']:
|
|
self.assertTrue(self.plugin.is_updatable(updatable))
|
|
|
|
for is_not_updatable in ['0.0.1', '1.0.0', '1.99.99']:
|
|
self.assertFalse(self.plugin.is_updatable(is_not_updatable))
|
|
|
|
@patch.object(Plugins, 'get_all_data',
|
|
return_value=[{'name': 'name1', 'version': '1.0.0'},
|
|
{'name': 'name2', 'version': '1.0.1'},
|
|
{'name': 'name2', 'version': '1.0.0'}])
|
|
def test_get_plugin(self, get_mock):
|
|
self.assertEqual(self.plugin.get_plugin('name2', '1.0.0'),
|
|
{'name': 'name2', 'version': '1.0.0'})
|
|
get_mock.assert_called_once_with()
|
|
|
|
@patch('fuelclient.objects.plugins.utils.find_exec')
|
|
@patch('fuelclient.objects.plugins.subprocess.Popen')
|
|
def test_raise_error_if_not_master(self, mpop, mfe):
|
|
plugins.IS_MASTER = None
|
|
process = MagicMock()
|
|
mfe.return_value = '/bin/rpm'
|
|
mpop.return_value = process
|
|
process.poll.return_value = 0
|
|
self.assertIsNone(plugins.raise_error_if_not_master())
|
|
mpop.assert_called_once_with(
|
|
['/bin/rpm', '-q', plugins.FUEL_PACKAGE],
|
|
stdout=mock.ANY, stderr=mock.ANY)
|
|
process.poll.assert_called_once_with()
|
|
process.communicate.assert_called_once_with()
|
|
|
|
@patch('fuelclient.objects.plugins.utils.find_exec')
|
|
@patch('fuelclient.objects.plugins.subprocess.Popen')
|
|
def test_raise_error_if_not_master_fuel_not_installed(self, mpop, mfe):
|
|
plugins.IS_MASTER = None
|
|
process = MagicMock()
|
|
mpop.return_value = process
|
|
process.poll.return_value = 1
|
|
self.assertRaises(error.WrongEnvironmentError,
|
|
plugins.raise_error_if_not_master)
|
|
|
|
@patch('fuelclient.objects.plugins.utils.find_exec')
|
|
def test_raise_error_if_not_master_rpm_not_found(self, mfe):
|
|
plugins.IS_MASTER = None
|
|
mfe.return_value = None
|
|
self.assertRaises(error.WrongEnvironmentError,
|
|
plugins.raise_error_if_not_master)
|
|
|
|
@patch('fuelclient.objects.plugins.os.access')
|
|
@patch('fuelclient.objects.plugins.os.path.isfile')
|
|
def test_find_exec(self, misf, macc):
|
|
misf.side_effect = [False, True, True]
|
|
macc.side_effect = [False, True]
|
|
# The combination will be like:
|
|
# 1) /foo/program does not exist
|
|
# 2) /bar/program does exist but isn't executable
|
|
# 3) /baz/program does exist and is executable
|
|
# So the function is to search 'program' in paths /foo, /bar, /baz
|
|
# and return /baz/program
|
|
self.useFixture(fixtures.EnvironmentVariable('PATH', '/foo:/bar:/baz'))
|
|
self.assertEqual('/baz/program', plugins.utils.find_exec('program'))
|
|
|
|
@patch('fuelclient.objects.plugins.os.access')
|
|
@patch('fuelclient.objects.plugins.os.path.isfile')
|
|
def test_find_exec_not_found(self, misf, macc):
|
|
misf.return_value = False
|
|
self.useFixture(fixtures.EnvironmentVariable('PATH', '/foo'))
|
|
self.assertIsNone(plugins.utils.find_exec('program'))
|