350 lines
14 KiB
Python
350 lines
14 KiB
Python
# 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 mock
|
|
import six.moves.urllib.parse as urlparse
|
|
|
|
from oslo_serialization import jsonutils
|
|
|
|
from nailgun import consts
|
|
from nailgun.objects.serializers.openstack_config import \
|
|
OpenstackConfigSerializer
|
|
from nailgun.test.base import BaseIntegrationTest
|
|
from nailgun.utils import reverse
|
|
|
|
|
|
class TestOpenstackConfigHandlers(BaseIntegrationTest):
|
|
|
|
def setUp(self):
|
|
super(TestOpenstackConfigHandlers, self).setUp()
|
|
|
|
self.env.create_cluster(api=False,
|
|
status=consts.CLUSTER_STATUSES.operational)
|
|
self.env.create_cluster(api=False,
|
|
status=consts.CLUSTER_STATUSES.operational)
|
|
|
|
self.clusters = self.env.clusters
|
|
self.nodes = self.env.create_nodes(
|
|
3, cluster_id=self.clusters[0].id,
|
|
status=consts.NODE_STATUSES.ready)
|
|
|
|
self.env.create_openstack_config(
|
|
cluster_id=self.clusters[0].id, configuration={})
|
|
self.env.create_openstack_config(
|
|
cluster_id=self.clusters[0].id, node_id=self.nodes[1].id,
|
|
configuration={
|
|
'nova_config': 'value_2_1'
|
|
})
|
|
self.env.create_openstack_config(
|
|
cluster_id=self.clusters[0].id, node_id=self.nodes[1].id,
|
|
configuration={
|
|
'nova_config': 'value_1_1'
|
|
})
|
|
|
|
self.configs = self.env.openstack_configs
|
|
|
|
def create_running_deployment_task(self):
|
|
return self.env.create_task(
|
|
cluster_id=self.clusters[0].id,
|
|
name=consts.TASK_NAMES.deployment,
|
|
status=consts.TASK_STATUSES.running
|
|
).id
|
|
|
|
def check_fail_deploy_running(self, deploy_task_id, resp):
|
|
self.assertEqual(resp.status_code, 403)
|
|
self.assertEqual("Cannot perform the action because there are "
|
|
"running deployment tasks '{0}'"
|
|
"".format(deploy_task_id), resp.json_body['message'])
|
|
|
|
def test_openstack_config_upload_new(self):
|
|
data = {
|
|
'cluster_id': self.clusters[0].id,
|
|
'node_id': self.nodes[0].id,
|
|
'configuration': {}
|
|
}
|
|
|
|
resp = self.app.post(
|
|
reverse('OpenstackConfigCollectionHandler'),
|
|
jsonutils.dumps(data),
|
|
headers=self.default_headers)
|
|
self.assertEqual(resp.status_code, 201)
|
|
resp_data = resp.json_body
|
|
self.assertEqual(resp_data['cluster_id'], self.clusters[0].id)
|
|
self.assertEqual(resp_data['node_id'], self.nodes[0].id)
|
|
|
|
def test_openstack_config_upload_override(self):
|
|
data = {
|
|
'cluster_id': self.clusters[0].id,
|
|
'node_id': self.nodes[1].id,
|
|
'configuration': {
|
|
'nova_config': 'value_1_2'
|
|
}
|
|
}
|
|
resp = self.app.post(
|
|
reverse('OpenstackConfigCollectionHandler'),
|
|
jsonutils.dumps(data),
|
|
headers=self.default_headers)
|
|
self.assertEqual(resp.status_code, 201)
|
|
resp_data = resp.json_body
|
|
self.assertEqual(resp_data['cluster_id'], self.clusters[0].id)
|
|
self.assertEqual(resp_data['node_id'], self.nodes[1].id)
|
|
|
|
resp = self.app.get(
|
|
reverse('OpenstackConfigHandler',
|
|
{'obj_id': self.configs[1].id}),
|
|
headers=self.default_headers)
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(resp.json_body['is_active'], False)
|
|
|
|
def test_openstack_config_upload_fail(self):
|
|
data = {
|
|
'cluster_id': self.clusters[1].id,
|
|
'node_id': self.nodes[1].id,
|
|
'configuration': {}
|
|
}
|
|
resp = self.app.post(
|
|
reverse('OpenstackConfigCollectionHandler'),
|
|
jsonutils.dumps(data),
|
|
headers=self.default_headers, expect_errors=True)
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertEqual(
|
|
resp.json_body['message'],
|
|
"Node '{0}' is not assigned to cluster '{1}'".format(
|
|
self.nodes[1].id, self.clusters[1].id))
|
|
|
|
def test_openstack_config_upload_fail_not_supported_config(self):
|
|
"""Test for uploading an update for not supported OpenStack config"""
|
|
data = {
|
|
'cluster_id': self.clusters[0].id,
|
|
'node_id': self.nodes[0].id,
|
|
'configuration': {
|
|
'not_supported_config': {}
|
|
}
|
|
}
|
|
|
|
resp = self.app.post(
|
|
reverse('OpenstackConfigCollectionHandler'),
|
|
jsonutils.dumps(data),
|
|
headers=self.default_headers,
|
|
expect_errors=True)
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertRegexpMatches(
|
|
resp.json_body['message'],
|
|
r"Configurations '\w+' can not be updated")
|
|
|
|
def test_openstack_config_upload_fail_deploy_running(self):
|
|
deploy_task_id = self.create_running_deployment_task()
|
|
data = {
|
|
'cluster_id': self.clusters[0].id,
|
|
'configuration': {}
|
|
}
|
|
|
|
resp = self.app.post(
|
|
reverse('OpenstackConfigCollectionHandler'),
|
|
jsonutils.dumps(data),
|
|
headers=self.default_headers,
|
|
expect_errors=True)
|
|
self.check_fail_deploy_running(deploy_task_id, resp)
|
|
|
|
def test_openstack_config_list(self):
|
|
url = self._make_filter_url(cluster_id=self.clusters[0].id)
|
|
resp = self.app.get(url, headers=self.default_headers)
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(len(resp.json_body), 2)
|
|
|
|
url = self._make_filter_url(
|
|
cluster_id=self.clusters[0].id, node_id=self.nodes[1].id)
|
|
resp = self.app.get(url, headers=self.default_headers)
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(len(resp.json_body), 1)
|
|
|
|
url = self._make_filter_url(
|
|
cluster_id=self.clusters[0].id, is_active=0)
|
|
resp = self.app.get(url, headers=self.default_headers)
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(len(resp.json_body), 1)
|
|
self.assertFalse(resp.json_body[0]['is_active'])
|
|
|
|
url = self._make_filter_url(cluster_id=self.clusters[1].id)
|
|
resp = self.app.get(url, headers=self.default_headers)
|
|
self.assertEqual(len(resp.json_body), 0)
|
|
|
|
def test_openstack_config_list_fail(self):
|
|
url = self._make_filter_url(
|
|
cluster_id=self.clusters[0].id, node_id=self.nodes[0].id,
|
|
node_role='controller')
|
|
resp = self.app.get(url, headers=self.default_headers,
|
|
expect_errors=True)
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertRegexpMatches(
|
|
resp.json_body['message'],
|
|
r"Parameter '\w+' conflicts with '\w+(, \w+)*'")
|
|
|
|
def test_openstack_config_get(self):
|
|
resp = self.app.get(
|
|
reverse('OpenstackConfigHandler',
|
|
{'obj_id': self.configs[0].id}),
|
|
headers=self.default_headers)
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
config = resp.json_body
|
|
self.assertDictContainsSubset({
|
|
'cluster_id': self.configs[0].cluster_id,
|
|
'node_id': self.configs[0].node_id,
|
|
'node_role': self.configs[0].node_role,
|
|
}, config)
|
|
self.assertEqual(sorted(config.keys()),
|
|
sorted(OpenstackConfigSerializer.fields))
|
|
|
|
def test_openstack_config_put(self):
|
|
resp = self.app.put(
|
|
reverse('OpenstackConfigHandler',
|
|
{'obj_id': self.configs[0].id}),
|
|
expect_errors=True)
|
|
self.assertEqual(resp.status_code, 405)
|
|
|
|
@mock.patch('nailgun.task.task.rpc.cast')
|
|
def test_openstack_config_execute(self, _):
|
|
data = {'cluster_id': self.clusters[0].id}
|
|
resp = self.app.put(
|
|
reverse('OpenstackConfigExecuteHandler'),
|
|
jsonutils.dumps(data), headers=self.default_headers
|
|
)
|
|
self.assertEqual(resp.status_code, 202)
|
|
|
|
@mock.patch('nailgun.task.task.rpc.cast')
|
|
def test_openstack_config_execute_force(self, _):
|
|
# Turn node 2 into provisioned state
|
|
self.env.nodes[2].status = consts.NODE_STATUSES.provisioned
|
|
self.db.flush()
|
|
# Try to update OpenStack configuration for cluster
|
|
data = {'cluster_id': self.clusters[0].id}
|
|
resp = self.app.put(
|
|
reverse('OpenstackConfigExecuteHandler'),
|
|
jsonutils.dumps(data), headers=self.default_headers,
|
|
expect_errors=True)
|
|
# Request shouldn't pass a validation
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertEqual("Nodes '{0}' are not in status 'ready' and "
|
|
"can not be updated directly."
|
|
"".format(self.env.nodes[2].uid),
|
|
resp.json_body['message'])
|
|
|
|
# Try to update OpenStack configuration for cluster with 'force' key
|
|
data = {'cluster_id': self.clusters[0].id,
|
|
'force': True}
|
|
resp = self.app.put(
|
|
reverse('OpenstackConfigExecuteHandler'),
|
|
jsonutils.dumps(data), headers=self.default_headers
|
|
)
|
|
# Update OpenStack configuration executed successfully
|
|
self.assertEqual(resp.status_code, 202)
|
|
|
|
def test_openstack_config_execute_fail_cluster_not_operational(self):
|
|
self.clusters[0].status = consts.CLUSTER_STATUSES.error
|
|
self.db.flush()
|
|
data = {'cluster_id': self.clusters[0].id}
|
|
resp = self.app.put(
|
|
reverse('OpenstackConfigExecuteHandler'),
|
|
jsonutils.dumps(data), headers=self.default_headers,
|
|
expect_errors=True
|
|
)
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertEqual(resp.json_body['message'],
|
|
"Cluster should be in the status 'operational'")
|
|
|
|
def test_openstack_config_execute_fail_deploy_running(self):
|
|
deploy_task_id = self.create_running_deployment_task()
|
|
data = {'cluster_id': self.clusters[0].id}
|
|
resp = self.app.put(
|
|
reverse('OpenstackConfigExecuteHandler'),
|
|
jsonutils.dumps(data), headers=self.default_headers,
|
|
expect_errors=True
|
|
)
|
|
self.check_fail_deploy_running(deploy_task_id, resp)
|
|
|
|
def test_openstack_config_execute_fail_no_ready_nodes(self):
|
|
# Turn node 0 into provisioned state
|
|
self.env.nodes[0].status = consts.NODE_STATUSES.provisioned
|
|
self.env.nodes[1].status = consts.NODE_STATUSES.provisioned
|
|
self.env.nodes[2].status = consts.NODE_STATUSES.provisioned
|
|
self.db.flush()
|
|
|
|
# Try to update configuration for node 0
|
|
data = {'cluster_id': self.clusters[0].id}
|
|
|
|
resp = self.app.put(
|
|
reverse('OpenstackConfigExecuteHandler'),
|
|
jsonutils.dumps(data), headers=self.default_headers,
|
|
expect_errors=True
|
|
)
|
|
# Request shouldn't pass a validation
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertEqual("No nodes in status 'ready'",
|
|
resp.json_body['message'])
|
|
|
|
def test_openstack_config_execute_fail_not_existed_cluster(self):
|
|
# Try to update not existed cluster
|
|
data = {'cluster_id': -1}
|
|
resp = self.app.put(
|
|
reverse('OpenstackConfigExecuteHandler'),
|
|
jsonutils.dumps(data), headers=self.default_headers,
|
|
expect_errors=True
|
|
)
|
|
# Request shouldn't pass a validation
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertEqual("Object 'Cluster' with UID=-1 is not found in DB",
|
|
resp.json_body['message'])
|
|
|
|
def test_openstack_config_delete(self):
|
|
obj_id = self.configs[0].id
|
|
|
|
resp = self.app.delete(
|
|
reverse('OpenstackConfigHandler',
|
|
{'obj_id': obj_id}),
|
|
expect_errors=True)
|
|
self.assertEqual(resp.status_code, 204)
|
|
|
|
resp = self.app.get(
|
|
reverse('OpenstackConfigHandler',
|
|
{'obj_id': obj_id}),
|
|
headers=self.default_headers)
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(resp.json_body['is_active'], False)
|
|
|
|
# Try delete already deleted object
|
|
resp = self.app.delete(
|
|
reverse('OpenstackConfigHandler',
|
|
{'obj_id': obj_id}),
|
|
headers=self.default_headers, expect_errors=True)
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertEqual(
|
|
resp.json_body['message'],
|
|
"Configuration '{0}' has been already disabled.".format(obj_id))
|
|
|
|
def test_openstack_config_delete_fail_deploy_running(self):
|
|
deploy_task_id = self.create_running_deployment_task()
|
|
resp = self.app.delete(
|
|
reverse('OpenstackConfigHandler',
|
|
{'obj_id': self.configs[0].id}),
|
|
expect_errors=True)
|
|
self.check_fail_deploy_running(deploy_task_id, resp)
|
|
|
|
@classmethod
|
|
def _make_filter_url(cls, **kwargs):
|
|
return '{0}?{1}'.format(
|
|
reverse('OpenstackConfigCollectionHandler'),
|
|
urlparse.urlencode(kwargs))
|