fuel-web/nailgun/nailgun/test/integration/test_plugins_api.py

566 lines
20 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2014 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 mock
import os
from oslo_serialization import jsonutils
import yaml
from nailgun import objects
from nailgun.plugins import adapters
from nailgun.test import base
def get_config(config):
def _get_config(*args):
return mock.mock_open(read_data=yaml.dump(config))()
return _get_config
class BasePluginTest(base.BaseIntegrationTest):
TASKS_CONFIG = [
{'priority': 10,
'role': ['controller'],
'type': 'shell',
'parameters': {'cmd': './lbaas_enable.sh', 'timeout': 42},
'stage': 'post_deployment'},
{'priority': 10,
'role': '*',
'type': 'shell',
'parameters': {'cmd': 'echo all > /tmp/plugin.all', 'timeout': 42},
'stage': 'pre_deployment'}]
def setUp(self):
super(BasePluginTest, self).setUp()
self.sample_plugin = self.env.get_default_plugin_metadata()
self.plugin_env_config = self.env.get_default_plugin_env_config()
attributes_metadata = self.env.get_default_plugin_env_config()
roles_metadata = self.env.get_default_plugin_node_roles_config()
volumes_metadata = self.env.get_default_plugin_volumes_config()
network_roles_metadata = self.env.get_default_network_roles_config()
deployment_tasks = self.env.get_default_plugin_deployment_tasks()
tasks = self.env.get_default_plugin_tasks()
self.mocked_metadata = {
'environment_config.yaml': attributes_metadata,
'node_roles.yaml': roles_metadata,
'volumes.yaml': volumes_metadata,
'network_roles.yaml': network_roles_metadata,
'deployment_tasks.yaml': deployment_tasks,
'tasks.yaml': tasks,
}
@mock.patch('nailgun.plugins.adapters.PluginAdapterBase._load_config')
def create_plugin(self, m_load_conf, sample=None, expect_errors=False):
def mock_load_config(configs, default_config):
def _load_config(config):
head, tail = os.path.split(config)
return configs.get(tail, default_config)
return _load_config
m_load_conf.side_effect = mock_load_config(
self.mocked_metadata,
self.plugin_env_config
)
sample = sample or self.sample_plugin
resp = self.app.post(
base.reverse('PluginCollectionHandler'),
jsonutils.dumps(sample),
headers=self.default_headers,
expect_errors=expect_errors
)
return resp
def delete_plugin(self, plugin_id, expect_errors=False):
resp = self.app.delete(
base.reverse('PluginHandler', {'obj_id': plugin_id}),
headers=self.default_headers,
expect_errors=expect_errors
)
return resp
def create_cluster(self, nodes=None):
nodes = nodes if nodes else []
with mock.patch('nailgun.plugins.adapters.os') as os:
with mock.patch(
'nailgun.plugins.adapters.open',
create=True,
side_effect=get_config(self.plugin_env_config)):
os.access.return_value = True
os.path.exists.return_value = True
self.env.create(
release_kwargs={'version': '2014.2-6.0',
'operating_system': 'Ubuntu',
'deployment_tasks': []},
nodes_kwargs=nodes)
return self.env.clusters[0]
def default_attributes(self, cluster):
resp = self.app.get(
base.reverse('ClusterAttributesDefaultsHandler',
{'cluster_id': cluster.id}),
headers=self.default_headers)
return resp
def modify_plugin(self, cluster, plugin_name, plugin_id, enabled):
editable_attrs = objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True)
editable_attrs[plugin_name]['metadata']['enabled'] = enabled
editable_attrs[plugin_name]['metadata']['chosen_id'] = plugin_id
resp = self.app.put(
base.reverse('ClusterAttributesHandler',
{'cluster_id': cluster.id}),
jsonutils.dumps({'editable': editable_attrs}),
headers=self.default_headers)
return resp
def enable_plugin(self, cluster, plugin_name, plugin_id):
return self.modify_plugin(cluster, plugin_name, plugin_id, True)
def disable_plugin(self, cluster, plugin_name):
return self.modify_plugin(cluster, plugin_name, None, False)
def get_pre_hooks(self, cluster):
with mock.patch('nailgun.plugins.adapters.glob') as glob:
glob.glob.return_value = ['/some/path']
with mock.patch('nailgun.plugins.adapters.os') as os:
with mock.patch(
'nailgun.plugins.adapters.open',
create=True,
side_effect=get_config(self.TASKS_CONFIG)):
os.access.return_value = True
os.path.exists.return_value = True
resp = self.app.get(
base.reverse('DefaultPrePluginsHooksInfo',
{'cluster_id': cluster.id}),
headers=self.default_headers)
return resp
def get_post_hooks(self, cluster):
with mock.patch('nailgun.plugins.adapters.os') as os:
with mock.patch(
'nailgun.plugins.adapters.open',
create=True,
side_effect=get_config(self.TASKS_CONFIG)):
os.access.return_value = True
os.path.exists.return_value = True
resp = self.app.get(
base.reverse('DefaultPostPluginsHooksInfo',
{'cluster_id': cluster.id}),
headers=self.default_headers)
return resp
def sync_plugins(self, params=None, expect_errors=False):
post_data = jsonutils.dumps(params) if params else ''
resp = self.app.post(
base.reverse('PluginSyncHandler'),
post_data,
headers=self.default_headers,
expect_errors=expect_errors
)
self.assertValidJSON(resp.body)
return resp
class TestPluginsApi(BasePluginTest):
def test_plugin_created_on_post(self):
resp = self.create_plugin()
self.assertEqual(resp.status_code, 201)
metadata = resp.json
del metadata['id']
self.assertEqual(metadata, self.sample_plugin)
def test_env_create_and_load_env_config(self):
self.create_plugin()
cluster = self.create_cluster()
self.assertIn(self.sample_plugin['name'],
objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True))
def test_enable_disable_plugin(self):
resp = self.create_plugin()
plugin = objects.Plugin.get_by_uid(resp.json['id'])
cluster = self.create_cluster()
self.assertItemsEqual(
[],
objects.ClusterPlugins.get_enabled(cluster.id)
)
resp = self.enable_plugin(cluster, plugin.name, plugin.id)
self.assertEqual(resp.status_code, 200)
self.assertItemsEqual(
[plugin],
objects.ClusterPlugins.get_enabled(cluster.id)
)
resp = self.disable_plugin(cluster, plugin.name)
self.assertEqual(resp.status_code, 200)
self.assertItemsEqual(
[],
objects.ClusterPlugins.get_enabled(cluster.id)
)
def test_delete_plugin(self):
resp = self.create_plugin()
del_resp = self.delete_plugin(resp.json['id'])
self.assertEqual(del_resp.status_code, 204)
def test_delete_unused_plugin(self):
self.create_cluster()
resp = self.create_plugin()
del_resp = self.delete_plugin(resp.json['id'])
self.assertEqual(del_resp.status_code, 204)
def test_no_delete_of_used_plugin(self):
resp = self.create_plugin()
plugin = objects.Plugin.get_by_uid(resp.json['id'])
cluster = self.create_cluster()
enable_resp = self.enable_plugin(cluster, plugin.name, plugin.id)
self.assertEqual(enable_resp.status_code, 200)
del_resp = self.delete_plugin(resp.json['id'], expect_errors=True)
self.assertEqual(del_resp.status_code, 400)
def test_update_plugin(self):
resp = self.create_plugin()
data = resp.json
data['package_version'] = '2.0.0'
plugin_id = data.pop('id')
resp = self.app.put(
base.reverse('PluginHandler', {'obj_id': plugin_id}),
jsonutils.dumps(data),
headers=self.default_headers
)
self.assertEqual(resp.status_code, 200)
updated_data = resp.json
updated_plugin_id = updated_data.pop('id')
self.assertEqual(plugin_id, updated_plugin_id)
self.assertEqual(updated_data, data)
def test_default_attributes_after_plugin_is_created(self):
self.create_plugin()
cluster = self.create_cluster()
default_attributes = self.default_attributes(cluster)
self.assertIn(self.sample_plugin['name'],
default_attributes.json_body['editable'])
def test_attributes_after_plugin_is_created(self):
sample = dict(
attributes_metadata={
"attr_text": {
"value": "value",
"type": "text",
"description": "description",
"weight": 25,
"label": "label"
}
}, **self.sample_plugin)
self.mocked_metadata['environment_config.yaml'] = {
'attributes': sample['attributes_metadata']
}
self.create_plugin(sample=sample).json_body
cluster = self.create_cluster()
editable = self.default_attributes(cluster).json_body['editable']
self.assertIn(
'attr_text',
editable[self.sample_plugin['name']]['metadata']['versions'][0]
)
def test_plugins_multiversioning(self):
def create_with_version(plugin_version):
response = self.create_plugin(
sample=self.env.get_default_plugin_metadata(
name='multiversion_plugin',
version=plugin_version
)
)
return response.json_body['id']
def get_num_enabled(cluster_id):
return objects.ClusterPlugins.get_enabled(cluster_id).count()
def get_enabled_version(cluster_id):
plugin = objects.ClusterPlugins.get_enabled(cluster_id).first()
return plugin.version
plugin_ids = []
for version in ['1.0.0', '2.0.0', '0.0.1']:
plugin_ids.append(create_with_version(version))
cluster = self.create_cluster()
self.assertEqual(get_num_enabled(cluster.id), 0)
self.enable_plugin(cluster, 'multiversion_plugin', plugin_ids[1])
self.assertEqual(get_num_enabled(cluster.id), 1)
self.assertEqual(get_enabled_version(cluster.id), '2.0.0')
# Create new plugin after environment is created
plugin_ids.append(create_with_version('5.0.0'))
self.assertEqual(len(cluster.plugins), 4)
self.assertEqual(get_num_enabled(cluster.id), 1)
self.assertEqual(get_enabled_version(cluster.id), '2.0.0')
self.enable_plugin(cluster, 'multiversion_plugin', plugin_ids[3])
self.assertEqual(get_num_enabled(cluster.id), 1)
self.assertEqual(get_enabled_version(cluster.id), '5.0.0')
self.disable_plugin(cluster, 'multiversion_plugin')
self.assertEqual(get_num_enabled(cluster.id), 0)
def test_sync_all_plugins(self):
self._create_new_and_old_version_plugins_for_sync()
resp = self.sync_plugins()
self.assertEqual(resp.status_code, 200)
def test_sync_specific_plugins(self):
plugin_ids = self._create_new_and_old_version_plugins_for_sync()
ids = plugin_ids[:1]
resp = self.sync_plugins(params={'ids': ids})
self.assertEqual(resp.status_code, 200)
def test_sync_failed_when_plugin_not_found(self):
plugin_ids = self._create_new_and_old_version_plugins_for_sync()
ids = [plugin_ids.pop() + 1]
resp = self.sync_plugins(params={'ids': ids}, expect_errors=True)
self.assertEqual(resp.status_code, 404)
@mock.patch('nailgun.plugins.adapters.open', create=True)
@mock.patch('nailgun.plugins.adapters.os.access')
def test_sync_with_invalid_yaml_files(self, maccess, mopen):
maccess.return_value = True
self._create_new_and_old_version_plugins_for_sync()
with mock.patch.object(yaml, 'safe_load') as yaml_safe_load:
yaml_safe_load.side_effect = yaml.YAMLError()
resp = self.sync_plugins(expect_errors=True)
self.assertEqual(resp.status_code, 400)
self.assertRegexpMatches(
resp.json_body["message"],
'Problem with loading YAML file')
def _create_new_and_old_version_plugins_for_sync(self):
plugin_ids = []
old_version_plugin = {
'name': 'test_name_0',
'version': '0.1.1',
'fuel_version': ['6.0'],
'title': 'Test plugin',
'package_version': '1.0.0',
'releases': [
{'os': 'Ubuntu',
'mode': ['ha', 'multinode'],
'version': '2014.2.1-5.1'}
],
}
resp = self.create_plugin(sample=old_version_plugin)
self.assertEqual(resp.status_code, 201)
new_version_plugin_1 = {
'name': 'test_name_1',
'version': '0.1.1',
'fuel_version': ['7.0'],
'title': 'Test plugin',
'package_version': '3.0.0',
'releases': [
{'os': 'Ubuntu',
'mode': ['ha', 'multinode'],
'version': '2014.2.1-5.1'}
],
}
resp = self.create_plugin(sample=new_version_plugin_1)
self.assertEqual(resp.status_code, 201)
# Only plugins with version 3.0.0 will be synced
plugin_ids.append(resp.json['id'])
new_version_plugin_2 = {
'name': 'test_name_2',
'version': '0.1.1',
'fuel_version': ['7.0'],
'title': 'Test plugin',
'package_version': '3.0.0',
'releases': [
{'os': 'Ubuntu',
'mode': ['ha'],
'version': '2014.2.1-5.1'}
],
}
resp = self.create_plugin(sample=new_version_plugin_2)
self.assertEqual(resp.status_code, 201)
plugin_ids.append(resp.json['id'])
return plugin_ids
class TestPrePostHooks(BasePluginTest):
def setUp(self):
super(TestPrePostHooks, self).setUp()
self._requests_mock = mock.patch(
'nailgun.utils.debian.requests.get',
return_value=mock.Mock(text='Archive: test'))
self._requests_mock.start()
resp = self.create_plugin()
self.plugin = adapters.wrap_plugin(
objects.Plugin.get_by_uid(resp.json['id']))
self.cluster = self.create_cluster([
{'roles': ['controller'], 'pending_addition': True},
{'roles': ['compute'], 'pending_addition': True}])
objects.Cluster.prepare_for_deployment(self.cluster)
self.enable_plugin(
self.cluster, self.sample_plugin['name'], resp.json['id']
)
def tearDown(self):
self._requests_mock.stop()
super(TestPrePostHooks, self).tearDown()
def test_generate_pre_hooks(self):
tasks = self.get_pre_hooks(self.cluster).json
plugins_tasks = [t for t in tasks if t.get('diagnostic_name')]
upload_file = [t for t in plugins_tasks if t['type'] == 'upload_file']
rsync = [t for t in plugins_tasks if t['type'] == 'sync']
cmd_tasks = [t for t in plugins_tasks if t['type'] == 'shell']
self.assertEqual(len(upload_file), 2)
self.assertEqual(len(rsync), 1)
self.assertEqual(len(cmd_tasks), 2)
for t in plugins_tasks:
# should uid be a string
self.assertEqual(
sorted(t['uids']), sorted([n.uid for n in self.cluster.nodes]))
# diagnostic name is present only for plugin tasks
self.assertEqual(t['diagnostic_name'], self.plugin.full_name)
apt_update = [t for t in cmd_tasks
if u'apt-get update' in t['parameters']['cmd']]
self.assertEqual(len(apt_update), 1)
def test_generate_post_hooks(self):
tasks = self.get_post_hooks(self.cluster).json
self.assertEqual(len(tasks), 1)
task = tasks[0]
controller_id = [n.uid for n in self.cluster.nodes
if 'controller' in n.roles]
self.assertEqual(controller_id, task['uids'])
self.assertEqual(task['diagnostic_name'], self.plugin.full_name)
class TestPluginValidation(BasePluginTest):
def test_valid(self):
sample = {
'name': 'test_name',
'version': '0.1.1',
'fuel_version': ['6.0'],
'title': 'Test plugin',
'package_version': '1.0.0',
'releases': [
{'os': 'Ubuntu',
'mode': ['ha', 'multinode'],
'version': '2014.2.1-5.1'}
],
}
resp = self.create_plugin(sample=sample)
self.assertEqual(resp.status_code, 201)
def test_releases_not_provided(self):
sample = {
'name': 'test_name',
'version': '0.1.1',
'fuel_version': ['6.0'],
'title': 'Test plugin',
'package_version': '1.0.0'
}
resp = self.create_plugin(sample=sample, expect_errors=True)
self.assertEqual(resp.status_code, 400)
def test_version_is_not_present_in_release_data(self):
sample = {
'name': 'test_name',
'version': '0.1.1',
'fuel_version': ['6.0'],
'title': 'Test plugin',
'package_version': '1.0.0',
'releases': [
{'os': 'Ubuntu', 'mode': ['ha', 'multinode']}
]
}
resp = self.create_plugin(sample=sample, expect_errors=True)
self.assertEqual(resp.status_code, 400)
def test_plugin_version_is_floating(self):
sample = {
'name': 'test_name',
'title': 'Test plugin',
'version': 1.1,
'fuel_version': ['6.0'],
'package_version': '1.0.0',
'releases': [
{'os': 'Ubuntu',
'mode': ['ha', 'multinode'],
'version': '2014.2.1-5.1'}
]
}
resp = self.create_plugin(sample=sample, expect_errors=True)
self.assertEqual(resp.status_code, 400)
def test_title_is_not_present(self):
sample = {
'name': 'test_name',
'version': '1.1',
'fuel_version': ['6.0'],
'package_version': '1.0.0',
'releases': [
{'os': 'Ubuntu',
'mode': ['multinode'],
'version': '2014.2.1-5.1'}
]
}
resp = self.create_plugin(sample=sample, expect_errors=True)
self.assertEqual(resp.status_code, 400)
class TestPluginSyncValidation(BasePluginTest):
def test_valid(self):
resp = self.sync_plugins()
self.assertEqual(resp.status_code, 200)
def test_ids_not_present(self):
sample = {'test': '1'}
resp = self.sync_plugins(params=sample, expect_errors=True)
self.assertEqual(resp.status_code, 400)
def test_ids_not_array_type(self):
sample = {'ids': {}}
resp = self.sync_plugins(params=sample, expect_errors=True)
self.assertEqual(resp.status_code, 400)