diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 0a0f39814..1bfc5e4b6 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -72,6 +72,7 @@ jobs: artifacts_dir: "ecchronos-binary/target" steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Cache local Maven repository uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: @@ -79,22 +80,34 @@ jobs: key: build-${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | build-${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java_version }} uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 with: java-version: ${{ matrix.java_version }} distribution: 'temurin' + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Set up Python 3 uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: 3 + + - name: Install black + run: python -m pip install black==24.10.0 + + - name: Run black in the check mode + run: black --check --verbose --line-length 120 ./ecchronos-binary/src + - name: install dependencies run: mvn install -DskipTests=true + - run: mvn $TEST_SUITE -B id: tests env: TEST_SUITE: ${{ matrix.test_suite }} + - name: Upload artifacts if: ${{ failure() && steps.tests.conclusion == 'failure' }} uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 @@ -102,6 +115,7 @@ jobs: name: ${{ matrix.name }}-cassandra-logs path: ${{ matrix.artifacts_dir }}/cassandra*.log if-no-files-found: 'ignore' + - name: Upload coverage to Codecov uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 with: diff --git a/ecchronos-binary/src/bin/ecctool.py b/ecchronos-binary/src/bin/ecctool.py index a5d370c4f..5c95682ec 100755 --- a/ecchronos-binary/src/bin/ecctool.py +++ b/ecchronos-binary/src/bin/ecctool.py @@ -38,10 +38,12 @@ def get_parser(): - parser = ArgumentParser(description="ecctool is a command line utility which can be used to perform actions " - "towards a local ecChronos instance. The actions are implemented in form of " - "subcommands with arguments. All visualization is displayed in form of " - "human-readable tables.") + parser = ArgumentParser( + description="ecctool is a command line utility which can be used to perform actions " + "towards a local ecChronos instance. The actions are implemented in form of " + "subcommands with arguments. All visualization is displayed in form of " + "human-readable tables." + ) sub_parsers = parser.add_subparsers(dest="subcommand") add_repairs_subcommand(sub_parsers) add_schedules_subcommand(sub_parsers) @@ -54,160 +56,262 @@ def get_parser(): return parser + def add_running_job_subcommand(sub_parsers): - parser_repairs = sub_parsers.add_parser("running-job", description="Show which (if any) job is currently running ") + parser_repairs = sub_parsers.add_parser("running-job", description="Show which (if any) job is currently running ") - parser_repairs.add_argument("-u", "--url", type=str, - help="The ecChronos host to connect to, specified in the format http://:.", - default=None) + parser_repairs.add_argument( + "-u", + "--url", + type=str, + help="The ecChronos host to connect to, specified in the format http://:.", + default=None, + ) def add_repairs_subcommand(sub_parsers): - parser_repairs = sub_parsers.add_parser("repairs", - description="Show the status of all manual repairs. This subcommand has " - "no mandatory parameters.") - parser_repairs.add_argument("-k", "--keyspace", type=str, - help="Show repairs for the specified keyspace. This argument is mutually exclusive " - "with -i and --id.") - parser_repairs.add_argument("-t", "--table", type=str, - help="Show repairs for the specified table. Keyspace argument -k or --keyspace " - "becomes mandatory if using this argument. This argument is mutually exclusive " - "with -i and --id.") - parser_repairs.add_argument("-u", "--url", type=str, - help="The ecChronos host to connect to, specified in the format http://:.", - default=None) - parser_repairs.add_argument("-i", "--id", type=str, - help="Show repairs matching the specified ID. This argument is mutually exclusive " - "with -k, --keyspace, -t and --table.") - parser_repairs.add_argument("-l", "--limit", type=int, - help="Limits the number of rows printed in the output. Specified as a number, " - "-1 to disable limit.", - default=-1) - parser_repairs.add_argument("--hostid", type=str, - help='Show repairs for the specified host id. The host id corresponds to the ' - 'Cassandra instance ecChronos is connected to.') + parser_repairs = sub_parsers.add_parser( + "repairs", description="Show the status of all manual repairs. This subcommand has " "no mandatory parameters." + ) + parser_repairs.add_argument( + "-k", + "--keyspace", + type=str, + help="Show repairs for the specified keyspace. This argument is mutually exclusive " "with -i and --id.", + ) + parser_repairs.add_argument( + "-t", + "--table", + type=str, + help="Show repairs for the specified table. Keyspace argument -k or --keyspace " + "becomes mandatory if using this argument. This argument is mutually exclusive " + "with -i and --id.", + ) + parser_repairs.add_argument( + "-u", + "--url", + type=str, + help="The ecChronos host to connect to, specified in the format http://:.", + default=None, + ) + parser_repairs.add_argument( + "-i", + "--id", + type=str, + help="Show repairs matching the specified ID. This argument is mutually exclusive " + "with -k, --keyspace, -t and --table.", + ) + parser_repairs.add_argument( + "-l", + "--limit", + type=int, + help="Limits the number of rows printed in the output. Specified as a number, " "-1 to disable limit.", + default=-1, + ) + parser_repairs.add_argument( + "--hostid", + type=str, + help="Show repairs for the specified host id. The host id corresponds to the " + "Cassandra instance ecChronos is connected to.", + ) def add_schedules_subcommand(sub_parsers): - parser_schedules = sub_parsers.add_parser("schedules", - description="Show the status of schedules. This subcommand has no " - "mandatory parameters.") - parser_schedules.add_argument("-k", "--keyspace", type=str, - help="Show schedules for the specified keyspace. This argument is mutually " - "exclusive with -i and --id.") - parser_schedules.add_argument("-t", "--table", type=str, - help="Show schedules for the specified table. Keyspace argument -k or --keyspace " - "becomes mandatory if using this argument. This argument is mutually exclusive " - "with -i and --id.") - parser_schedules.add_argument("-u", "--url", type=str, - help="The ecChronos host to connect to, specified in the format " - "http://:.", - default=None) - parser_schedules.add_argument("-i", "--id", type=str, - help="Show schedules matching the specified ID. This argument is mutually exclusive " - "with -k, --keyspace, -t and --table.") - parser_schedules.add_argument("-f", "--full", action="store_true", - help="Show full schedules, can only be used with -i or --id. Full schedules include " - "schedule configuration and repair state per vnode.", - default=False) - parser_schedules.add_argument("-l", "--limit", type=int, - help="Limits the number of rows printed in the output. Specified as a number, " - "-1 to disable limit.", - default=-1) + parser_schedules = sub_parsers.add_parser( + "schedules", description="Show the status of schedules. This subcommand has no " "mandatory parameters." + ) + parser_schedules.add_argument( + "-k", + "--keyspace", + type=str, + help="Show schedules for the specified keyspace. This argument is mutually " "exclusive with -i and --id.", + ) + parser_schedules.add_argument( + "-t", + "--table", + type=str, + help="Show schedules for the specified table. Keyspace argument -k or --keyspace " + "becomes mandatory if using this argument. This argument is mutually exclusive " + "with -i and --id.", + ) + parser_schedules.add_argument( + "-u", + "--url", + type=str, + help="The ecChronos host to connect to, specified in the format " "http://:.", + default=None, + ) + parser_schedules.add_argument( + "-i", + "--id", + type=str, + help="Show schedules matching the specified ID. This argument is mutually exclusive " + "with -k, --keyspace, -t and --table.", + ) + parser_schedules.add_argument( + "-f", + "--full", + action="store_true", + help="Show full schedules, can only be used with -i or --id. Full schedules include " + "schedule configuration and repair state per vnode.", + default=False, + ) + parser_schedules.add_argument( + "-l", + "--limit", + type=int, + help="Limits the number of rows printed in the output. Specified as a number, " "-1 to disable limit.", + default=-1, + ) def add_run_repair_subcommand(sub_parsers): - parser_run_repair = sub_parsers.add_parser("run-repair", - description="Run a manual repair. The manual repair will be triggered " - "in ecChronos. EcChronos will perform repair through " - "Cassandra JMX interface. This subcommand has no " - "mandatory parameters.") - parser_run_repair.add_argument("-u", "--url", type=str, - help="The ecChronos host to connect to, specified in the format " - "http://:.", - default=None) - parser_run_repair.add_argument("--local", action='store_true', - help='Run repair for the local node only, i.e repair will only be performed for ' - 'the ranges that the local node is a replica for.', default=False) - parser_run_repair.add_argument("-r", "--repair_type", type=str, - help="The type of the repair, possible values are 'vnode', 'parallel_vnode', " - "'incremental'", required=False) - parser_run_repair.add_argument("-k", "--keyspace", type=str, - help="Run repair for the specified keyspace. Repair will be run for all tables " - "within the keyspace with replication factor higher than 1.", required=False) - parser_run_repair.add_argument("-t", "--table", type=str, - help="Run repair for the specified table. Keyspace argument -k or --keyspace " - "becomes mandatory if using this argument.", required=False) + parser_run_repair = sub_parsers.add_parser( + "run-repair", + description="Run a manual repair. The manual repair will be triggered " + "in ecChronos. EcChronos will perform repair through " + "Cassandra JMX interface. This subcommand has no " + "mandatory parameters.", + ) + parser_run_repair.add_argument( + "-u", + "--url", + type=str, + help="The ecChronos host to connect to, specified in the format " "http://:.", + default=None, + ) + parser_run_repair.add_argument( + "--local", + action="store_true", + help="Run repair for the local node only, i.e repair will only be performed for " + "the ranges that the local node is a replica for.", + default=False, + ) + parser_run_repair.add_argument( + "-r", + "--repair_type", + type=str, + help="The type of the repair, possible values are 'vnode', 'parallel_vnode', " "'incremental'", + required=False, + ) + parser_run_repair.add_argument( + "-k", + "--keyspace", + type=str, + help="Run repair for the specified keyspace. Repair will be run for all tables " + "within the keyspace with replication factor higher than 1.", + required=False, + ) + parser_run_repair.add_argument( + "-t", + "--table", + type=str, + help="Run repair for the specified table. Keyspace argument -k or --keyspace " + "becomes mandatory if using this argument.", + required=False, + ) def add_repair_info_subcommand(sub_parsers): - parser_repair_info = sub_parsers.add_parser("repair-info", - description="Get information about repairs for tables. The repair " - "information is based on repair history, meaning that " - "both manual repairs and schedules will contribute to the " - "repair information. This subcommand requires the user to " - "provide either --since or --duration if --keyspace and " - "--table is not provided. If repair info is fetched for a " - "specific table using --keyspace and --table, " - "the duration will default to the table's " - "GC_GRACE_SECONDS.") - parser_repair_info.add_argument("-k", "--keyspace", type=str, - help="Show repair information for all tables in the specified keyspace.") - parser_repair_info.add_argument("-t", "--table", type=str, - help="Show repair information for the specified table. Keyspace argument -k or " - "--keyspace becomes mandatory if using this argument.") - parser_repair_info.add_argument("-s", "--since", type=str, - help="Show repair information since the specified date to now. Date must be " - "specified in ISO8601 format. The time-window will be since to now. " - "Mandatory if --duration or --keyspace and --table is not specified.", - default=None) - parser_repair_info.add_argument("-d", "--duration", type=str, - help="Show repair information for the duration. Duration can be specified as " - "ISO8601 format or as simple format in form: 5s, 5m, 5h, 5d. The time-window " - "will be now-duration to now. Mandatory if --since or --keyspace and --table " - "is not specified.", - default=None) - parser_repair_info.add_argument("--local", action='store_true', - help='Show repair information only for the local node.', - default=False) - parser_repair_info.add_argument("-u", "--url", type=str, - help="The ecChronos host to connect to, specified in the format " - "http://:.", - default=None) - parser_repair_info.add_argument("-l", "--limit", type=int, - help="Limits the number of rows printed in the output. Specified as a number, " - "-1 to disable limit.", - default=-1) + parser_repair_info = sub_parsers.add_parser( + "repair-info", + description="Get information about repairs for tables. The repair " + "information is based on repair history, meaning that " + "both manual repairs and schedules will contribute to the " + "repair information. This subcommand requires the user to " + "provide either --since or --duration if --keyspace and " + "--table is not provided. If repair info is fetched for a " + "specific table using --keyspace and --table, " + "the duration will default to the table's " + "GC_GRACE_SECONDS.", + ) + parser_repair_info.add_argument( + "-k", "--keyspace", type=str, help="Show repair information for all tables in the specified keyspace." + ) + parser_repair_info.add_argument( + "-t", + "--table", + type=str, + help="Show repair information for the specified table. Keyspace argument -k or " + "--keyspace becomes mandatory if using this argument.", + ) + parser_repair_info.add_argument( + "-s", + "--since", + type=str, + help="Show repair information since the specified date to now. Date must be " + "specified in ISO8601 format. The time-window will be since to now. " + "Mandatory if --duration or --keyspace and --table is not specified.", + default=None, + ) + parser_repair_info.add_argument( + "-d", + "--duration", + type=str, + help="Show repair information for the duration. Duration can be specified as " + "ISO8601 format or as simple format in form: 5s, 5m, 5h, 5d. The time-window " + "will be now-duration to now. Mandatory if --since or --keyspace and --table " + "is not specified.", + default=None, + ) + parser_repair_info.add_argument( + "--local", action="store_true", help="Show repair information only for the local node.", default=False + ) + parser_repair_info.add_argument( + "-u", + "--url", + type=str, + help="The ecChronos host to connect to, specified in the format " "http://:.", + default=None, + ) + parser_repair_info.add_argument( + "-l", + "--limit", + type=int, + help="Limits the number of rows printed in the output. Specified as a number, " "-1 to disable limit.", + default=-1, + ) def add_start_subcommand(sub_parsers): - parser_config = sub_parsers.add_parser("start", - description="Start the ecChronos service. This subcommand has no mandatory " - "parameters.") - parser_config.add_argument("-f", "--foreground", action="store_true", - help="Start the ecChronos instance in foreground mode (exec in current terminal and " - "log to stdout)", default=False) - parser_config.add_argument("-p", "--pidfile", type=str, - help="Start the ecChronos instance and store the pid in the specified pid file.") + parser_config = sub_parsers.add_parser( + "start", description="Start the ecChronos service. This subcommand has no mandatory " "parameters." + ) + parser_config.add_argument( + "-f", + "--foreground", + action="store_true", + help="Start the ecChronos instance in foreground mode (exec in current terminal and " "log to stdout)", + default=False, + ) + parser_config.add_argument( + "-p", "--pidfile", type=str, help="Start the ecChronos instance and store the pid in the specified pid file." + ) def add_stop_subcommand(sub_parsers): - parser_stop = sub_parsers.add_parser("stop", - description="Stop the ecChronos instance. Stopping of ecChronos is done by " - "using kill with SIGTERM signal (same as kill in shell) for the " - "pid. This subcommand has no mandatory parameters.") - parser_stop.add_argument("-p", "--pidfile", type=str, - help="Stops the ecChronos instance by pid fetched from the specified pid file.") + parser_stop = sub_parsers.add_parser( + "stop", + description="Stop the ecChronos instance. Stopping of ecChronos is done by " + "using kill with SIGTERM signal (same as kill in shell) for the " + "pid. This subcommand has no mandatory parameters.", + ) + parser_stop.add_argument( + "-p", "--pidfile", type=str, help="Stops the ecChronos instance by pid fetched from the specified pid file." + ) def add_status_subcommand(sub_parsers): - parser_status = sub_parsers.add_parser("status", - description="View status of ecChronos instance. This subcommand has no " - "mandatory parameters.") - parser_status.add_argument("-u", "--url", type=str, - help="The ecChronos host to connect to, specified in the format " - "http://:.", - default=None) + parser_status = sub_parsers.add_parser( + "status", description="View status of ecChronos instance. This subcommand has no " "mandatory parameters." + ) + parser_status.add_argument( + "-u", + "--url", + type=str, + help="The ecChronos host to connect to, specified in the format " "http://:.", + default=None, + ) def schedules(arguments): @@ -275,8 +379,9 @@ def run_repair(arguments): if not arguments.keyspace and arguments.table: print("--keyspace must be specified if table is specified") sys.exit(1) - result = request.post(keyspace=arguments.keyspace, table=arguments.table, local=arguments.local, - repair_type=arguments.repair_type) + result = request.post( + keyspace=arguments.keyspace, table=arguments.table, local=arguments.local, repair_type=arguments.repair_type + ) if result.is_successful(): table_printer.print_repairs(result.data) else: @@ -297,9 +402,13 @@ def repair_info(arguments): print("'+' and '-' is not allowed in duration, check help for more information") sys.exit(1) duration = arguments.duration.upper() - result = request.get_repair_info(keyspace=arguments.keyspace, table=arguments.table, - since=arguments.since, duration=duration, - local=arguments.local) + result = request.get_repair_info( + keyspace=arguments.keyspace, + table=arguments.table, + since=arguments.since, + duration=duration, + local=arguments.local, + ) if result.is_successful(): table_printer.print_repair_info(result.data, arguments.limit) else: @@ -336,8 +445,12 @@ def get_jvm_opts(conf_dir): def run_ecc(cwd, command, arguments): if arguments.foreground: command += " -f" - proc = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # pylint: disable=consider-using-with - cwd=cwd) + proc = subprocess.Popen( # pylint: disable=consider-using-with + command.split(" "), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=cwd, + ) pid = proc.pid print("ecc started with pid {0}".format(pid)) pid_file = os.path.join(cwd, DEFAULT_PID_FILE) @@ -377,13 +490,13 @@ def status(arguments, print_running=False): print("ecChronos is not running") sys.exit(1) + def running_job(arguments): request = rest.V2RepairSchedulerRequest(base_url=arguments.url) result = request.running_job() print(result) - def run_subcommand(arguments): if arguments.subcommand == "repairs": status(arguments) diff --git a/ecchronos-binary/src/conf.py b/ecchronos-binary/src/conf.py index 943946ea1..06703ca93 100644 --- a/ecchronos-binary/src/conf.py +++ b/ecchronos-binary/src/conf.py @@ -14,11 +14,11 @@ # limitations under the License. # -project = 'ecChronos' -copyright = '2023, Telefonaktiebolaget LM Ericsson' -author = 'masokol' +project = "ecChronos" +copyright = "2023, Telefonaktiebolaget LM Ericsson" +author = "masokol" -extensions = ['sphinxcontrib.autoprogram', 'sphinx_markdown_builder'] +extensions = ["sphinxcontrib.autoprogram", "sphinx_markdown_builder"] -templates_path = ['_templates'] +templates_path = ["_templates"] exclude_patterns = [] diff --git a/ecchronos-binary/src/pylib/ecchronoslib/rest.py b/ecchronos-binary/src/pylib/ecchronoslib/rest.py index 556828972..32a4e4f4b 100644 --- a/ecchronos-binary/src/pylib/ecchronoslib/rest.py +++ b/ecchronos-binary/src/pylib/ecchronoslib/rest.py @@ -19,7 +19,7 @@ from urllib.parse import quote except ImportError: from urllib2 import urlopen, Request, HTTPError, URLError - from urllib import quote # pylint: disable=ungrouped-imports + from urllib import quote # pylint: disable=ungrouped-imports import json import os import ssl @@ -51,15 +51,14 @@ def is_successful(self): return self.status_code == 200 def transform_with_data(self, new_data): - return RequestResult(status_code=self.status_code, - data=new_data, - exception=self.exception, - message=self.message) + return RequestResult( + status_code=self.status_code, data=new_data, exception=self.exception, message=self.message + ) class RestRequest(object): - default_base_url = 'http://localhost:8080' - default_https_base_url = 'https://localhost:8080' + default_base_url = "http://localhost:8080" + default_https_base_url = "https://localhost:8080" def __init__(self, base_url=None): if base_url: @@ -78,9 +77,9 @@ def get_param(httpmessage, param): @staticmethod def get_charset(response): - return RestRequest.get_param(response.info(), 'charset') or 'utf-8' + return RestRequest.get_param(response.info(), "charset") or "utf-8" - def request(self, url, method='GET'): + def request(self, url, method="GET"): request_url = "{0}/{1}".format(self.base_url, url) try: request = Request(request_url) @@ -99,17 +98,15 @@ def request(self, url, method='GET'): response.close() return RequestResult(status_code=response.getcode(), data=json_data) except HTTPError as e: - return RequestResult(status_code=e.code, - message="Unable to retrieve resource {0}".format(request_url), - exception=e) + return RequestResult( + status_code=e.code, message="Unable to retrieve resource {0}".format(request_url), exception=e + ) except URLError as e: - return RequestResult(status_code=404, - message="Unable to connect to {0}".format(request_url), - exception=e) + return RequestResult(status_code=404, message="Unable to connect to {0}".format(request_url), exception=e) except Exception as e: # pylint: disable=broad-except - return RequestResult(exception=e, - message="Unable to retrieve resource {0}".format(request_url)) - def basic_request(self, url, method='GET'): + return RequestResult(exception=e, message="Unable to retrieve resource {0}".format(request_url)) + + def basic_request(self, url, method="GET"): request_url = "{0}/{1}".format(self.base_url, url) try: request = Request(request_url) @@ -127,38 +124,35 @@ def basic_request(self, url, method='GET'): data = response.read() response.close() - return data.decode('UTF-8') + return data.decode("UTF-8") except HTTPError as e: - return RequestResult(status_code=e.code, - message="Unable to retrieve resource {0}".format(request_url), - exception=e) + return RequestResult( + status_code=e.code, message="Unable to retrieve resource {0}".format(request_url), exception=e + ) except URLError as e: - return RequestResult(status_code=404, - message="Unable to connect to {0}".format(request_url), - exception=e) + return RequestResult(status_code=404, message="Unable to connect to {0}".format(request_url), exception=e) except Exception as e: # pylint: disable=broad-except - return RequestResult(exception=e, - message="Unable to retrieve resource {0}".format(request_url)) + return RequestResult(exception=e, message="Unable to retrieve resource {0}".format(request_url)) class V2RepairSchedulerRequest(RestRequest): - ROOT = 'repair-management/' - PROTOCOL = ROOT + 'v2/' - REPAIRS = PROTOCOL + 'repairs' - SCHEDULES = PROTOCOL + 'schedules' + ROOT = "repair-management/" + PROTOCOL = ROOT + "v2/" + REPAIRS = PROTOCOL + "repairs" + SCHEDULES = PROTOCOL + "schedules" v2_schedule_status_url = SCHEDULES - v2_schedule_id_status_url = SCHEDULES + '/{0}' - v2_schedule_id_full_status_url = SCHEDULES + '/{0}?full=true' + v2_schedule_id_status_url = SCHEDULES + "/{0}" + v2_schedule_id_full_status_url = SCHEDULES + "/{0}?full=true" v2_repair_status_url = REPAIRS - v2_repair_id_status_url = REPAIRS + '/{0}' + v2_repair_id_status_url = REPAIRS + "/{0}" v2_repair_run_url = REPAIRS - repair_info_url = PROTOCOL + 'repairInfo' + repair_info_url = PROTOCOL + "repairInfo" - running_job_url = PROTOCOL + 'running-job' + running_job_url = PROTOCOL + "running-job" def __init__(self, base_url=None): RestRequest.__init__(self, base_url) @@ -234,13 +228,19 @@ def post(self, keyspace=None, table=None, local=False, repair_type="vnode"): request_url += "&repairType=" + repair_type else: request_url += "?repairType=" + repair_type - result = self.request(request_url, 'POST') + result = self.request(request_url, "POST") if result.is_successful(): result = result.transform_with_data(new_data=[Repair(x) for x in result.data]) return result - def get_repair_info(self, keyspace=None, table=None, since=None, # pylint: disable=too-many-arguments, too-many-positional-arguments - duration=None, local=False): + def get_repair_info( # pylint: disable=too-many-arguments, too-many-positional-arguments + self, + keyspace=None, + table=None, + since=None, + duration=None, + local=False, + ): request_url = V2RepairSchedulerRequest.repair_info_url if keyspace: request_url += "?keyspace=" + quote(keyspace) diff --git a/ecchronos-binary/src/pylib/ecchronoslib/table_printer.py b/ecchronos-binary/src/pylib/ecchronoslib/table_printer.py index 3442f7e84..47377c6ad 100644 --- a/ecchronos-binary/src/pylib/ecchronoslib/table_printer.py +++ b/ecchronos-binary/src/pylib/ecchronoslib/table_printer.py @@ -20,7 +20,7 @@ def print_schedule(schedule, max_lines, full=False): if not schedule.is_valid(): - print('Schedule not found') + print("Schedule not found") return verbose_print_format = "{0:15s}: {1}" @@ -38,8 +38,9 @@ def print_schedule(schedule, max_lines, full=False): if full: vnode_state_table = [["Start token", "End token", "Replicas", "Repaired at", "Repaired"]] - sorted_vnode_states = sorted(schedule.vnode_states, key=lambda vnode: vnode.last_repaired_at_in_ms, - reverse=True) + sorted_vnode_states = sorted( + schedule.vnode_states, key=lambda vnode: vnode.last_repaired_at_in_ms, reverse=True + ) if max_lines > -1: sorted_vnode_states = sorted_vnode_states[:max_lines] @@ -51,8 +52,13 @@ def print_schedule(schedule, max_lines, full=False): def _add_vnode_state_to_table(vnode_state, table): - entry = [vnode_state.start_token, vnode_state.end_token, ', '.join(vnode_state.replicas), - vnode_state.get_last_repaired_at(), vnode_state.repaired] + entry = [ + vnode_state.start_token, + vnode_state.end_token, + ", ".join(vnode_state.replicas), + vnode_state.get_last_repaired_at(), + vnode_state.repaired, + ] table.append(entry) @@ -60,41 +66,48 @@ def _add_vnode_state_to_table(vnode_state, table): def print_summary(schedules): status_list = [schedule.status for schedule in schedules] summary_format = "Summary: {0} completed, {1} on time, {2} blocked, {3} late, {4} overdue" - print(summary_format.format(status_list.count('COMPLETED'), - status_list.count('ON_TIME'), - status_list.count('BLOCKED'), - status_list.count('LATE'), - status_list.count('OVERDUE'))) + print( + summary_format.format( + status_list.count("COMPLETED"), + status_list.count("ON_TIME"), + status_list.count("BLOCKED"), + status_list.count("LATE"), + status_list.count("OVERDUE"), + ) + ) def print_repair_summary(repairs): status_list = [repair.status for repair in repairs] summary_format = "Summary: {0} completed, {1} in queue, {2} blocked, {3} warning, {4} error" - print(summary_format.format(status_list.count('COMPLETED'), - status_list.count('IN_QUEUE'), - status_list.count('BLOCKED'), - status_list.count('WARNING'), - status_list.count('ERROR'))) + print( + summary_format.format( + status_list.count("COMPLETED"), + status_list.count("IN_QUEUE"), + status_list.count("BLOCKED"), + status_list.count("WARNING"), + status_list.count("ERROR"), + ) + ) def print_schedules(schedules, max_lines): - schedule_table = [["Id", "Keyspace", "Table", "Status", "Repaired(%)", - "Completed at", "Next repair", "Repair type"]] + schedule_table = [ + ["Id", "Keyspace", "Table", "Status", "Repaired(%)", "Completed at", "Next repair", "Repair type"] + ] print("Snapshot as of", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) print_schedule_table(schedule_table, schedules, max_lines) print_summary(schedules) def print_repairs(repairs, max_lines=-1): - repair_table = [["Id", "Host Id", "Keyspace", "Table", "Status", "Repaired(%)", - "Completed at", "Repair type"]] + repair_table = [["Id", "Host Id", "Keyspace", "Table", "Status", "Repaired(%)", "Completed at", "Repair type"]] print_repair_table(repair_table, repairs, max_lines) print_repair_summary(repairs) def print_schedule_table(schedule_table, schedules, max_lines): - sorted_schedules = sorted(schedules, key=lambda x: (x.last_repaired_at_in_ms, x.repaired_ratio), - reverse=False) + sorted_schedules = sorted(schedules, key=lambda x: (x.last_repaired_at_in_ms, x.repaired_ratio), reverse=False) if max_lines > -1: sorted_schedules = sorted_schedules[:max_lines] @@ -114,21 +127,38 @@ def print_repair_table(repair_table, repairs, max_lines): def print_repair(repair): - repair_table = [["Id", "Host Id", "Keyspace", "Table", "Status", "Repaired(%)", - "Completed at", "Repair type"], _convert_repair(repair)] + repair_table = [ + ["Id", "Host Id", "Keyspace", "Table", "Status", "Repaired(%)", "Completed at", "Repair type"], + _convert_repair(repair), + ] table_formatter.format_table(repair_table) def _convert_repair(repair): - entry = [repair.job_id, repair.host_id, repair.keyspace, repair.table, repair.status, - repair.get_repair_percentage(), repair.get_completed_at(), repair.repair_type] + entry = [ + repair.job_id, + repair.host_id, + repair.keyspace, + repair.table, + repair.status, + repair.get_repair_percentage(), + repair.get_completed_at(), + repair.repair_type, + ] return entry def _convert_schedule(schedule): - entry = [schedule.job_id, schedule.keyspace, schedule.table, schedule.status, - schedule.get_repair_percentage(), schedule.get_last_repaired_at(), schedule.get_next_repair(), - schedule.repair_type] + entry = [ + schedule.job_id, + schedule.keyspace, + schedule.table, + schedule.status, + schedule.get_repair_percentage(), + schedule.get_last_repaired_at(), + schedule.get_next_repair(), + schedule.repair_type, + ] return entry @@ -139,8 +169,7 @@ def print_repair_info(repair_info, max_lines=-1): def print_repair_stats(repair_stats, max_lines=-1): - repair_stats_table = [["Keyspace", "Table", "Repaired (%)", - "Repair time taken"]] + repair_stats_table = [["Keyspace", "Table", "Repaired (%)", "Repair time taken"]] sorted_repair_stats = sorted(repair_stats, key=lambda x: (x.repaired_ratio, x.keyspace, x.table), reverse=False) if max_lines > -1: sorted_repair_stats = sorted_repair_stats[:max_lines] @@ -151,6 +180,10 @@ def print_repair_stats(repair_stats, max_lines=-1): def _convert_repair_stat(repair_stat): - entry = [repair_stat.keyspace, repair_stat.table, repair_stat.get_repaired_percentage(), - repair_stat.get_repair_time_taken()] + entry = [ + repair_stat.keyspace, + repair_stat.table, + repair_stat.get_repaired_percentage(), + repair_stat.get_repair_time_taken(), + ] return entry diff --git a/ecchronos-binary/src/pylib/ecchronoslib/types.py b/ecchronos-binary/src/pylib/ecchronoslib/types.py index f522559b1..3050c6b2e 100644 --- a/ecchronos-binary/src/pylib/ecchronoslib/types.py +++ b/ecchronos-binary/src/pylib/ecchronoslib/types.py @@ -43,7 +43,7 @@ def __init__(self, data): self.repaired = data["repaired"] if "repaired" in data else "False" def get_last_repaired_at(self): - return datetime.datetime.fromtimestamp(self.last_repaired_at_in_ms / 1000).strftime('%Y-%m-%d %H:%M:%S') + return datetime.datetime.fromtimestamp(self.last_repaired_at_in_ms / 1000).strftime("%Y-%m-%d %H:%M:%S") class Job(object): @@ -73,7 +73,7 @@ def __init__(self, data): def get_completed_at(self): if self.completed_at == -1: return "-" - return datetime.datetime.fromtimestamp(self.completed_at / 1000).strftime('%Y-%m-%d %H:%M:%S') + return datetime.datetime.fromtimestamp(self.completed_at / 1000).strftime("%Y-%m-%d %H:%M:%S") class Schedule(Job): @@ -87,17 +87,17 @@ def __init__(self, data): self.repair_type = data["repairType"] if "repairType" in data else "VNODE" def get_config(self): - return json.dumps(self.config).strip('{}') + return json.dumps(self.config).strip("{}") def get_next_repair(self): if self.next_repair_in_ms == -1: return "-" - return datetime.datetime.fromtimestamp(self.next_repair_in_ms / 1000).strftime('%Y-%m-%d %H:%M:%S') + return datetime.datetime.fromtimestamp(self.next_repair_in_ms / 1000).strftime("%Y-%m-%d %H:%M:%S") def get_last_repaired_at(self): if self.last_repaired_at_in_ms == -1: return "-" - return datetime.datetime.fromtimestamp(self.last_repaired_at_in_ms / 1000).strftime('%Y-%m-%d %H:%M:%S') + return datetime.datetime.fromtimestamp(self.last_repaired_at_in_ms / 1000).strftime("%Y-%m-%d %H:%M:%S") class FullSchedule(Schedule): @@ -119,12 +119,12 @@ def __init__(self, data): def get_since(self): if self.since_in_ms == -1: return "-" - return datetime.datetime.fromtimestamp(self.since_in_ms / 1000).strftime('%Y-%m-%d %H:%M:%S') + return datetime.datetime.fromtimestamp(self.since_in_ms / 1000).strftime("%Y-%m-%d %H:%M:%S") def get_to(self): if self.since_in_ms == -1: return "-" - return datetime.datetime.fromtimestamp(self.to_in_ms / 1000).strftime('%Y-%m-%d %H:%M:%S') + return datetime.datetime.fromtimestamp(self.to_in_ms / 1000).strftime("%Y-%m-%d %H:%M:%S") class RepairStats(object): diff --git a/ecchronos-binary/src/pylib/setup.py b/ecchronos-binary/src/pylib/setup.py index b8a91a545..9d4472846 100755 --- a/ecchronos-binary/src/pylib/setup.py +++ b/ecchronos-binary/src/pylib/setup.py @@ -17,11 +17,13 @@ from distutils.core import setup -setup(name='ecChronos library', - version='1.0', - description='ecChronos REST library', - author='Marcus Olsson', - author_email='marcus.olsson@ericsson.com', - url='https://github.com/Ericsson/ecchronos', - license='Apache License, Version 2.0', - packages=['ecchronoslib']) +setup( + name="ecChronos library", + version="1.0", + description="ecChronos REST library", + author="Marcus Olsson", + author_email="marcus.olsson@ericsson.com", + url="https://github.com/Ericsson/ecchronos", + license="Apache License, Version 2.0", + packages=["ecchronoslib"], +) diff --git a/ecchronos-binary/src/test/behave/ecc_step_library/common.py b/ecchronos-binary/src/test/behave/ecc_step_library/common.py index f5c7523cc..bc42539e3 100644 --- a/ecchronos-binary/src/test/behave/ecc_step_library/common.py +++ b/ecchronos-binary/src/test/behave/ecc_step_library/common.py @@ -23,10 +23,10 @@ from behave import given, then, when # pylint: disable=no-name-in-module -ID_PATTERN = r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}' -REPAIR_SUMMARY_PATTERN = r'Summary: \d+ completed, \d+ in queue, \d+ blocked, \d+ warning, \d+ error' -REPAIR_HEADER = r'| Id | Host Id | Keyspace | Table | Status | Repaired(%) | Completed at | Repair type |' -REPAIR_ROW_FORMAT_PATTERN = r'\| .* \| .* \| {0} \| {1} \| (COMPLETED|IN_QUEUE|WARNING|ERROR) \| \d+[.]\d+ \| .* \| {2} \|' # pylint: disable=line-too-long +ID_PATTERN = r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" +REPAIR_SUMMARY_PATTERN = r"Summary: \d+ completed, \d+ in queue, \d+ blocked, \d+ warning, \d+ error" +REPAIR_HEADER = r"| Id | Host Id | Keyspace | Table | Status | Repaired(%) | Completed at | Repair type |" +REPAIR_ROW_FORMAT_PATTERN = r"\| .* \| .* \| {0} \| {1} \| (COMPLETED|IN_QUEUE|WARNING|ERROR) \| \d+[.]\d+ \| .* \| {2} \|" # pylint: disable=line-too-long def table_row(template, keyspace, table, repair_type=None): @@ -36,7 +36,7 @@ def table_row(template, keyspace, table, repair_type=None): def strip_and_collapse(line): - return re.sub(' +', ' ', line.rstrip().lstrip()) + return re.sub(" +", " ", line.rstrip().lstrip()) def match_and_remove_row(rows, expected_row): @@ -56,7 +56,7 @@ def match_and_remove_row(rows, expected_row): def handle_repair_output(context): - output_data = context.out.decode('ascii').lstrip().rstrip().split('\n') + output_data = context.out.decode("ascii").lstrip().rstrip().split("\n") context.header = output_data[0:3] context.rows = output_data[3:-1] context.summary = output_data[-1:] @@ -80,19 +80,19 @@ def validate_last_table_row(rows): def get_job_id(context): - out = context.out.decode('ascii') + out = context.out.decode("ascii") job_id = re.search(ID_PATTERN, out).group(0) assert job_id, "Could not find job id matching {0} in {1}".format(ID_PATTERN, out) return job_id -@given('we have access to ecctool') +@given("we have access to ecctool") def step_init(context): assert context.config.userdata.get("ecctool") is not False assert os.path.isfile(context.config.userdata.get("ecctool")) -@then('the output should contain a valid repair summary') +@then("the output should contain a valid repair summary") def step_validate_list_repairs_contains_summary(context): assert len(context.summary) == 1, "Expecting only 1 row summary" @@ -100,17 +100,17 @@ def step_validate_list_repairs_contains_summary(context): assert re.match(REPAIR_SUMMARY_PATTERN, summary), "Faulty summary '{0}'".format(summary) -@then('the output should not contain more rows') +@then("the output should not contain more rows") def step_validate_list_rows_clear(context): validate_last_table_row(context.rows) -@then('the output should contain a valid repair header') +@then("the output should contain a valid repair header") def step_validate_list_tables_header(context): validate_header(context.header, REPAIR_HEADER) -@then('the output should contain a repair row for {keyspace}.{table} with type {repair_type}') +@then("the output should contain a repair row for {keyspace}.{table} with type {repair_type}") def step_validate_repair_row(context, keyspace, table, repair_type): expected_row = table_row(REPAIR_ROW_FORMAT_PATTERN, keyspace, table, repair_type) match_and_remove_row(context.rows, expected_row) @@ -118,10 +118,10 @@ def step_validate_repair_row(context, keyspace, table, repair_type): def get_behave_dir(): current_dir = os.path.dirname(__file__) - return os.path.abspath(os.path.join(current_dir, '../features')) + return os.path.abspath(os.path.join(current_dir, "../features")) -@given('I have a json schema {schema_name}') +@given("I have a json schema {schema_name}") def step_import_schema(context, schema_name): schema_file = os.path.join(get_behave_dir(), "schemas", "{0}.json".format(schema_name)) @@ -129,16 +129,15 @@ def step_import_schema(context, schema_name): setattr(context, schema_name, json.loads(jsonfile.read())) -@given('I use the url {url}') +@given("I use the url {url}") def step_set_url(context, url): context.url = url -@when('I send a GET request') +@when("I send a GET request") def step_send_get_request(context): assert context.url is not None - assert not context.url.startswith("http"), \ - "context.url cannot contain protocol 'http' or 'https'" + assert not context.url.startswith("http"), "context.url cannot contain protocol 'http' or 'https'" client_cert = context.config.userdata.get("ecc_client_cert") client_key = context.config.userdata.get("ecc_client_key") client_ca = context.config.userdata.get("ecc_client_ca") @@ -150,11 +149,10 @@ def step_send_get_request(context): context.response = requests.get(url, timeout=10) -@when('I send a POST request') +@when("I send a POST request") def step_send_post_request(context): assert context.url is not None - assert not context.url.startswith("http"), \ - "context.url cannot contain protocol 'http' or 'https'" + assert not context.url.startswith("http"), "context.url cannot contain protocol 'http' or 'https'" client_cert = context.config.userdata.get("ecc_client_cert") client_key = context.config.userdata.get("ecc_client_key") client_ca = context.config.userdata.get("ecc_client_ca") @@ -166,13 +164,13 @@ def step_send_post_request(context): context.response = requests.post(url, timeout=10) -@then('the response is successful') +@then("the response is successful") def step_verify_response_is_successful(context): assert context.response is not None assert context.response.status_code == 200 -@then('the response matches the json schema {schema_name}') +@then("the response matches the json schema {schema_name}") def step_verify_schema(context, schema_name): schema = getattr(context, schema_name, None) assert schema is not None @@ -182,7 +180,7 @@ def step_verify_schema(context, schema_name): validate(instance=context.json, schema=schema) -@then('the id from response is extracted for {keyspace}.{table}') +@then("the id from response is extracted for {keyspace}.{table}") def step_extract_id(context, keyspace, table): assert context.response is not None context.json = context.response.json() @@ -193,7 +191,7 @@ def step_extract_id(context, keyspace, table): assert context.id is not None -@then('the job list contains only keyspace {keyspace}') +@then("the job list contains only keyspace {keyspace}") def step_verify_job_list(context, keyspace): for obj in context.json: assert obj["keyspace"] == keyspace @@ -206,8 +204,6 @@ def run_ecctool(context, params): client_ca = context.config.userdata.get("ecc_client_ca") env = {} if client_cert and client_key and client_ca: - env = {"ECCTOOL_CERT_FILE": client_cert, - "ECCTOOL_KEY_FILE": client_key, - "ECCTOOL_CA_FILE": client_ca} + env = {"ECCTOOL_CERT_FILE": client_cert, "ECCTOOL_KEY_FILE": client_key, "ECCTOOL_CA_FILE": client_ca} context.proc = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) # pylint: disable=consider-using-with (context.out, context.err) = context.proc.communicate() diff --git a/ecchronos-binary/src/test/behave/features/environment.py b/ecchronos-binary/src/test/behave/features/environment.py index e09617d47..79c1d77b1 100644 --- a/ecchronos-binary/src/test/behave/features/environment.py +++ b/ecchronos-binary/src/test/behave/features/environment.py @@ -37,8 +37,8 @@ def before_all(context): username = context.config.userdata.get("cql_user") password = context.config.userdata.get("cql_password") - auth_provider=None - if (username and username != '') and (password and password != ''): + auth_provider = None + if (username and username != "") and (password and password != ""): auth_provider = PlainTextAuthProvider(username=username, password=password) no_tls = context.config.userdata.get("no_tls") @@ -50,7 +50,8 @@ def before_all(context): ssl_context.verify_mode = ssl.CERT_REQUIRED ssl_context.load_cert_chain( certfile=context.config.userdata.get("cql_client_cert"), - keyfile=context.config.userdata.get("cql_client_key")) + keyfile=context.config.userdata.get("cql_client_key"), + ) cluster = Cluster([cassandra_address], ssl_context=ssl_context, auth_provider=auth_provider) context.environment = Environment() context.environment.cluster = cluster @@ -60,10 +61,10 @@ def before_all(context): context.environment.host_id = host.host_id -def after_feature(context, feature): # pylint: disable=unused-argument +def after_feature(context, feature): # pylint: disable=unused-argument wait_for_local_repairs_to_complete(context) - context.environment.session.execute('TRUNCATE TABLE ecchronos.on_demand_repair_status') - context.environment.session.execute('TRUNCATE TABLE ecchronos.repair_history') + context.environment.session.execute("TRUNCATE TABLE ecchronos.on_demand_repair_status") + context.environment.session.execute("TRUNCATE TABLE ecchronos.repair_history") def wait_for_local_repairs_to_complete(context): @@ -71,14 +72,16 @@ def wait_for_local_repairs_to_complete(context): count = 0 while count < timeout_seconds: uncompleted_repairs = 0 - rows = context.environment.session.execute('SELECT host_id, job_id, status FROM ecchronos.on_demand_repair_status') + rows = context.environment.session.execute( + "SELECT host_id, job_id, status FROM ecchronos.on_demand_repair_status" + ) for row in rows: if row.host_id == context.environment.host_id: - if row.status == u'started': + if row.status == "started": uncompleted_repairs += 1 if uncompleted_repairs < 1: break count += 1 time.sleep(1) - assert count < timeout_seconds, 'All repairs did not finish in {0} seconds'.format(timeout_seconds) - print('Waiting for repairs to finish took {0} seconds'.format(count)) + assert count < timeout_seconds, "All repairs did not finish in {0} seconds".format(timeout_seconds) + print("Waiting for repairs to finish took {0} seconds".format(count)) diff --git a/ecchronos-binary/src/test/behave/features/steps/ecc_rest_repairs.py b/ecchronos-binary/src/test/behave/features/steps/ecc_rest_repairs.py index 05c53a4a9..f4bdbd95d 100644 --- a/ecchronos-binary/src/test/behave/features/steps/ecc_rest_repairs.py +++ b/ecchronos-binary/src/test/behave/features/steps/ecc_rest_repairs.py @@ -16,7 +16,7 @@ from behave import given # pylint: disable=no-name-in-module -@given('I fetch repairs with id') +@given("I fetch repairs with id") def step_fetch_repairs_with_id(context): assert context.id is not None context.url = "localhost:8080/repair-management/v2/repairs/{0}".format(context.id) diff --git a/ecchronos-binary/src/test/behave/features/steps/ecc_rest_schedules.py b/ecchronos-binary/src/test/behave/features/steps/ecc_rest_schedules.py index 7498c7f1b..ba4da2fc9 100644 --- a/ecchronos-binary/src/test/behave/features/steps/ecc_rest_schedules.py +++ b/ecchronos-binary/src/test/behave/features/steps/ecc_rest_schedules.py @@ -16,13 +16,13 @@ from behave import given # pylint: disable=no-name-in-module -@given('I fetch schedules with id and full') +@given("I fetch schedules with id and full") def step_fetch_schedule_with_id_and_full(context): assert context.id is not None context.url = "localhost:8080/repair-management/v2/schedules/{0}?full=true".format(context.id) -@given('I fetch schedules with id') +@given("I fetch schedules with id") def step_fetch_schedule_with_id(context): assert context.id is not None context.url = "localhost:8080/repair-management/v2/schedules/{0}".format(context.id) diff --git a/ecchronos-binary/src/test/behave/features/steps/ecc_spring.py b/ecchronos-binary/src/test/behave/features/steps/ecc_spring.py index 6eef563e8..51e1f0c5c 100644 --- a/ecchronos-binary/src/test/behave/features/steps/ecc_spring.py +++ b/ecchronos-binary/src/test/behave/features/steps/ecc_spring.py @@ -16,8 +16,8 @@ from behave import then # pylint: disable=no-name-in-module -@then('the status is {state}') +@then("the status is {state}") def step_verify_server_status(context, state): context.json = context.response.json() - assert context.json['status'] == state + assert context.json["status"] == state diff --git a/ecchronos-binary/src/test/behave/features/steps/ecctool_repair_info.py b/ecchronos-binary/src/test/behave/features/steps/ecctool_repair_info.py index 5c67fd0e4..bfa8a449a 100644 --- a/ecchronos-binary/src/test/behave/features/steps/ecctool_repair_info.py +++ b/ecchronos-binary/src/test/behave/features/steps/ecctool_repair_info.py @@ -17,8 +17,8 @@ from ecc_step_library.common import match_and_remove_row, validate_header, run_ecctool, table_row -REPAIR_INFO_HEADER = r'| Keyspace | Table | Repaired (%) | Repair time taken |' -REPAIR_INFO_ROW_FORMAT_PATTERN = r'\| {0} \| {1} \| \d+[.]\d+ \| .* \|' +REPAIR_INFO_HEADER = r"| Keyspace | Table | Repaired (%) | Repair time taken |" +REPAIR_INFO_ROW_FORMAT_PATTERN = r"\| {0} \| {1} \| \d+[.]\d+ \| .* \|" def run_ecc_repair_info(context, params): @@ -26,96 +26,96 @@ def run_ecc_repair_info(context, params): def handle_repair_info_output(context): - output_data = context.out.decode('ascii').lstrip().rstrip().split('\n') + output_data = context.out.decode("ascii").lstrip().rstrip().split("\n") context.time_window = output_data[0:1] context.header = output_data[1:4] context.rows = output_data[4:] -@when('we get repair-info with since {since} and duration {duration}') +@when("we get repair-info with since {since} and duration {duration}") def step_get_repair_info_with_since_and_duration(context, since, duration): - run_ecc_repair_info(context, ['--since', since, '--duration', duration]) + run_ecc_repair_info(context, ["--since", since, "--duration", duration]) handle_repair_info_output(context) -@when('we get repair-info with duration {duration} and limit {limit}') +@when("we get repair-info with duration {duration} and limit {limit}") def step_get_repair_info_with_duration_and_limit(context, duration, limit): - run_ecc_repair_info(context, ['--duration', duration, '--limit', limit]) + run_ecc_repair_info(context, ["--duration", duration, "--limit", limit]) handle_repair_info_output(context) -@when('we get repair-info with duration {duration}') +@when("we get repair-info with duration {duration}") def step_get_repair_info_with_duration(context, duration): - run_ecc_repair_info(context, ['--duration', duration]) + run_ecc_repair_info(context, ["--duration", duration]) handle_repair_info_output(context) -@when('we get repair-info with since {since} and limit {limit}') +@when("we get repair-info with since {since} and limit {limit}") def step_get_repair_info_with_since_and_limit(context, since, limit): - run_ecc_repair_info(context, ['--since', since, '--limit', limit]) + run_ecc_repair_info(context, ["--since", since, "--limit", limit]) handle_repair_info_output(context) -@when('we get repair-info with since {since}') +@when("we get repair-info with since {since}") def step_get_repair_info_with_since(context, since): - run_ecc_repair_info(context, ['--since', since]) + run_ecc_repair_info(context, ["--since", since]) handle_repair_info_output(context) -@when('we get local repair-info with since {since}') +@when("we get local repair-info with since {since}") def step_get_local_repair_info_with_since(context, since): - run_ecc_repair_info(context, ['--local', '--since', since]) + run_ecc_repair_info(context, ["--local", "--since", since]) handle_repair_info_output(context) -@when('we get repair-info for keyspace {keyspace} with since {since} and duration {duration}') +@when("we get repair-info for keyspace {keyspace} with since {since} and duration {duration}") def step_get_repair_info_for_keyspace_with_since_and_duration(context, keyspace, since, duration): - run_ecc_repair_info(context, ['--keyspace', keyspace, '--since', since, '--duration', duration]) + run_ecc_repair_info(context, ["--keyspace", keyspace, "--since", since, "--duration", duration]) handle_repair_info_output(context) -@when('we get repair-info for keyspace {keyspace} with duration {duration}') +@when("we get repair-info for keyspace {keyspace} with duration {duration}") def step_get_repair_info_for_keyspace_with_duration(context, keyspace, duration): - run_ecc_repair_info(context, ['--keyspace', keyspace, '--duration', duration]) + run_ecc_repair_info(context, ["--keyspace", keyspace, "--duration", duration]) handle_repair_info_output(context) -@when('we get repair-info for keyspace {keyspace} with since {since}') +@when("we get repair-info for keyspace {keyspace} with since {since}") def step_get_repair_info_for_keyspace_with_since(context, keyspace, since): - run_ecc_repair_info(context, ['--keyspace', keyspace, '--since', since]) + run_ecc_repair_info(context, ["--keyspace", keyspace, "--since", since]) handle_repair_info_output(context) -@when('we get repair-info for table {keyspace}.{table} with since {since} and duration {duration}') +@when("we get repair-info for table {keyspace}.{table} with since {since} and duration {duration}") def step_get_repair_info_for_table_with_since_and_duration(context, keyspace, table, since, duration): - run_ecc_repair_info(context, ['--keyspace', keyspace, '--table', table, '--since', since, '--duration', duration]) + run_ecc_repair_info(context, ["--keyspace", keyspace, "--table", table, "--since", since, "--duration", duration]) handle_repair_info_output(context) -@when('we get repair-info for table {keyspace}.{table} with duration {duration}') +@when("we get repair-info for table {keyspace}.{table} with duration {duration}") def step_get_repair_info_for_table_with_duration(context, keyspace, table, duration): - run_ecc_repair_info(context, ['--keyspace', keyspace, '--table', table, '--duration', duration]) + run_ecc_repair_info(context, ["--keyspace", keyspace, "--table", table, "--duration", duration]) handle_repair_info_output(context) -@when('we get repair-info for table {keyspace}.{table} with since {since}') +@when("we get repair-info for table {keyspace}.{table} with since {since}") def step_get_repair_info_for_table_with_since(context, keyspace, table, since): - run_ecc_repair_info(context, ['--keyspace', keyspace, '--table', table, '--since', since]) + run_ecc_repair_info(context, ["--keyspace", keyspace, "--table", table, "--since", since]) handle_repair_info_output(context) -@when('we get repair-info for table {keyspace}.{table}') +@when("we get repair-info for table {keyspace}.{table}") def step_get_repair_info_for_table(context, keyspace, table): - run_ecc_repair_info(context, ['--keyspace', keyspace, '--table', table]) + run_ecc_repair_info(context, ["--keyspace", keyspace, "--table", table]) handle_repair_info_output(context) -@then('the output should contain a valid repair-info header') +@then("the output should contain a valid repair-info header") def step_validate_repair_info_header(context): validate_header(context.header, REPAIR_INFO_HEADER) -@then('the output should contain a repair-info row for {keyspace}.{table}') +@then("the output should contain a repair-info row for {keyspace}.{table}") def step_validate_repair_info_row(context, keyspace, table): expected_row = table_row(REPAIR_INFO_ROW_FORMAT_PATTERN, keyspace, table) match_and_remove_row(context.rows, expected_row) diff --git a/ecchronos-binary/src/test/behave/features/steps/ecctool_repairs.py b/ecchronos-binary/src/test/behave/features/steps/ecctool_repairs.py index 9ee62457e..078cf22d1 100644 --- a/ecchronos-binary/src/test/behave/features/steps/ecctool_repairs.py +++ b/ecchronos-binary/src/test/behave/features/steps/ecctool_repairs.py @@ -21,51 +21,52 @@ def run_ecc_repair_status(context, params): run_ecctool(context, ["repairs"] + params) -@when('we list all repairs') +@when("we list all repairs") def step_list_repairs(context): run_ecc_repair_status(context, []) handle_repair_output(context) -@when('we list all repairs with limit of {limit}') +@when("we list all repairs with limit of {limit}") def step_list_repairs_with_limit(context, limit): - run_ecc_repair_status(context, ['--limit', limit]) + run_ecc_repair_status(context, ["--limit", limit]) handle_repair_output(context) -@when('we list all repairs for keyspace {keyspace} with a limit of {limit}') +@when("we list all repairs for keyspace {keyspace} with a limit of {limit}") def step_list_repairs_for_keyspace_with_limit(context, keyspace, limit): - run_ecc_repair_status(context, ['--keyspace', keyspace, '--limit', limit]) + run_ecc_repair_status(context, ["--keyspace", keyspace, "--limit", limit]) handle_repair_output(context) -@when('we list all repairs for keyspace {keyspace}') +@when("we list all repairs for keyspace {keyspace}") def step_list_repairs_for_keyspace(context, keyspace): - run_ecc_repair_status(context, ['--keyspace', keyspace]) + run_ecc_repair_status(context, ["--keyspace", keyspace]) handle_repair_output(context) -@when('we list repairs {keyspace}.{table} with a limit of {limit}') +@when("we list repairs {keyspace}.{table} with a limit of {limit}") def step_show_repair_with_limit(context, keyspace, table, limit): - run_ecc_repair_status(context, ['--keyspace', keyspace, '--table', table]) - run_ecc_repair_status(context, ['--id', get_job_id(context), '--limit', limit]) + run_ecc_repair_status(context, ["--keyspace", keyspace, "--table", table]) + run_ecc_repair_status(context, ["--id", get_job_id(context), "--limit", limit]) handle_repair_output(context) -@when('we list repairs for table {keyspace}.{table}') +@when("we list repairs for table {keyspace}.{table}") def step_show_repair(context, keyspace, table): - run_ecc_repair_status(context, ['--keyspace', keyspace, '--table', table]) + run_ecc_repair_status(context, ["--keyspace", keyspace, "--table", table]) handle_repair_output(context) -@when('we list repairs for hostid and table {keyspace}.{table}') +@when("we list repairs for hostid and table {keyspace}.{table}") def step_show_repair_with_nodeid(context, keyspace, table): - run_ecc_repair_status(context, ['--keyspace', keyspace, '--table', table, - '--hostid', '{0}'.format(context.environment.host_id)]) + run_ecc_repair_status( + context, ["--keyspace", keyspace, "--table", table, "--hostid", "{0}".format(context.environment.host_id)] + ) handle_repair_output(context) -@then('the output should contain {limit:d} repair rows') +@then("the output should contain {limit:d} repair rows") def step_validate_list_repairs_contains_rows_with_limit(context, limit): rows = context.rows diff --git a/ecchronos-binary/src/test/behave/features/steps/ecctool_run_repair.py b/ecchronos-binary/src/test/behave/features/steps/ecctool_run_repair.py index 5d66a19d9..7b0fabfbf 100644 --- a/ecchronos-binary/src/test/behave/features/steps/ecctool_run_repair.py +++ b/ecchronos-binary/src/test/behave/features/steps/ecctool_run_repair.py @@ -21,37 +21,37 @@ def run_ecc_run_repair(context, params): run_ecctool(context, ["run-repair"] + params) -@when('we run repair for keyspace {keyspace} and table {table}') +@when("we run repair for keyspace {keyspace} and table {table}") def step_run_repair(context, keyspace, table): - run_ecc_run_repair(context, ['--keyspace', keyspace, '--table', table]) + run_ecc_run_repair(context, ["--keyspace", keyspace, "--table", table]) handle_repair_output(context) -@when('we run repair for keyspace {keyspace}') +@when("we run repair for keyspace {keyspace}") def step_run_repair_keyspace(context, keyspace): - run_ecc_run_repair(context, ['--keyspace', keyspace]) + run_ecc_run_repair(context, ["--keyspace", keyspace]) handle_repair_output(context) -@when('we run repair') +@when("we run repair") def step_run_repair_cluster(context): run_ecc_run_repair(context, []) handle_repair_output(context) -@when('we run local repair for keyspace {keyspace} and table {table} with type {repair_type}') +@when("we run local repair for keyspace {keyspace} and table {table} with type {repair_type}") def step_run_local_repair(context, keyspace, table, repair_type): - run_ecc_run_repair(context, ['--keyspace', keyspace, '--table', table, '--local', '--repair_type', repair_type]) + run_ecc_run_repair(context, ["--keyspace", keyspace, "--table", table, "--local", "--repair_type", repair_type]) handle_repair_output(context) -@when('we run local repair for keyspace {keyspace}') +@when("we run local repair for keyspace {keyspace}") def step_run_local_repair_for_keyspace(context, keyspace): - run_ecc_run_repair(context, ['--keyspace', keyspace, '--local']) + run_ecc_run_repair(context, ["--keyspace", keyspace, "--local"]) handle_repair_output(context) -@when('we run local repair') +@when("we run local repair") def step_run_local_repair_cluster(context): - run_ecc_run_repair(context, ['--local']) + run_ecc_run_repair(context, ["--local"]) handle_repair_output(context) diff --git a/ecchronos-binary/src/test/behave/features/steps/ecctool_schedules.py b/ecchronos-binary/src/test/behave/features/steps/ecctool_schedules.py index ce1f75342..92e827331 100644 --- a/ecchronos-binary/src/test/behave/features/steps/ecctool_schedules.py +++ b/ecchronos-binary/src/test/behave/features/steps/ecctool_schedules.py @@ -15,13 +15,21 @@ import re from behave import when, then # pylint: disable=no-name-in-module -from ecc_step_library.common import get_job_id, match_and_remove_row, strip_and_collapse, validate_header, step_validate_list_rows_clear, run_ecctool, table_row # pylint: disable=line-too-long +from ecc_step_library.common import ( + get_job_id, + match_and_remove_row, + strip_and_collapse, + validate_header, + step_validate_list_rows_clear, + run_ecctool, + table_row, +) # pylint: disable=line-too-long -SCHEDULE_SUMMARY = r'Summary: \d+ completed, \d+ on time, \d+ blocked, \d+ late, \d+ overdue' +SCHEDULE_SUMMARY = r"Summary: \d+ completed, \d+ on time, \d+ blocked, \d+ late, \d+ overdue" -SCHEDULE_HEADER = r'| Id | Keyspace | Table | Status | Repaired(%) | Completed at | Next repair | Repair type |' -SNAPSHOT_HEADER = r'Snapshot as of .*' -SCHEDULE_ROW_FORMAT_PATTERN = r'\| .* \| {0} \| {1} \| (COMPLETED|ON_TIME|LATE|OVERDUE) \| \d+[.]\d+ \| .* \| {2} \|' # pylint: disable=line-too-long +SCHEDULE_HEADER = r"| Id | Keyspace | Table | Status | Repaired(%) | Completed at | Next repair | Repair type |" +SNAPSHOT_HEADER = r"Snapshot as of .*" +SCHEDULE_ROW_FORMAT_PATTERN = r"\| .* \| {0} \| {1} \| (COMPLETED|ON_TIME|LATE|OVERDUE) \| \d+[.]\d+ \| .* \| {2} \|" # pylint: disable=line-too-long def run_ecc_schedule_status(context, params): @@ -29,77 +37,77 @@ def run_ecc_schedule_status(context, params): def handle_schedule_output(context): - output_data = context.out.decode('ascii').lstrip().rstrip().split('\n') + output_data = context.out.decode("ascii").lstrip().rstrip().split("\n") context.snapshot = output_data[0:1] context.header = output_data[1:4] context.rows = output_data[4:-1] context.summary = output_data[-1:] -@when('we list all schedules') +@when("we list all schedules") def step_list_schedules(context): run_ecc_schedule_status(context, []) handle_schedule_output(context) -@when('we list all schedules with a limit of {limit}') +@when("we list all schedules with a limit of {limit}") def step_list_schedules_with_limit(context, limit): - run_ecc_schedule_status(context, ['--limit', limit]) + run_ecc_schedule_status(context, ["--limit", limit]) handle_schedule_output(context) -@when('we list all schedules for keyspace {keyspace} with a limit of {limit}') +@when("we list all schedules for keyspace {keyspace} with a limit of {limit}") def step_list_schedules_for_keyspace_with_limit(context, keyspace, limit): - run_ecc_schedule_status(context, ['--keyspace', keyspace, '--limit', limit]) + run_ecc_schedule_status(context, ["--keyspace", keyspace, "--limit", limit]) handle_schedule_output(context) -@when('we list all schedules for keyspace {keyspace}') +@when("we list all schedules for keyspace {keyspace}") def step_list_schedules_for_keyspace(context, keyspace): - run_ecc_schedule_status(context, ['--keyspace', keyspace]) + run_ecc_schedule_status(context, ["--keyspace", keyspace]) handle_schedule_output(context) -@when('we list schedules {keyspace}.{table} with a limit of {limit}') +@when("we list schedules {keyspace}.{table} with a limit of {limit}") def step_list_schedule_with_limit(context, keyspace, table, limit): - run_ecc_schedule_status(context, ['--keyspace', keyspace, '--table', table]) - run_ecc_schedule_status(context, ['--id', get_job_id(context), '--limit', limit]) + run_ecc_schedule_status(context, ["--keyspace", keyspace, "--table", table]) + run_ecc_schedule_status(context, ["--id", get_job_id(context), "--limit", limit]) handle_schedule_output(context) -@when('we list schedules for table {keyspace}.{table}') +@when("we list schedules for table {keyspace}.{table}") def step_show_schedule(context, keyspace, table): - run_ecc_schedule_status(context, ['--keyspace', keyspace, '--table', table]) + run_ecc_schedule_status(context, ["--keyspace", keyspace, "--table", table]) handle_schedule_output(context) -@then('the output should contain a schedule row for {keyspace}.{table} with type {repair_type}') +@then("the output should contain a schedule row for {keyspace}.{table} with type {repair_type}") def step_validate_list_tables_row(context, keyspace, table, repair_type): expected_row = table_row(SCHEDULE_ROW_FORMAT_PATTERN, keyspace, table, repair_type) match_and_remove_row(context.rows, expected_row) -@when('we fetch schedule {keyspace}.{table} by id') +@when("we fetch schedule {keyspace}.{table} by id") def step_show_schedule_with_id(context, keyspace, table): - run_ecc_schedule_status(context, ['--keyspace', keyspace, '--table', table]) - run_ecc_schedule_status(context, ['--id', get_job_id(context)]) - output_data = context.out.decode('ascii').lstrip().rstrip().split('\n') + run_ecc_schedule_status(context, ["--keyspace", keyspace, "--table", table]) + run_ecc_schedule_status(context, ["--id", get_job_id(context)]) + output_data = context.out.decode("ascii").lstrip().rstrip().split("\n") context.table_info = output_data[0:8] context.conf = output_data[8:9] -@when('we show schedule {keyspace}.{table} with a limit of {limit}') +@when("we show schedule {keyspace}.{table} with a limit of {limit}") def step_show_schedule_with_limit(context, keyspace, table, limit): - run_ecc_schedule_status(context, ['--keyspace', keyspace, '--table', table]) - run_ecc_schedule_status(context, ['--id', get_job_id(context), '--limit', limit, '--full']) - output_data = context.out.decode('ascii').lstrip().rstrip().split('\n') + run_ecc_schedule_status(context, ["--keyspace", keyspace, "--table", table]) + run_ecc_schedule_status(context, ["--id", get_job_id(context), "--limit", limit, "--full"]) + output_data = context.out.decode("ascii").lstrip().rstrip().split("\n") context.table_info = output_data[0:8] context.header = output_data[9:10] context.rows = output_data[12:] -@then('the output should contain a valid schedule summary') +@then("the output should contain a valid schedule summary") def step_validate_list_schedule_contains_summary(context): assert len(context.summary) == 1, "Expecting only 1 row summary" @@ -107,7 +115,7 @@ def step_validate_list_schedule_contains_summary(context): assert re.match(SCHEDULE_SUMMARY, summary), "Faulty summary '{0}'".format(summary) -@then('the output should contain a valid schedule for {keyspace}.{table} with type {repair_type}') +@then("the output should contain a valid schedule for {keyspace}.{table} with type {repair_type}") def step_validate_list_schedule_contains_rows(context, keyspace, table, repair_type): assert len(context.table_info) == 8, "Expecting 8 rows" assert len(context.conf) == 1, "Expecting 1 row" @@ -115,17 +123,17 @@ def step_validate_list_schedule_contains_rows(context, keyspace, table, repair_t step_validate_expected_show_table_header(context, keyspace, table, repair_type) -@then('the output should contain a valid snapshot header') +@then("the output should contain a valid snapshot header") def step_validate_list_snapshot_header(context): match_and_remove_row(context.snapshot, SNAPSHOT_HEADER) -@then('the output should contain a valid schedule header') +@then("the output should contain a valid schedule header") def step_validate_list_schedule_header(context): validate_header(context.header, SCHEDULE_HEADER) -@then('the output should contain {limit:d} row') +@then("the output should contain {limit:d} row") def step_validate_list_schedules_contains_rows_with_limit(context, limit): rows = context.rows @@ -137,28 +145,32 @@ def step_validate_list_schedules_contains_rows_with_limit(context, limit): step_validate_list_rows_clear(context) -@then('the expected schedule header should be for {keyspace}.{table} with type {repair_type}') +@then("the expected schedule header should be for {keyspace}.{table} with type {repair_type}") def step_validate_expected_show_table_header(context, keyspace, table, repair_type): table_info = context.table_info assert re.match("Id : .*", strip_and_collapse(table_info[0])), "Faulty Id '{0}'".format(table_info[0]) - assert strip_and_collapse( - table_info[1]) == "Keyspace : {0}".format(keyspace), "Faulty keyspace '{0}'".format(table_info[1]) - assert strip_and_collapse( - table_info[2]) == "Table : {0}".format(table), "Faulty table '{0}'".format(table_info[2]) - assert re.match("Status : (COMPLETED|ON_TIME|LATE|OVERDUE)", strip_and_collapse(table_info[3])), \ - "Faulty status '{0}'".format(table_info[3]) - assert re.match("Repaired\\(%\\) : \\d+[.]\\d+", strip_and_collapse(table_info[4])), \ - "Faulty repaired(%) '{0}'".format(table_info[4]) + assert strip_and_collapse(table_info[1]) == "Keyspace : {0}".format(keyspace), "Faulty keyspace '{0}'".format( + table_info[1] + ) + assert strip_and_collapse(table_info[2]) == "Table : {0}".format(table), "Faulty table '{0}'".format(table_info[2]) assert re.match( - "Completed at : .*", strip_and_collapse(table_info[5])), "Faulty repaired at '{0}'".format(table_info[5]) + "Status : (COMPLETED|ON_TIME|LATE|OVERDUE)", strip_and_collapse(table_info[3]) + ), "Faulty status '{0}'".format(table_info[3]) assert re.match( - "Next repair : .*", strip_and_collapse(table_info[6])), "Faulty next repair '{0}'".format(table_info[6]) + "Repaired\\(%\\) : \\d+[.]\\d+", strip_and_collapse(table_info[4]) + ), "Faulty repaired(%) '{0}'".format(table_info[4]) + assert re.match("Completed at : .*", strip_and_collapse(table_info[5])), "Faulty repaired at '{0}'".format( + table_info[5] + ) + assert re.match("Next repair : .*", strip_and_collapse(table_info[6])), "Faulty next repair '{0}'".format( + table_info[6] + ) assert re.match( - "Repair type : {0}".format(repair_type), strip_and_collapse(table_info[7])), \ - "Faulty repair type '{0}'".format(table_info[7]) + "Repair type : {0}".format(repair_type), strip_and_collapse(table_info[7]) + ), "Faulty repair type '{0}'".format(table_info[7]) -@then('the token list should contain {limit:d} rows') +@then("the token list should contain {limit:d} rows") def step_validate_token_list(context, limit): for _ in range(limit): remove_token_row(context)