From 7076053627ec1c621c9f1cc5596a5b15fe885510 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Mon, 23 Mar 2020 17:49:22 +0000 Subject: [PATCH] Update rabbitmq tests for version 3.8.2 on focal Ubuntu focal ships with rabbitmq server 3.8.2 which has changed the text output format for the cli commands that the tests rely on. Fortunately, 3.8.2 also adds a --formatter=json option. This patch takes advantage of that. --- .../charm_tests/rabbitmq_server/utils.py | 118 +++++++++++++++++- 1 file changed, 114 insertions(+), 4 deletions(-) diff --git a/zaza/openstack/charm_tests/rabbitmq_server/utils.py b/zaza/openstack/charm_tests/rabbitmq_server/utils.py index dba2d92..9826add 100644 --- a/zaza/openstack/charm_tests/rabbitmq_server/utils.py +++ b/zaza/openstack/charm_tests/rabbitmq_server/utils.py @@ -120,6 +120,26 @@ def delete_user(units, username="testuser1"): output = zaza.model.run_on_unit(units[0].entity_id, cmd_user_del) +def is_rabbitmq_version_ge_382(unit): + """Test is the rabbitmq version on the :param:`unit` is 3.8.2+. + + Returns True if the rabbitmq_server version installed on the :param:`unit` + is >= 3.8.2 + + :param unit: the unit to test + :type unit: :class:`juju.model.ModelEntity` + :returns: True if the server is 3.8.2 or later + :rtype: Boolean + """ + cmd = 'rabbitmqctl version' + output = zaza.model.run_on_unit(unit.entity_id, cmd)['Stdout'].strip() + logging.debug('{} rabbitmq version:{}'.format(unit.entity_id, output)) + try: + return tuple(map(int, output.split('.')[:3])) >= (3, 8, 2) + except Exception: + return False + + def get_cluster_status(unit): """Get RabbitMQ cluster status output. @@ -138,10 +158,34 @@ def get_cluster_status(unit): def get_cluster_running_nodes(unit): """Get a list of RabbitMQ cluster's running nodes. + Return a list of the running rabbitmq cluster nodes from the specified + unit. + + NOTE: this calls one of two functions depending on whether the installed + version on the unit is 3.8.2 and newer, or older. If newer then the + --formatter=json option is used to simplify parsing of the cluster data. + + :param unit: the unit to fetch running nodes list from + :type unit: :class:`juju.model.ModelEntity` + :returns: List containing node names of running nodes + :rtype: List[str] + """ + if is_rabbitmq_version_ge_382(unit): + return _get_cluster_running_nodes_38(unit) + else: + return _get_cluster_running_nodes_pre_38(unit) + + +def _get_cluster_running_nodes_pre_38(unit): + """Get a list of RabbitMQ cluster's running nodes (pre 3.8.2). + Parse rabbitmqctl cluster_status output string, return list of running rabbitmq cluster nodes. + :param unit: unit pointer + :type unit: :class:`juju.model.ModelEntity` :returns: List containing node names of running nodes + :rtype: List[str] """ # NOTE(beisner): rabbitmqctl cluster_status output is not # json-parsable, do string chop foo, then json.loads that. @@ -156,6 +200,23 @@ def get_cluster_running_nodes(unit): return [] +def _get_cluster_running_nodes_38(unit): + """Get a list of RabbitMQ cluster's running nodes (3.8.2+). + + Return a list of the running rabbitmq cluster nodes from the specified + unit. + + :param unit: the unit to fetch running nodes list from + :type unit: :class:`juju.model.ModelEntity` + :returns: List containing node names of running nodes + :rtype: List[str] + """ + cmd = 'rabbitmqctl cluster_status --formatter=json' + output = zaza.model.run_on_unit(unit.entity_id, cmd)['Stdout'].strip() + decoded = json.loads(output) + return decoded['running_nodes'] + + def validate_cluster_running_nodes(units): """Check all rmq unit hostnames are represented in cluster_status. @@ -454,10 +515,53 @@ def get_amqp_message_by_unit(unit, queue="test", def check_unit_cluster_nodes(unit, unit_node_names): - """Check if unit exists in list of Rmq cluster node names.""" - unit_name = unit.entity_id + """Check if unit exists in list of Rmq cluster node names. + + NOTE: this calls one of two functions depending on whether the installed + version on the unit is 3.8.2 and newer, or older. If newer then the + --formatter=json option is used to simplify parsing of the cluster data. + + :param unit: the unit to fetch running nodes list from + :type unit: :class:`juju.model.ModelEntity` + :param unit_node_names: The unit node names to check against + :type unit_node_names: List[str] + :returns: List containing node names of running nodes + :rtype: List[str] + """ + if is_rabbitmq_version_ge_382(unit): + return _check_unit_cluster_nodes_38(unit, unit_node_names) + else: + return _check_unit_cluster_nodes_pre_38(unit, unit_node_names) + + +def _check_unit_cluster_nodes_38(unit, unit_node_names): + """Check if unit exists in list of Rmq cluster node names (3.8.2+). + + :param unit: the unit to fetch running nodes list from + :type unit: :class:`juju.model.ModelEntity` + :param unit_node_names: The unit node names to check against + :type unit_node_names: List[str] + :returns: List containing node names of running nodes + :rtype: List[str] + """ + cmd = 'rabbitmqctl cluster_status --formatter=json' + output = zaza.model.run_on_unit(unit.entity_id, cmd)['Stdout'].strip() + decoded = json.loads(output) + return _post_check_unit_cluster_nodes( + unit, decoded['disk_nodes'], unit_node_names) + + +def _check_unit_cluster_nodes_pre_38(unit, unit_node_names): + """Check if unit exists in list of Rmq cluster node names (pre 3.8.2). + + :param unit: the unit to fetch running nodes list from + :type unit: :class:`juju.model.ModelEntity` + :param unit_node_names: The unit node names to check against + :type unit_node_names: List[str] + :returns: List containing node names of running nodes + :rtype: List[str] + """ nodes = [] - errors = [] str_stat = get_cluster_status(unit) # make the interesting part of rabbitmqctl cluster_status output # json-parseable. @@ -466,11 +570,17 @@ def check_unit_cluster_nodes(unit, unit_node_names): pos_end = str_stat.find(']}]},', pos_start) + 1 str_nodes = str_stat[pos_start:pos_end].replace("'", '"') nodes = json.loads(str_nodes) + return _post_check_unit_cluster_nodes(unit, nodes, unit_node_names) + + +def _post_check_unit_cluster_nodes(unit, nodes, unit_node_names): + """Finish of the check_unit_cluster_nodes function (internal).""" + unit_name = unit.entity_id + errors = [] for node in nodes: if node not in unit_node_names: errors.append('Cluster registration check failed on {}: ' '{} should not be registered with RabbitMQ ' 'after unit removal.\n' ''.format(unit_name, node)) - return errors