diff --git a/lib/collectinfo_analyzer/get_controller.py b/lib/collectinfo_analyzer/get_controller.py index 36190b51..c6966b30 100644 --- a/lib/collectinfo_analyzer/get_controller.py +++ b/lib/collectinfo_analyzer/get_controller.py @@ -357,3 +357,15 @@ def get_role( ] = self.log_handler.admin_acl(stanza=constants.ADMIN_ROLES, nodes=nodes) return GetAclController.new_dict_with_key(data, role_name) + + +class GetClusterMetadataController: + # TODO: do we need this? Technically asadm only really ever deals with metadata. I + # want this to handle things that arn't configs or stats . . . + + def __init__(self, log_analyzer: CollectinfoLogHandler): + self.log_handler = log_analyzer + + def get_builds(self) -> TimestampDict[NodeDict[str]]: + builds = self.log_handler.info_meta_data(stanza="asd_build") + return util.filter_exceptions(builds) diff --git a/lib/collectinfo_analyzer/show_controller.py b/lib/collectinfo_analyzer/show_controller.py index ec2348f6..dc9d74f2 100644 --- a/lib/collectinfo_analyzer/show_controller.py +++ b/lib/collectinfo_analyzer/show_controller.py @@ -14,10 +14,11 @@ from lib.collectinfo_analyzer.get_controller import ( GetAclController, + GetClusterMetadataController, GetConfigController, GetStatisticsController, ) -from lib.utils import common, constants, util +from lib.utils import common, constants, util, version from lib.base_controller import CommandHelp, CommandName, ModifierHelp from .collectinfo_command_controller import CollectinfoCommandController @@ -785,12 +786,12 @@ def __init__(self): self.getter = GetStatisticsController( self.log_handler ) # TODO: Use this getter for more than just xdr + self.meta_getter = GetClusterMetadataController(self.log_handler) @CommandHelp( "Displays bin, set, service, and namespace statistics", ) def _do_default(self, line): - self.do_bins(line[:]) self.do_sets(line[:]) self.do_service(line[:]) self.do_namespace(line[:]) @@ -1004,6 +1005,21 @@ def do_bins(self, line): mods=self.mods, ) + builds = self.meta_getter.get_builds() + + for timestamp in sorted(builds.keys()): + nodes_builds = builds[timestamp] + if any( + [ + version.LooseVersion(build) + >= version.LooseVersion(constants.SERVER_INFO_BINS_REMOVAL_VERSION) + for build in nodes_builds.values() + ] + ): + self.logger.error( + f"Server version {constants.SERVER_INFO_BINS_REMOVAL_VERSION} removed namespace bin-name limits and statistics." + ) + new_bin_stats = self.log_handler.info_statistics( stanza=constants.STAT_BINS, flip=True ) diff --git a/lib/live_cluster/client/node.py b/lib/live_cluster/client/node.py index dabdc869..509f97bc 100644 --- a/lib/live_cluster/client/node.py +++ b/lib/live_cluster/client/node.py @@ -94,7 +94,7 @@ async def wrapper(*args, raise_exception=False, **kwargs): except OSError as e: args[0].alive = False exception = e - except (ASInfoError, ASProtocolError, Exception) as e: + except Exception as e: exception = e if raise_exception: diff --git a/lib/live_cluster/manage_controller.py b/lib/live_cluster/manage_controller.py index 40178b6a..69e5682a 100644 --- a/lib/live_cluster/manage_controller.py +++ b/lib/live_cluster/manage_controller.py @@ -41,7 +41,10 @@ CDTContext, ) from .live_cluster_command_controller import LiveClusterCommandController -from lib.live_cluster.get_controller import GetJobsController +from lib.live_cluster.get_controller import ( + GetClusterMetadataController, + GetJobsController, +) logger = logging.getLogger(__name__) logger.setLevel(logging.CRITICAL) @@ -1082,6 +1085,7 @@ class ManageSIndexCreateController(ManageLeafCommandController): def __init__(self): self.required_modifiers = set(["line", "ns", "bin"]) self.modifiers = set(["set", "in", "ctx"]) + self.meta_getter = GetClusterMetadataController(self.cluster) @staticmethod def _split_ctx_list(ctx_str: str) -> list[str]: @@ -1267,7 +1271,7 @@ async def _do_create(self, line, bin_type: str): cdt_ctx = None if ctx_list: - builds = await self.cluster.info_build(nodes=self.nodes) + builds = await self.meta_getter.get_builds(nodes=self.nodes) if not all( [ @@ -1323,6 +1327,22 @@ async def do_string(self, line): async def do_geo2dsphere(self, line): await self._do_create(line, "geo2dsphere") + # Hack for auto-complete + async def do_blob(self, line): + builds = await self.meta_getter.get_builds() + if any( + [ + version.LooseVersion(build) + < version.LooseVersion(constants.SERVER_SINDEX_BLOB_TYPE_FIRST_VERSION) + for build in builds.values() + ] + ): + raise ShellException( + f"Blob type secondary index is not supported on server version < {constants.SERVER_SINDEX_BLOB_TYPE_FIRST_VERSION}." + ) + + await self._do_create(line, "blob") + @CommandHelp( "Delete a secondary index", diff --git a/lib/utils/constants.py b/lib/utils/constants.py index 9e289aa4..61e0fe92 100644 --- a/lib/utils/constants.py +++ b/lib/utils/constants.py @@ -154,6 +154,7 @@ class JobType: # server versions with critical changes # TODO: Change to functions on the node SERVER_INFO_BINS_REMOVAL_VERSION = "7.0" +SERVER_SINDEX_BLOB_TYPE_FIRST_VERSION = "7.0" SERVER_SINDEX_ON_CDT_FIRST_VERSION = "6.1" SERVER_QUERIES_ABORT_ALL_FIRST_VERSION = "6.0" SERVER_39_BYTE_OVERHEAD_FIRST_VERSION = "6.0" diff --git a/lib/view/view.py b/lib/view/view.py index a16fdb5b..a9e1d327 100644 --- a/lib/view/view.py +++ b/lib/view/view.py @@ -2051,15 +2051,6 @@ def print_health_output( ########################### - @staticmethod - def get_summary_line_prefix(index, key): - s = " " * 3 - s += (str(index) + ".").ljust(3) - s += " " * 2 - s += key.ljust(19) - s += ":" + (" " * 2) - return s - @staticmethod def _summary_cluster_table_view(stats: SummaryClusterDict, **ignore): title = "Cluster Summary" diff --git a/test/e2e/aerospike.conf b/test/e2e/aerospike_6.x.conf similarity index 98% rename from test/e2e/aerospike.conf rename to test/e2e/aerospike_6.x.conf index 1fdf1f19..63806a79 100644 --- a/test/e2e/aerospike.conf +++ b/test/e2e/aerospike_6.x.conf @@ -24,6 +24,7 @@ ${security_stanza} service { + cluster-name 6.x-asadm-test feature-key-file ${feature_path} run-as-daemon false work-directory ${state_directory} diff --git a/test/e2e/aerospike_latest.conf b/test/e2e/aerospike_latest.conf new file mode 100644 index 00000000..7e4a6aac --- /dev/null +++ b/test/e2e/aerospike_latest.conf @@ -0,0 +1,94 @@ +# Aerospike Asadm Test Configuration Template +# +# Copyright (c) 2008-2023 Aerospike, Inc. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +${security_stanza} + +service { + cluster-name 7.x-asadm-test + feature-key-file ${feature_path} + run-as-daemon false + work-directory ${state_directory} + pidfile ${state_directory}/asd.pid + proto-fd-max 1024 + transaction-retry-ms 10000 + transaction-max-ms 10000 +} + +logging { + console { + context any info + context security info + } + file ${log_path} { + context any info + } +} + +mod-lua { + user-path ${udf_directory} +} + +network { + service { + port ${service_port} + address any + access-address ${access_address} + } + + heartbeat { + mode mesh + address any + port ${heartbeat_port} + interval 100 + timeout 3 + connect-timeout-ms 100 + ${peer_connection} + } + + fabric { + port ${fabric_port} + address any + } + + info { + port ${info_port} + address any + } +} + +namespace ${namespace} { + replication-factor 2 + default-ttl 0 + storage-engine memory { + file /opt/aerospike/data/test.dat + filesize 1G + } + nsup-period 60 +} + +xdr { + dc DC1 { + namespace ${namespace} { + } + } +} diff --git a/test/e2e/lib.py b/test/e2e/lib.py index f4cd3ab0..4d61e8b7 100644 --- a/test/e2e/lib.py +++ b/test/e2e/lib.py @@ -279,6 +279,8 @@ def start_server( first_base, index, access_address="127.0.0.1", + docker_tag="latest", + template_file="aerospike_latest.conf", template_content=None, config_content=None, ): @@ -299,7 +301,7 @@ def start_server( base = first_base + 1000 * (index - 1) if template_content is None and not config_content: - template_file = absolute_path("aerospike.conf") + template_file = absolute_path(template_file) with codecs.open(template_file, "r", "UTF-8") as file_obj: template_content = file_obj.read() @@ -329,7 +331,7 @@ def start_server( pass container = DOCKER_CLIENT.containers.run( - "aerospike/aerospike-server-enterprise:latest", + f"aerospike/aerospike-server-enterprise:{docker_tag}", command=cmd, ports={ str(base) + "/tcp": str(base), @@ -352,7 +354,12 @@ def start_server( def start( - do_reset=True, num_nodes=DEFAULT_N_NODES, template_content=None, config_content=None + do_reset=True, + num_nodes=DEFAULT_N_NODES, + docker_tag="latest", + template_file="aerospike_latest.conf", + template_content=None, + config_content=None, ): global CLIENT global NODES @@ -373,6 +380,8 @@ def start( ip = start_server( first_base, index, + docker_tag=docker_tag, + template_file=template_file, template_content=template_content, config_content=config_content, ) diff --git a/test/e2e/live_cluster/test_all.py b/test/e2e/live_cluster/test_all.py index 2e1fc7a5..d508d74c 100644 --- a/test/e2e/live_cluster/test_all.py +++ b/test/e2e/live_cluster/test_all.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json -import shlex import time -from typing import Any +from typing import Any, Callable import unittest -from parameterized import parameterized +from parameterized import parameterized, parameterized_class +from lib.utils import version from test.e2e import util from .. import lib @@ -71,62 +70,121 @@ end """ + +class Cmd: + def __init__(self, cmd: str, server_filter: Callable[[str], bool] | None = None): + self.cmd = cmd + self.server_filter = server_filter + + def check_skip(self, tc: unittest.TestCase, server_version: str): + if self.server_filter and not self.server_filter(server_version): + tc.skipTest(f"Skipping test for server version {server_version}") + + def __hash__(self) -> int: + return hash(self.cmd) + + def __eq__(self, other: object) -> bool: + if isinstance(other, Cmd): + return self.cmd == other.cmd + return False + + CMDS = [ - ("info network"), - ("info namespace object"), - ("info namespace usage"), - ("info set"), - ("info xdr"), - ("info sindex"), - ("show config namespace"), - ("show config network"), - ("show config security"), - ("show config service"), - ("show config dc"), - ("show config xdr dc"), - ("show config xdr filter"), - ("show config xdr namespace"), - ("show statistics namespace"), - ("show statistics service"), - ("show statistics sindex"), - ("show statistics sets"), - ("show statistics bins"), - ("show statistics dc"), - ("show statistics xdr dc"), - ("show statistics xdr namespace"), - ("show latencies -v"), - ("show distribution time_to_live"), # TODO: Causing issues on github actions - ("show distribution object_size"), - ("show mapping ip"), - ("show mapping node"), - ("show pmap"), - ("show best-practices"), - ("show jobs queries"), - ("show racks"), - ("show roster"), - ("show roles"), - ("show users"), - ("show users admin"), - ("show users statistics"), - ("show users statistics admin"), - ("show udfs"), - ("show sindex"), - ("show stop-writes"), - ("summary"), - (f"generate config with all"), + Cmd("info network"), + Cmd("info namespace object"), + Cmd("info namespace usage"), + Cmd("info set"), + Cmd("info xdr"), + Cmd("info sindex"), + Cmd("show config namespace"), + Cmd("show config network"), + Cmd("show config security"), + Cmd("show config service"), + Cmd("show config dc"), + Cmd("show config xdr dc"), + Cmd("show config xdr filter"), + Cmd("show config xdr namespace"), + Cmd("show statistics namespace"), + Cmd("show statistics service"), + Cmd("show statistics sindex"), + Cmd("show statistics sets"), + Cmd( + "show statistics bins", + lambda v: v != "latest" + and version.LooseVersion(v) < version.LooseVersion("7.0.0"), + ), + Cmd("show statistics dc"), + Cmd("show statistics xdr dc"), + Cmd("show statistics xdr namespace"), + Cmd("show latencies -v"), + Cmd("show distribution time_to_live"), # TODO: Causing issues on github actions + Cmd("show distribution object_size"), + Cmd("show mapping ip"), + Cmd("show mapping node"), + Cmd("show pmap"), + Cmd("show best-practices"), + Cmd("show jobs queries"), + Cmd("show racks"), + Cmd("show roster"), + Cmd("show roles"), + Cmd("show users"), + Cmd("show users admin"), + Cmd("show users statistics"), + Cmd("show users statistics admin"), + Cmd("show udfs"), + Cmd("show sindex"), + Cmd("show stop-writes"), + Cmd("summary"), + Cmd(f"generate config with all"), ] NOT_IN_CI_MODE = [ - "show mapping ip", - "show mapping node", - "show pmap", - f"generate config with all", + Cmd("show mapping ip"), + Cmd("show mapping node"), + Cmd("show pmap"), + Cmd(f"generate config with all"), ] -class TableRenderNoErrorTests(unittest.TestCase): +class TableRenderTestCase(unittest.TestCase): + def assertEntryNotError(self, entry: dict[str, Any]): + raw_data = entry["raw"] + conv_data = entry["converted"] + self.assertNotEqual(raw_data, "error") + self.assertNotEqual(conv_data, "~~") + + def assertRecordNotError(self, record: dict[str, Any]): + for col_name, data in record.items(): + if isinstance(list(data.values())[0], dict): + for col_name, data2 in data.items(): + print(data2) + self.assertEntryNotError(data2) + else: + print(data) + self.assertEntryNotError(data) + + def check_cmd_for_errors(self, cp: util.CompletedProcess): + self.assertEqual(cp.returncode, 0, "Incorrect return code") + + if "traceback" in cp.stderr: + self.fail("Traceback found in stderr") + + +@parameterized_class( + [ + {"template_file": "aerospike_latest.conf", "docker_tag": "latest"}, + # {"template_file": "aerospike_6.x.conf", "docker_tag": "6.4.0.7"}, # Add this + # to all tests once we create multiple test workflows. I am thinking one for + # unittest, one for e2e against latest, and another that is e2e against all + # notable versions i.e. 4.9, 5.6, 6.4 + ] +) +class TableRenderNoErrorTests(TableRenderTestCase): + template_file = "" + docker_tag = "" + @classmethod def setUpClass(cls): - lib.start() + lib.start(template_file=cls.template_file, docker_tag=cls.docker_tag) lib.populate_db("no-error-test") lib.create_sindex("a-index", "numeric", lib.NAMESPACE, "a", "no-error-test") lib.create_xdr_filter(lib.NAMESPACE, lib.DC, "kxGRSJMEk1ECo2FnZRU=") @@ -145,50 +203,20 @@ def setUpClass(cls): def tearDownClass(cls): lib.stop() - def assertEntryNotError(self, tc: unittest.TestCase, entry: dict[str, Any]): - raw_data = entry["raw"] - conv_data = entry["converted"] - tc.assertNotEqual(raw_data, "error") - tc.assertNotEqual(conv_data, "~~") - - def assertRecordNotError(self, tc: unittest.TestCase, record: dict[str, Any]): - for col_name, data in record.items(): - if isinstance(list(data.values())[0], dict): - for col_name, data2 in data.items(): - print(data2) - self.assertEntryNotError(tc, data2) - else: - print(data) - self.assertEntryNotError(tc, data) - - def check_cmd_for_errors(self, cp: util.CompletedProcess): - self.assertEqual(cp.returncode, 0, "Incorrect return code") - - try: - stdout_dicts = util.get_separate_output(cp.stdout) - except Exception as e: - self.fail("Unable to unmarshal json: {}".format(e)) - - if "traceback" in cp.stderr: - self.fail("Traceback found in stderr") - - for dict in stdout_dicts: - for group in dict["groups"]: - for record in group["records"]: - self.assertRecordNotError(self, record) - @parameterized.expand(CMDS) - def test_live_cmds_for_errors(self, cmd): - args = f"-h {lib.SERVER_IP}:{lib.PORT} -e '{cmd}' --json -Uadmin -Padmin" + def test_live_cmds_for_errors(self, cmd: Cmd): + cmd.check_skip(self, self.docker_tag) + args = f"-h {lib.SERVER_IP}:{lib.PORT} -e '{cmd.cmd}' --json -Uadmin -Padmin" o = util.run_asadm(args) self.check_cmd_for_errors(o) @parameterized.expand(list(set(CMDS).difference(NOT_IN_CI_MODE))) - def test_collectinfo_cmds_for_errors(self, cmd): + def test_collectinfo_cmds_for_errors(self, cmd: Cmd): + cmd.check_skip(self, self.docker_tag) collectinfo_path = util.get_collectinfo_path( self.collectinfo_cp, "/tmp/asadm_test_" ) - args = "-cf {} -e '{}' --json".format(collectinfo_path, cmd) + args = "-cf {} -e '{}' --json".format(collectinfo_path, cmd.cmd) o = util.run_asadm(args) print(o.stdout) print(o.stderr) @@ -196,10 +224,22 @@ def test_collectinfo_cmds_for_errors(self, cmd): self.check_cmd_for_errors(o) -class TableRenderNodeUnreachableTests(unittest.TestCase): +@parameterized_class( + [ + {"template_file": "aerospike_latest.conf", "docker_tag": "latest"}, + # {"template_file": "aerospike_6.x.conf", "docker_tag": "6.4.0.7"}, # Add this + # to all tests once we create multiple test workflows. I am thinking one for + # unittest, one for e2e against latest, and another that is e2e against all + # notable versions i.e. 4.9, 5.6, 6.4 + ] +) +class TableRenderNodeUnreachableTests(TableRenderTestCase): + template_file = "" + docker_tag = "" + @classmethod def setUpClass(cls): - lib.start() + lib.start(template_file=cls.template_file, docker_tag=cls.docker_tag) lib.populate_db("no-error-test") lib.create_sindex("a-index", "numeric", lib.NAMESPACE, "a", "no-error-test") lib.create_xdr_filter(lib.NAMESPACE, lib.DC, "kxGRSJMEk1ECo2FnZRU=") @@ -219,40 +259,20 @@ def setUpClass(cls): def tearDownClass(cls): lib.stop() - def assertEntryNotError(self, tc: unittest.TestCase, entry: dict[str, Any]): - raw_data = entry["raw"] - conv_data = entry["converted"] - tc.assertNotEqual(raw_data, "error") - tc.assertNotEqual(conv_data, "~~") - - def assertRecordNotError(self, tc: unittest.TestCase, record: dict[str, Any]): - for col_name, data in record.items(): - if isinstance(list(data.values())[0], dict): - for col_name, data2 in data.items(): - print(data2) - self.assertEntryNotError(tc, data2) - else: - print(data) - self.assertEntryNotError(tc, data) - - def check_cmd_for_errors(self, cp: util.CompletedProcess): - self.assertEqual(cp.returncode, 0, "Incorrect return code") - - if "traceback" in cp.stderr: - self.fail("Traceback found in stderr") - @parameterized.expand(CMDS) - def test_live_cmds_for_errors(self, cmd): - args = f"-h {lib.SERVER_IP}:{lib.PORT} -e '{cmd}' --json -Uadmin -Padmin" + def test_live_cmds_for_errors(self, cmd: Cmd): + cmd.check_skip(self, self.docker_tag) + args = f"-h {lib.SERVER_IP}:{lib.PORT} -e '{cmd.cmd}' --json -Uadmin -Padmin" o = util.run_asadm(args) self.check_cmd_for_errors(o) @parameterized.expand(list(set(CMDS).difference(NOT_IN_CI_MODE))) - def test_collectinfo_cmds_for_errors(self, cmd): + def test_collectinfo_cmds_for_errors(self, cmd: Cmd): + cmd.check_skip(self, self.docker_tag) collectinfo_path = util.get_collectinfo_path( self.collectinfo_cp, "/tmp/asadm_test_" ) - args = "-cf {} -e '{}' --json".format(collectinfo_path, cmd) + args = "-cf {} -e '{}' --json".format(collectinfo_path, cmd.cmd) o = util.run_asadm(args) print(o.stdout) print(o.stderr) diff --git a/test/e2e/live_cluster/test_collectinfo.py b/test/e2e/live_cluster/test_collectinfo.py index b3197a24..ac85e8cf 100644 --- a/test/e2e/live_cluster/test_collectinfo.py +++ b/test/e2e/live_cluster/test_collectinfo.py @@ -35,7 +35,7 @@ class TestCollectinfo(asynctest.TestCase): CMDS = [ ("info network", ["Uptime", "Client Conns"], None), ("info namespace object", [], None), - ("info namespace usage", [], None), + ("info namespace usage", ["System Memory"], None), ("info set", [], None), ("info xdr", ["Lag (hh:mm:ss)"], None), ("info sindex", [], None), diff --git a/test/e2e/live_cluster/test_confgen.py b/test/e2e/live_cluster/test_confgen.py index 420ef9a8..5c603cf6 100644 --- a/test/e2e/live_cluster/test_confgen.py +++ b/test/e2e/live_cluster/test_confgen.py @@ -108,7 +108,7 @@ def tearDown(self): lib.stop() async def test_genconf(self): - lib.start(num_nodes=1, template_content=aerospike_conf) + lib.start(num_nodes=1) time.sleep(1) conf_gen_cmd = f"generate config with 127.0.0.1:{lib.PORT}" show_config_cmd = "show config; show config security; show config xdr" @@ -167,7 +167,7 @@ async def test_genconf(self): ) async def test_genconf_save_to_file(self): - lib.start(num_nodes=1, template_content=aerospike_conf) + lib.start(num_nodes=1) time.sleep(1) conf_gen_cmd = f"generate config with all" cp = util.run_asadm( diff --git a/test/e2e/live_cluster/test_manage.py b/test/e2e/live_cluster/test_manage.py index 051b07bb..a7c00ccf 100644 --- a/test/e2e/live_cluster/test_manage.py +++ b/test/e2e/live_cluster/test_manage.py @@ -768,6 +768,21 @@ def test_can_create_geo2dspehere_sindex(self): self.assertEqual(exp_stdout, actual.stdout) self.assertStdErrEqual(exp_stderr, actual.stderr) + def test_can_create_blob_sindex(self): + exp_stdout = self.success_msg + exp_stderr = "" + + actual = test_util.run_asadm( + self.get_args( + "manage sindex create blob {} ns {} set {} bin {}".format( + self.exp_sindex, self.exp_ns, self.exp_set, self.exp_bin + ) + ) + ) + + self.assertEqual(exp_stdout, actual.stdout) + self.assertStdErrEqual(exp_stderr, actual.stderr) + def test_can_create_sindex_in_list(self): exp_stdout = self.success_msg exp_stderr = "" @@ -926,8 +941,8 @@ def tearDownClass(cls) -> None: ["Node", "Response"], ), ( - "manage config namespace test storage-engine param cache-replica-writes to false", - "Set Namespace Param cache-replica-writes to false", + "manage config namespace test storage-engine param enable-benchmarks-storage to false", + "Set Namespace Param enable-benchmarks-storage to false", ["Node", "Response"], ), ( diff --git a/test/unit/live_cluster/test_manage_controller.py b/test/unit/live_cluster/test_manage_controller.py index 78ef98b5..7ab56cc6 100644 --- a/test/unit/live_cluster/test_manage_controller.py +++ b/test/unit/live_cluster/test_manage_controller.py @@ -21,7 +21,7 @@ ModifierHelp, ShellException, ) -from mock import MagicMock, patch +from mock import MagicMock, create_autospec, patch from mock.mock import AsyncMock, call from parameterized import parameterized @@ -36,6 +36,7 @@ from lib.live_cluster.client.config_handler import JsonDynamicConfigHandler from lib.live_cluster.client.ctx import ASValues, CDTContext, CTXItems from lib.live_cluster.client.node import Node +from lib.live_cluster.get_controller import GetClusterMetadataController from lib.live_cluster.live_cluster_command_controller import ( LiveClusterCommandController, ) @@ -57,7 +58,6 @@ ManageRosterRemoveController, ManageRosterStageNodesController, ManageRosterStageObservedController, - ManageSIndexController, ManageSIndexCreateController, ManageSIndexDeleteController, ManageTruncateController, @@ -1693,8 +1693,9 @@ def setUp(self) -> None: self.prompt_mock = patch( "lib.live_cluster.manage_controller.ManageSIndexCreateController.prompt_challenge" ).start() - - self.cluster_mock.info_build.return_value = {"principal": "6.1.0.0"} + self.meta_mock = self.controller.meta_getter = create_autospec( + GetClusterMetadataController + ) self.addCleanup(patch.stopall) @@ -1702,6 +1703,7 @@ async def test_prompt_challenge_fails(self): line = "numeric a-index ns test bin a ctx list_value(1)".split() self.controller.warn = True self.prompt_mock.return_value = False + self.meta_mock.get_builds.return_value = {"principal": "6.1.0.0"} await self.controller.execute(line) @@ -1709,11 +1711,13 @@ async def test_prompt_challenge_fails(self): "Adding a secondary index will cause longer restart times." ) - async def test_create_successful(self): - line = "numeric a-index ns test set testset bin a in mapkeys ctx list_value(int(1))".split() + @parameterized.expand([("numeric",), ("string",), ("geo2dsphere",), ("blob",)]) + async def test_create_successful(self, bin_type): + line = f"{bin_type} a-index ns test set testset bin a in mapkeys ctx list_value(int(1))".split() self.cluster_mock.info_sindex_create.return_value = { "1.1.1.1": ASINFO_RESPONSE_OK } + self.meta_mock.get_builds.return_value = {"principal": "7.0.0.0"} await self.controller.execute(line) @@ -1721,7 +1725,7 @@ async def test_create_successful(self): "a-index", "test", "a", - "numeric", + bin_type, "mapkeys", "testset", CDTContext([CTXItems.ListValue(ASValues.ASInt(1))]), @@ -1733,6 +1737,7 @@ async def test_create_successful(self): async def test_create_fails_with_asinfo_error(self): line = "numeric a-index ns test bin a ctx list_value(1)".split() + self.meta_mock.get_builds.return_value = {"principal": "6.1.0.0"} self.cluster_mock.info_sindex_create.return_value = { "1.1.1.1": ASInfoResponseError("foo", "ERROR::bar") } @@ -1743,7 +1748,7 @@ async def test_create_fails_with_asinfo_error(self): async def test_ctx_invalid_format(self): line = "numeric a-index ns test bin a ctx foo".split() - self.cluster_mock.info_build.return_value = {"principal": "6.1.0.0"} + self.meta_mock.get_builds.return_value = {"principal": "6.1.0.0"} await self.assertAsyncRaisesRegex( ShellException, @@ -1753,7 +1758,7 @@ async def test_ctx_invalid_format(self): async def test_ctx_not_supported(self): line = "numeric a-index ns test bin a ctx [foo]".split() - self.cluster_mock.info_build.return_value = {"principal": "6.0.0.0"} + self.meta_mock.get_builds.return_value = {"principal": "6.0.0.0"} await self.assertAsyncRaisesRegex( ShellException, @@ -1761,6 +1766,16 @@ async def test_ctx_not_supported(self): self.controller.execute(line), ) + async def test_blob_not_supported(self): + line = "blob a-index ns test bin a ctx [foo]".split() + self.meta_mock.get_builds.return_value = {"principal": "6.4.0.0"} + + await self.assertAsyncRaisesRegex( + ShellException, + "Blob type secondary index is not supported on server version < 7.0", + self.controller.execute(line), + ) + class ManageSIndexDeleteControllerTest(asynctest.TestCase): def setUp(self) -> None: