From a72d2f68628fdbf12ba98891a507a6c638e259ee Mon Sep 17 00:00:00 2001 From: Artem Roma Date: Tue, 1 Mar 2016 15:38:37 +0200 Subject: [PATCH] Add handler for copying of VIPs New handler triggers copying of VIPs from given original cluster to new one. The reason for separation of this action from copying of network configuration is that accordion to [1] copying of existing VIPs/allocation of new ones will not take effect for new cluster unless it has assigned nodes. Thus in context of cluster upgrade procedure VIP transfer must be done after node reassignment, and as long as nodes are being operated on one by one it would be not efficient to call VIP copying method after each such reassignment. Tests updated accordingly. [1]: https://review.openstack.org/#/c/284841/ Change-Id: I33670e8f2561be6fe18cec75bfc7ecc056ae2f6b Closes-Bug: #1552744 --- cluster_upgrade/extension.py | 2 + cluster_upgrade/handlers.py | 38 ++++++++++++ cluster_upgrade/tests/base.py | 14 +++-- cluster_upgrade/tests/test_handlers.py | 33 +++++++++-- cluster_upgrade/tests/test_upgrade.py | 73 ++++++++++++++++++------ cluster_upgrade/tests/test_validators.py | 34 +++++++++++ cluster_upgrade/upgrade.py | 7 ++- cluster_upgrade/validators.py | 15 +++++ 8 files changed, 188 insertions(+), 28 deletions(-) diff --git a/cluster_upgrade/extension.py b/cluster_upgrade/extension.py index f484fa9..66df106 100644 --- a/cluster_upgrade/extension.py +++ b/cluster_upgrade/extension.py @@ -31,6 +31,8 @@ class ClusterUpgradeExtension(extensions.BaseExtension): 'handler': handlers.ClusterUpgradeCloneHandler}, {'uri': r'/clusters/(?P\d+)/upgrade/assign/?$', 'handler': handlers.NodeReassignHandler}, + {'uri': r'/clusters/(?P\d+)/upgrade/vips/?$', + 'handler': handlers.CopyVIPsHandler}, ] @classmethod diff --git a/cluster_upgrade/handlers.py b/cluster_upgrade/handlers.py index 21f0bb0..4f61f2e 100644 --- a/cluster_upgrade/handlers.py +++ b/cluster_upgrade/handlers.py @@ -101,3 +101,41 @@ class NodeReassignHandler(base.BaseHandler): if reprovision: self.handle_task(cluster_id, [node.node]) + + +class CopyVIPsHandler(base.BaseHandler): + single = objects.Cluster + validator = validators.CopyVIPsValidator + + @base.content + def POST(self, cluster_id): + """Copy VIPs from original cluster to new one + + Original cluster object is obtained from existing relation between + clusters that is created on cluster clone operation + + :param cluster_id: id of cluster that VIPs must be copied to + + :http: * 200 (OK) + * 400 (validation failed) + * 404 (seed cluster is not found) + """ + from .objects import relations + + cluster = self.get_object_or_404(self.single, cluster_id) + relation = relations.UpgradeRelationObject.get_cluster_relation( + cluster.id) + + self.checked_data(cluster=cluster, relation=relation) + + # get original cluster object and create adapter with it + orig_cluster_adapter = \ + adapters.NailgunClusterAdapter( + adapters.NailgunClusterAdapter.get_by_uid( + relation.orig_cluster_id) + ) + + seed_cluster_adapter = adapters.NailgunClusterAdapter(cluster) + + upgrade.UpgradeHelper.copy_vips(orig_cluster_adapter, + seed_cluster_adapter) diff --git a/cluster_upgrade/tests/base.py b/cluster_upgrade/tests/base.py index 8d7ce10..497656f 100644 --- a/cluster_upgrade/tests/base.py +++ b/cluster_upgrade/tests/base.py @@ -37,14 +37,18 @@ class BaseCloneClusterTest(nailgun_test_base.BaseIntegrationTest): version="liberty-9.0", ) - self.src_cluster_db = self.env.create_cluster( - api=False, - release_id=self.src_release.id, - net_provider=consts.CLUSTER_NET_PROVIDERS.neutron, - net_l23_provider=consts.NEUTRON_L23_PROVIDERS.ovs, + self.src_cluster_db = self.env.create( + cluster_kwargs={ + 'api': False, + 'release_id': self.src_release.id, + 'net_provider': consts.CLUSTER_NET_PROVIDERS.neutron, + 'net_l23_provider': consts.NEUTRON_L23_PROVIDERS.ovs, + }, + nodes_kwargs=[{'roles': ['controller']}] ) self.src_cluster = adapters.NailgunClusterAdapter( self.src_cluster_db) + self.data = { "name": "cluster-clone-{0}".format(self.src_cluster.id), "release_id": self.dst_release.id, diff --git a/cluster_upgrade/tests/test_handlers.py b/cluster_upgrade/tests/test_handlers.py index 575f4d3..127172d 100644 --- a/cluster_upgrade/tests/test_handlers.py +++ b/cluster_upgrade/tests/test_handlers.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from mock import patch +import mock from oslo_serialization import jsonutils @@ -75,7 +75,7 @@ class TestClusterUpgradeCloneHandler(tests_base.BaseCloneClusterTest): class TestNodeReassignHandler(base.BaseIntegrationTest): - @patch('nailgun.task.task.rpc.cast') + @mock.patch('nailgun.task.task.rpc.cast') def test_node_reassign_handler(self, mcast): self.env.create( cluster_kwargs={'api': False}, @@ -97,7 +97,7 @@ class TestNodeReassignHandler(base.BaseIntegrationTest): provisioned_uids = [int(n['uid']) for n in nodes] self.assertEqual([node_id, ], provisioned_uids) - @patch('nailgun.task.task.rpc.cast') + @mock.patch('nailgun.task.task.rpc.cast') def test_node_reassign_handler_with_roles(self, mcast): cluster = self.env.create( cluster_kwargs={'api': False}, @@ -122,7 +122,7 @@ class TestNodeReassignHandler(base.BaseIntegrationTest): self.assertEqual(node.pending_roles, ['compute']) self.assertTrue(mcast.called) - @patch('nailgun.task.task.rpc.cast') + @mock.patch('nailgun.task.task.rpc.cast') def test_node_reassign_handler_without_reprovisioning(self, mcast): cluster = self.env.create( cluster_kwargs={'api': False}, @@ -229,3 +229,28 @@ class TestNodeReassignHandler(base.BaseIntegrationTest): headers=self.default_headers, expect_errors=True) self.assertEqual(400, resp.status_code) + + +class TestCopyVipsHandler(base.BaseIntegrationTest): + + def test_copy_vips_called(self): + from ..objects import relations + + orig_cluster = self.env.create_cluster(api=False) + new_cluster = self.env.create_cluster(api=False) + + relations.UpgradeRelationObject.create_relation( + orig_cluster.id, new_cluster.id) + + with mock.patch('nailgun.extensions.cluster_upgrade.handlers' + '.upgrade.UpgradeHelper.copy_vips') as copy_vips_mc: + resp = self.app.post( + reverse( + 'CopyVIPsHandler', + kwargs={'cluster_id': new_cluster.id} + ), + headers=self.default_headers, + ) + + self.assertEqual(resp.status_code, 200) + self.assertTrue(copy_vips_mc.called) diff --git a/cluster_upgrade/tests/test_upgrade.py b/cluster_upgrade/tests/test_upgrade.py index 723f0ee..dac04b5 100644 --- a/cluster_upgrade/tests/test_upgrade.py +++ b/cluster_upgrade/tests/test_upgrade.py @@ -17,13 +17,31 @@ import copy import six +from nailgun import consts from nailgun.objects.serializers import network_configuration from . import base as base_tests +from ..objects import adapters from ..objects import relations class TestUpgradeHelperCloneCluster(base_tests.BaseCloneClusterTest): + + def setUp(self): + super(TestUpgradeHelperCloneCluster, self).setUp() + + self.orig_net_manager = self.src_cluster.get_network_manager() + + self.serialize_nets = network_configuration.\ + NeutronNetworkConfigurationSerializer.\ + serialize_for_cluster + + self.public_net_data = { + "cidr": "192.168.42.0/24", + "gateway": "192.168.42.1", + "ip_ranges": [["192.168.42.5", "192.168.42.11"]], + } + def test_create_cluster_clone(self): new_cluster = self.helper.create_cluster_clone(self.src_cluster, self.data) @@ -60,28 +78,48 @@ class TestUpgradeHelperCloneCluster(base_tests.BaseCloneClusterTest): self.assertEqual(editable_attrs[section][key]["value"], value["value"]) + def update_public_net_params(self, networks): + pub_net = self._get_pub_net(networks) + pub_net.update(self.public_net_data) + self.orig_net_manager.update(networks) + + def _get_pub_net(self, networks): + return next(net for net in networks['networks'] if + net['name'] == consts.NETWORKS.public) + def test_copy_network_config(self): new_cluster = self.helper.create_cluster_clone(self.src_cluster, self.data) - orig_net_manager = self.src_cluster.get_network_manager() - serialize_nets = network_configuration.\ - NeutronNetworkConfigurationSerializer.\ - serialize_for_cluster - - # Do some unordinary changes - nets = serialize_nets(self.src_cluster.cluster) - nets["networks"][0].update({ - "cidr": "172.16.42.0/24", - "gateway": "172.16.42.1", - "ip_ranges": [["172.16.42.2", "172.16.42.126"]], - }) - orig_net_manager.update(nets) - orig_net_manager.assign_vips_for_net_groups() + # Do some unordinary changes to public network + nets = self.serialize_nets(self.src_cluster.cluster) + self.update_public_net_params(nets) self.helper.copy_network_config(self.src_cluster, new_cluster) - orig_nets = serialize_nets(self.src_cluster_db) - new_nets = serialize_nets(new_cluster.cluster) + new_nets = self.serialize_nets(new_cluster.cluster) + + public_net = self._get_pub_net(new_nets) + + self.assertEqual(public_net['cidr'], self.public_net_data['cidr']) + self.assertEqual(public_net['gateway'], + self.public_net_data['gateway']) + self.assertEqual(public_net['ip_ranges'], + self.public_net_data['ip_ranges']) + + def test_copy_vips(self): + new_cluster = self.helper.clone_cluster(self.src_cluster, self.data) + + # we have to move node to new cluster before VIP assignment + # because there is no point in the operation for a cluster + # w/o nodes + node = adapters.NailgunNodeAdapter(self.src_cluster.cluster.nodes[0]) + self.helper.assign_node_to_cluster(node, new_cluster, node.roles, []) + + self.helper.copy_vips(self.src_cluster, new_cluster) + + orig_nets = self.serialize_nets(self.src_cluster.cluster) + new_nets = self.serialize_nets(new_cluster.cluster) + self.assertEqual(orig_nets["management_vip"], new_nets["management_vip"]) self.assertEqual(orig_nets["management_vrouter_vip"], @@ -92,8 +130,7 @@ class TestUpgradeHelperCloneCluster(base_tests.BaseCloneClusterTest): new_nets["public_vrouter_vip"]) def test_clone_cluster(self): - orig_net_manager = self.src_cluster.get_network_manager() - orig_net_manager.assign_vips_for_net_groups() + self.orig_net_manager.assign_vips_for_net_groups() new_cluster = self.helper.clone_cluster(self.src_cluster, self.data) relation = relations.UpgradeRelationObject.get_cluster_relation( self.src_cluster.id) diff --git a/cluster_upgrade/tests/test_validators.py b/cluster_upgrade/tests/test_validators.py index 9902dca..950ef21 100644 --- a/cluster_upgrade/tests/test_validators.py +++ b/cluster_upgrade/tests/test_validators.py @@ -190,3 +190,37 @@ class TestNodeReassignNoReinstallValidator(tests_base.BaseCloneClusterTest): msg = '^Role "controller" in conflict with role compute$' with self.assertRaisesRegexp(errors.InvalidData, msg): self.validator.validate(data, self.dst_cluster) + + +class TestCopyVIPsValidator(base.BaseTestCase): + validator = validators.CopyVIPsValidator + + def test_non_existing_relation_fail(self): + with self.assertRaises(errors.InvalidData) as cm: + self.validator.validate(data=None, cluster=None, relation=None) + + self.assertEqual( + cm.exception.message, + "Relation for given cluster does not exist" + ) + + def test_cluster_is_not_seed(self): + cluster = self.env.create_cluster(api=False) + seed_cluster = self.env.create_cluster(api=False) + + relations.UpgradeRelationObject.create_relation( + orig_cluster_id=cluster.id, + seed_cluster_id=cluster.id, + ) + + relation = relations.UpgradeRelationObject.get_cluster_relation( + cluster.id) + + with self.assertRaises(errors.InvalidData) as cm: + self.validator.validate(data=None, cluster=seed_cluster, + relation=relation) + + self.assertEqual( + cm.exception.message, + "Given cluster is not seed cluster" + ) diff --git a/cluster_upgrade/upgrade.py b/cluster_upgrade/upgrade.py index 6278b74..b84e366 100644 --- a/cluster_upgrade/upgrade.py +++ b/cluster_upgrade/upgrade.py @@ -150,10 +150,15 @@ class UpgradeHelper(object): nets_serializer.serialize_for_cluster(orig_cluster.cluster), nets_serializer.serialize_for_cluster(new_cluster.cluster)) - orig_net_manager = orig_cluster.get_network_manager() new_net_manager = new_cluster.get_network_manager() new_net_manager.update(nets) + + @classmethod + def copy_vips(cls, orig_cluster, new_cluster): + orig_net_manager = orig_cluster.get_network_manager() + new_net_manager = new_cluster.get_network_manager() + vips = orig_net_manager.get_assigned_vips() for ng_name in vips: if ng_name not in (consts.NETWORKS.public, diff --git a/cluster_upgrade/validators.py b/cluster_upgrade/validators.py index b29dee8..3874147 100644 --- a/cluster_upgrade/validators.py +++ b/cluster_upgrade/validators.py @@ -147,3 +147,18 @@ class NodeReassignValidator(assignment.NodeAssignmentValidator): raise errors.InvalidData("Node {0} is already assigned to cluster" " {1}".format(node.id, cluster.id), log_message=True) + + +class CopyVIPsValidator(base.BasicValidator): + + @classmethod + def validate(cls, data, cluster, relation): + if relation is None: + raise errors.InvalidData( + "Relation for given cluster does not exist" + ) + + if cluster.id != relation.seed_cluster_id: + raise errors.InvalidData("Given cluster is not seed cluster") + + return data