Allow to update roles for deployed nodes.

Closes-bug: #1641189
Change-Id: Ibe55b6011922f6a0c8cabfcaa1b4ced3ccd328be
This commit is contained in:
Evgeny L 2016-10-05 11:46:31 +00:00 committed by Evgeniy L
parent ef98ad14e3
commit d6d6d1abdd
4 changed files with 99 additions and 54 deletions

View File

@ -41,13 +41,13 @@ class NodeAssignmentHandler(BaseHandler):
* 400 (invalid nodes data specified)
* 404 (cluster/node not found in db)
"""
self.get_object_or_404(
cluster = self.get_object_or_404(
objects.Cluster,
cluster_id
)
data = self.checked_data(
self.validator.validate_collection_update,
cluster_id=cluster_id
cluster_id=cluster.id
)
nodes = self.get_objects_list_or_404(
objects.NodeCollection,
@ -55,9 +55,15 @@ class NodeAssignmentHandler(BaseHandler):
)
for node in nodes:
objects.Node.update(node, {"cluster_id": cluster_id,
"pending_roles": data[node.id],
"pending_addition": True})
update = {"cluster_id": cluster.id, "pending_roles": data[node.id]}
# NOTE(el): don't update pending_addition flag
# if node is already assigned to the cluster
# otherwise it would create problems for roles
# update
if not node.cluster:
update["pending_addition"] = True
objects.Node.update(node, update)
# fuel-client expects valid json for all put and post request
raise self.http(200, None)

View File

@ -12,7 +12,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from operator import attrgetter
import sqlalchemy as sa
from nailgun.api.v1.validators.base import BasicValidator
@ -30,9 +29,6 @@ from nailgun.utils.restrictions import RestrictionBase
class AssignmentValidator(BasicValidator):
predicate = None
done_error_msg_template = None
@staticmethod
def check_all_nodes(nodes, node_ids):
not_found_node_ids = set(node_ids) - set(n.id for n in nodes)
@ -44,24 +40,15 @@ class AssignmentValidator(BasicValidator):
), log_message=True
)
@classmethod
def check_if_already_done(cls, nodes):
already_done_nodes = [n.id for n in nodes if cls.predicate(n)]
already_done_nodes.sort()
if already_done_nodes:
raise errors.InvalidData(
cls.done_error_msg_template
.format(", ".join(map(str, already_done_nodes))),
log_message=True
)
@classmethod
def check_unique_hostnames(cls, nodes, cluster_id):
hostnames = [node.hostname for node in nodes]
node_ids = [node.id for node in nodes]
conflicting_hostnames = [
x[0] for x in
db.query(
Node.hostname).filter(sa.and_(
~Node.id.in_(node_ids),
Node.hostname.in_(hostnames),
Node.cluster_id == cluster_id,
)
@ -76,11 +63,6 @@ class AssignmentValidator(BasicValidator):
class NodeAssignmentValidator(AssignmentValidator):
predicate = attrgetter('cluster')
done_error_msg_template = "Nodes with ids {0} already assigned to " \
"environments. Nodes must be unassigned " \
"before they can be assigned again."
@classmethod
def validate_collection_update(cls, data, cluster_id=None):
data = cls.validate_json(data)
@ -89,7 +71,6 @@ class NodeAssignmentValidator(AssignmentValidator):
received_node_ids = dict_data.keys()
nodes = db.query(Node).filter(Node.id.in_(received_node_ids))
cls.check_all_nodes(nodes, received_node_ids)
cls.check_if_already_done(nodes)
cluster = objects.Cluster.get_by_uid(
cluster_id, fail_if_not_found=True
)
@ -160,13 +141,6 @@ class NodeAssignmentValidator(AssignmentValidator):
class NodeUnassignmentValidator(AssignmentValidator):
done_error_msg_template = "Can't unassign nodes with ids {0} " \
"if they not assigned."
@staticmethod
def predicate(node):
return not node.cluster or node.pending_deletion
@classmethod
def validate_collection_update(cls, data, cluster_id=None):
list_data = cls.validate_json(data)
@ -190,5 +164,4 @@ class NodeUnassignmentValidator(AssignmentValidator):
), cluster_id), log_message=True
)
cls.check_all_nodes(nodes, node_ids_set)
cls.check_if_already_done(nodes)
return nodes

View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# Copyright 2016 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
from oslo_serialization import jsonutils
from nailgun import consts
from nailgun import rpc
from nailgun.test.base import BaseIntegrationTest
from nailgun.test.base import fake_tasks
from nailgun.utils import reverse
class TestNodeAssignment(BaseIntegrationTest):
def setUp(self):
super(TestNodeAssignment, self).setUp()
self.env.create(
api=False,
nodes_kwargs=[
{"name": "First",
"pending_roles": ["controller"],
"pending_addition": True},
{"name": "Second",
"pending_roles": ["compute"],
"pending_addition": True}
]
)
self.cluster = self.env.clusters[-1]
@fake_tasks(fake_rpc=False, mock_rpc=False)
@mock.patch('nailgun.rpc.cast')
def test_reassign_role_to_deployed_node(self, _):
# First deploy
task = self.env.launch_deployment(cluster_id=self.cluster.id)
for t in task.subtasks:
rpc.receiver.NailgunReceiver().deploy_resp(
task_uuid=t.uuid,
status=consts.TASK_STATUSES.ready,
progress=100,
nodes=[{'uid': n.uid, 'status': consts.NODE_STATUSES.ready}
for n in self.cluster.nodes])
# Update roles
first_node = filter(
lambda n: n.name == 'First', self.cluster.nodes)[0]
second_node = filter(
lambda n: n.name == 'Second', self.cluster.nodes)[0]
assignment_data = [{
"id": first_node.id,
"roles": ['controller', 'cinder']}]
self.app.post(
reverse('NodeAssignmentHandler',
kwargs={'cluster_id': self.cluster.id}),
jsonutils.dumps(assignment_data),
headers=self.default_headers)
unassignment_data = [{"id": second_node.id}]
self.app.post(
reverse('NodeUnassignmentHandler',
kwargs={'cluster_id': self.cluster.id}),
jsonutils.dumps(unassignment_data),
headers=self.default_headers)
# Second deploy
# In case of problems will raise an error
self.app.put(reverse(
'DeploySelectedNodes', kwargs={'cluster_id': self.cluster.id}),
'{}', headers=self.default_headers)

View File

@ -62,9 +62,9 @@ class TestAssignmentHandlers(BaseIntegrationTest):
node.pending_roles,
assignment_data[0]["roles"]
)
# NOTE(el): Role can be reassigned after initial assigment
resp = self._assign_roles(assignment_data, True)
self.assertEqual(400, resp.status_code)
self.assertEqual(200, resp.status_code)
def test_unassignment(self):
cluster = self.env.create(
@ -145,24 +145,6 @@ class TestAssignmentHandlers(BaseIntegrationTest):
self.assertEqual(resp.status_code, 200)
self.assertEqual(node.pending_deletion, True)
def test_assigment_with_already_assigned_node(self):
cluster = self.env.create_cluster(api=False)
node = self.env.create_node(cluster_id=cluster.id)
resp = self.app.post(
reverse(
'NodeAssignmentHandler',
kwargs={'cluster_id': cluster.id}
),
jsonutils.dumps([{'id': node.id, 'roles': ['controller']}]),
headers=self.default_headers,
expect_errors=True
)
msg = 'Nodes with ids {} already assigned ' \
'to environments. Nodes must be unassigned ' \
'before they can be assigned again.'.format(node.id)
self.assertEquals(400, resp.status_code)
self.assertEquals(msg, resp.json_body["message"])
def test_assigment_with_invalid_cluster(self):
node = self.env.create_node(api=False)