Add check for nodes provisioning to BaseDeploySelectedNodes handler
In some cases (e.g. user is using CLI or API interface for deployment of standalone controller node for cluster in ha mode) not all nodes intended for deployment are yet provisioned. Separate validator added for check such situation. Change-Id: I7405ba54d90f6bc986befb5d035b213e4c0f6a73 Closes-Bug: #1415968
This commit is contained in:
parent
9707a82356
commit
6e41156536
|
@ -23,6 +23,7 @@ from nailgun.api.v1.handlers.base import BaseHandler
|
|||
from nailgun.api.v1.handlers.base import content
|
||||
from nailgun.api.v1.validators.cluster import ProvisionSelectedNodesValidator
|
||||
from nailgun.api.v1.validators.graph import GraphVisualizationValidator
|
||||
from nailgun.api.v1.validators.node import DeploySelectedNodesValidator
|
||||
from nailgun.api.v1.validators.node import NodeDeploymentValidator
|
||||
from nailgun.api.v1.validators.node import NodesFilterValidator
|
||||
|
||||
|
@ -255,6 +256,7 @@ class ProvisionSelectedNodes(SelectedNodesBase):
|
|||
|
||||
class BaseDeploySelectedNodes(SelectedNodesBase):
|
||||
|
||||
validator = DeploySelectedNodesValidator
|
||||
task_manager = DeploymentTaskManager
|
||||
|
||||
def get_default_nodes(self, cluster):
|
||||
|
@ -264,7 +266,14 @@ class BaseDeploySelectedNodes(SelectedNodesBase):
|
|||
nodes_to_deploy = super(
|
||||
BaseDeploySelectedNodes, self).get_nodes(cluster)
|
||||
if cluster.is_ha_mode:
|
||||
return TaskHelper.nodes_to_deploy_ha(cluster, nodes_to_deploy)
|
||||
nodes_to_deploy = TaskHelper.nodes_to_deploy_ha(
|
||||
cluster,
|
||||
nodes_to_deploy
|
||||
)
|
||||
|
||||
self.checked_data(self.validator.validate_nodes_to_deploy,
|
||||
nodes=nodes_to_deploy, cluster_id=cluster.id)
|
||||
|
||||
return nodes_to_deploy
|
||||
|
||||
|
||||
|
|
|
@ -321,8 +321,63 @@ class NodesFilterValidator(BasicValidator):
|
|||
return node_ids
|
||||
|
||||
|
||||
class ProvisionSelectedNodesValidator(NodesFilterValidator):
|
||||
|
||||
@classmethod
|
||||
def validate_provision(cls, data, cluster):
|
||||
"""Check whether provision allowed or not for a given cluster
|
||||
|
||||
:param data: raw json data, usually web.data()
|
||||
:param cluster: cluster instance
|
||||
:returns: loaded json or empty array
|
||||
"""
|
||||
if cluster.release.state == consts.RELEASE_STATES.unavailable:
|
||||
raise errors.UnavailableRelease(
|
||||
"Release '{0} {1}' is unavailable!".format(
|
||||
cluster.release.name, cluster.release.version))
|
||||
|
||||
|
||||
class DeploySelectedNodesValidator(NodesFilterValidator):
|
||||
|
||||
@classmethod
|
||||
def validate_nodes_to_deploy(cls, data, nodes, cluster_id):
|
||||
"""Check whether nodes that scheduled for deployment are
|
||||
in proper state
|
||||
|
||||
:param data: raw json data, usually web.data(). Is not used here
|
||||
and is needed for mantaining consistency of data validating logic
|
||||
:param nodes: list of node objects state of which to be checked
|
||||
:param cluster_id: id of the cluster for which operation is performed
|
||||
"""
|
||||
|
||||
# in some cases (e.g. user tries to deploy single controller node
|
||||
# via CLI or API for ha cluster) there may be situation when not
|
||||
# all nodes scheduled for deployment are provisioned, hence
|
||||
# here we check for such situation
|
||||
not_provisioned_nodes_ids = [
|
||||
n.id for n in nodes
|
||||
if any(
|
||||
[n.pending_addition,
|
||||
n.needs_reprovision,
|
||||
n.needs_redeletion,
|
||||
n.status == consts.NODE_STATUSES.provisioning]
|
||||
)
|
||||
]
|
||||
|
||||
if not_provisioned_nodes_ids:
|
||||
err_msg = ("Deployment operation cannot be started as nodes "
|
||||
"with uids {0} are not provisioned yet for "
|
||||
"cluster with id {1}."
|
||||
.format(not_provisioned_nodes_ids, cluster_id))
|
||||
|
||||
raise errors.InvalidData(
|
||||
err_msg,
|
||||
log_message=True
|
||||
)
|
||||
|
||||
|
||||
class NodeDeploymentValidator(TaskDeploymentValidator,
|
||||
NodesFilterValidator):
|
||||
DeploySelectedNodesValidator):
|
||||
|
||||
@classmethod
|
||||
def validate_deployment(cls, data, cluster):
|
||||
|
@ -339,19 +394,3 @@ class NodeDeploymentValidator(TaskDeploymentValidator,
|
|||
raise errors.InvalidData('Tasks list must be specified.')
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ProvisionSelectedNodesValidator(NodesFilterValidator):
|
||||
|
||||
@classmethod
|
||||
def validate_provision(cls, data, cluster):
|
||||
"""Check whether provision allowed or not for a given cluster
|
||||
|
||||
:param data: raw json data, usually web.data()
|
||||
:param cluster: cluster instance
|
||||
:returns: loaded json or empty array
|
||||
"""
|
||||
if cluster.release.state == consts.RELEASE_STATES.unavailable:
|
||||
raise errors.UnavailableRelease(
|
||||
"Release '{0} {1}' is unavailable!".format(
|
||||
cluster.release.name, cluster.release.version))
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
from mock import patch
|
||||
from oslo.serialization import jsonutils
|
||||
|
||||
import nailgun
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun import objects
|
||||
|
||||
|
@ -153,27 +151,54 @@ class BaseSelectedNodesTest(BaseIntegrationTest):
|
|||
{'roles': ['cinder'], 'pending_addition': True}])
|
||||
|
||||
self.cluster = self.env.clusters[0]
|
||||
self.node_uids = [n.uid for n in self.cluster.nodes][:3]
|
||||
self.nodes = [n for n in self.cluster.nodes][:3]
|
||||
self.node_uids = [n.uid for n in self.nodes]
|
||||
|
||||
def send_put(self, url, data=None):
|
||||
return self.app.put(
|
||||
url, jsonutils.dumps(data),
|
||||
headers=self.default_headers, expect_errors=True)
|
||||
|
||||
def make_action_url(self, handler_name, node_uids):
|
||||
return reverse(
|
||||
handler_name,
|
||||
kwargs={'cluster_id': self.cluster.id}) + \
|
||||
make_orchestrator_uri(node_uids)
|
||||
|
||||
def emulate_nodes_provisioning(self, nodes):
|
||||
for node in nodes:
|
||||
node.status = consts.NODE_STATUSES.provisioned
|
||||
node.pending_addition = False
|
||||
|
||||
self.db.add_all(nodes)
|
||||
self.db.flush()
|
||||
|
||||
def check_deployment_call_made(self, nodes_uids, mcast):
|
||||
args, kwargs = mcast.call_args
|
||||
deployed_uids = [n['uid'] for n in args[1]['args']['deployment_info']]
|
||||
self.assertEqual(len(nodes_uids), len(deployed_uids))
|
||||
self.assertItemsEqual(nodes_uids, deployed_uids)
|
||||
|
||||
def check_resp_declined(self, resp):
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertRegexpMatches(
|
||||
resp.body,
|
||||
"Deployment operation cannot be started .*"
|
||||
)
|
||||
|
||||
|
||||
class TestSelectedNodesAction(BaseSelectedNodesTest):
|
||||
|
||||
@fake_tasks(fake_rpc=False, mock_rpc=False)
|
||||
@patch('nailgun.rpc.cast')
|
||||
def test_start_provisioning_on_selected_nodes(self, mock_rpc):
|
||||
action_url = reverse(
|
||||
'ProvisionSelectedNodes',
|
||||
kwargs={'cluster_id': self.cluster.id}) + \
|
||||
make_orchestrator_uri(self.node_uids)
|
||||
|
||||
@patch('nailgun.task.task.rpc.cast')
|
||||
def test_start_provisioning_on_selected_nodes(self, mcast):
|
||||
action_url = self.make_action_url(
|
||||
"ProvisionSelectedNodes",
|
||||
self.node_uids
|
||||
)
|
||||
self.send_put(action_url)
|
||||
|
||||
args, kwargs = nailgun.task.manager.rpc.cast.call_args
|
||||
args, kwargs = mcast.call_args
|
||||
provisioned_uids = [
|
||||
n['uid'] for n in args[1]['args']['provisioning_info']['nodes']]
|
||||
|
||||
|
@ -181,10 +206,11 @@ class TestSelectedNodesAction(BaseSelectedNodesTest):
|
|||
self.assertItemsEqual(self.node_uids, provisioned_uids)
|
||||
|
||||
def test_start_provisioning_is_unavailable(self):
|
||||
action_url = reverse(
|
||||
'ProvisionSelectedNodes',
|
||||
kwargs={'cluster_id': self.cluster.id}) + \
|
||||
make_orchestrator_uri(self.node_uids)
|
||||
action_url = self.make_action_url(
|
||||
"ProvisionSelectedNodes",
|
||||
self.node_uids
|
||||
)
|
||||
|
||||
self.env.releases[0].state = consts.RELEASE_STATES.unavailable
|
||||
|
||||
resp = self.send_put(action_url)
|
||||
|
@ -193,23 +219,56 @@ class TestSelectedNodesAction(BaseSelectedNodesTest):
|
|||
self.assertRegexpMatches(resp.body, 'Release .* is unavailable')
|
||||
|
||||
@fake_tasks(fake_rpc=False, mock_rpc=False)
|
||||
@patch('nailgun.rpc.cast')
|
||||
def test_start_deployment_on_selected_nodes(self, mock_rpc):
|
||||
@patch('nailgun.task.task.rpc.cast')
|
||||
def test_start_deployment_on_selected_nodes(self, mcast):
|
||||
controller_nodes = [
|
||||
n for n in self.cluster.nodes
|
||||
if "controller" in n.roles
|
||||
]
|
||||
|
||||
self.emulate_nodes_provisioning(controller_nodes)
|
||||
|
||||
nodes_uids = [n.uid for n in controller_nodes]
|
||||
|
||||
controller_to_deploy = nodes_uids[0]
|
||||
|
||||
# if cluster is ha, then DeploySelectedNodes must call
|
||||
# TaskHelper.nodes_to_deploy_ha(cluster, nodes) and it must
|
||||
# append third controller to the list of nodes which are to deploy
|
||||
node_uids = [n.uid for n in self.cluster.nodes][2:3]
|
||||
action_url = reverse(
|
||||
'DeploySelectedNodes',
|
||||
kwargs={'cluster_id': self.cluster.id}) + \
|
||||
make_orchestrator_uri(node_uids)
|
||||
deploy_action_url = self.make_action_url(
|
||||
"DeploySelectedNodes",
|
||||
[controller_to_deploy]
|
||||
)
|
||||
self.send_put(deploy_action_url)
|
||||
|
||||
self.send_put(action_url)
|
||||
self.check_deployment_call_made(nodes_uids, mcast)
|
||||
|
||||
args, kwargs = nailgun.task.manager.rpc.cast.call_args
|
||||
deployed_uids = [n['uid'] for n in args[1]['args']['deployment_info']]
|
||||
self.assertEqual(3, len(deployed_uids))
|
||||
self.assertItemsEqual(self.node_uids, deployed_uids)
|
||||
@fake_tasks(fake_rpc=False, mock_rpc=False)
|
||||
@patch('nailgun.task.task.rpc.cast')
|
||||
def test_deployment_of_node_is_forbidden(self, mcast):
|
||||
# cluster is in ha mode so for the sanity of the check
|
||||
# lets operate on non-controller node
|
||||
node_to_deploy = [
|
||||
n for n in self.cluster.nodes if
|
||||
not
|
||||
set(["primary-controller", "controller"])
|
||||
&
|
||||
set(n.roles)
|
||||
].pop()
|
||||
|
||||
deploy_action_url = self.make_action_url(
|
||||
"DeploySelectedNodes",
|
||||
[node_to_deploy.uid]
|
||||
)
|
||||
|
||||
resp = self.send_put(deploy_action_url)
|
||||
self.check_resp_declined(resp)
|
||||
|
||||
self.emulate_nodes_provisioning([node_to_deploy])
|
||||
|
||||
self.send_put(deploy_action_url)
|
||||
|
||||
self.check_deployment_call_made([node_to_deploy.uid], mcast)
|
||||
|
||||
|
||||
class TestDeploymentHandlerSkipTasks(BaseSelectedNodesTest):
|
||||
|
@ -221,31 +280,42 @@ class TestDeploymentHandlerSkipTasks(BaseSelectedNodesTest):
|
|||
|
||||
@patch('nailgun.task.task.rpc.cast')
|
||||
def test_use_only_certain_tasks(self, mcast):
|
||||
action_url = reverse(
|
||||
'DeploySelectedNodesWithTasks',
|
||||
kwargs={'cluster_id': self.cluster.id}) + \
|
||||
make_orchestrator_uri(self.node_uids)
|
||||
self.emulate_nodes_provisioning(self.nodes)
|
||||
|
||||
action_url = self.make_action_url(
|
||||
"DeploySelectedNodesWithTasks",
|
||||
self.node_uids
|
||||
)
|
||||
out = self.send_put(action_url, self.tasks)
|
||||
self.assertEqual(out.status_code, 202)
|
||||
|
||||
args, kwargs = mcast.call_args
|
||||
deployed_uids = [n['uid'] for n in args[1]['args']['deployment_info']]
|
||||
deployment_data = args[1]['args']['deployment_info'][0]
|
||||
self.assertEqual(deployed_uids, self.node_uids)
|
||||
self.assertEqual(len(deployment_data['tasks']), 1)
|
||||
|
||||
def test_deployment_is_forbidden(self):
|
||||
action_url = self.make_action_url(
|
||||
"DeploySelectedNodesWithTasks",
|
||||
self.node_uids
|
||||
)
|
||||
resp = self.send_put(action_url, self.tasks)
|
||||
self.check_resp_declined(resp)
|
||||
|
||||
def test_error_raised_on_non_existent_tasks(self):
|
||||
action_url = reverse(
|
||||
'DeploySelectedNodesWithTasks',
|
||||
kwargs={'cluster_id': self.cluster.id}) + \
|
||||
make_orchestrator_uri(self.node_uids)
|
||||
action_url = self.make_action_url(
|
||||
"DeploySelectedNodesWithTasks",
|
||||
self.node_uids
|
||||
)
|
||||
out = self.send_put(action_url, self.non_existent)
|
||||
self.assertEqual(out.status_code, 400)
|
||||
|
||||
def test_error_on_empty_list_tasks(self):
|
||||
action_url = reverse(
|
||||
'DeploySelectedNodesWithTasks',
|
||||
kwargs={'cluster_id': self.cluster.id}) + \
|
||||
make_orchestrator_uri(self.node_uids)
|
||||
action_url = self.make_action_url(
|
||||
"DeploySelectedNodesWithTasks",
|
||||
self.node_uids
|
||||
)
|
||||
out = self.send_put(action_url, [])
|
||||
self.assertEqual(out.status_code, 400)
|
||||
|
||||
|
@ -255,11 +325,15 @@ class TestDeployMethodVersioning(BaseSelectedNodesTest):
|
|||
def assert_deployment_method(self, version, method, mcast):
|
||||
self.cluster.release.version = version
|
||||
self.db.flush()
|
||||
action_url = reverse(
|
||||
'DeploySelectedNodes',
|
||||
kwargs={'cluster_id': self.cluster.id}) + \
|
||||
make_orchestrator_uri(self.node_uids)
|
||||
|
||||
self.emulate_nodes_provisioning(self.nodes)
|
||||
|
||||
action_url = self.make_action_url(
|
||||
"DeploySelectedNodes",
|
||||
self.node_uids
|
||||
)
|
||||
self.send_put(action_url)
|
||||
|
||||
deployment_method = mcast.call_args_list[0][0][1]['method']
|
||||
self.assertEqual(deployment_method, method)
|
||||
|
||||
|
|
Loading…
Reference in New Issue