Files
scylladb/test/nodetool/test_gossipinfo.py
Avi Kivity 0ae22a09d4 LICENSE: Update to version 1.1
Updated terms of non-commercial use (must be a never-customer).
2026-04-12 19:46:33 +03:00

133 lines
3.8 KiB
Python

#
# Copyright 2024-present ScyllaDB
#
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.1
#
import pytest
from copy import deepcopy
from enum import Enum, auto
from textwrap import indent
from typing import NamedTuple
from test.nodetool.rest_api_mock import expected_request
# see gms/application_state.hh
# cassandra sends the index of application_state over wire, but nodetool
# prints their names, so we have to replicate the mapping here
class State(Enum):
STATUS = 0
LOAD = auto()
SCHEMA = auto()
DC = auto()
RACK = auto()
RELEASE_VERSION = auto()
REMOVAL_COORDINATOR = auto()
INTERNAL_IP = auto()
RPC_ADDRESS = auto()
X_11_PADDIN = auto()
SEVERITY = auto()
NET_VERSION = auto()
HOST_ID = auto()
TOKENS = auto()
SUPPORTED_FEATURES = auto()
CACHE_HITRATES = auto()
SCHEMA_TABLES_VERSION = auto()
RPC_READY = auto()
VIEW_BACKLOG = auto()
SHARD_COUNT = auto()
IGNORE_MSB_BITS = auto()
CDC_GENERATION_ID = auto()
SNITCH_NAME = auto()
class VersionedValue(NamedTuple):
version: int
value: str
def endpoint_to_jsonable(e):
application_state = e['application_state']
states = []
for state, versioned_value in application_state.items():
jsonable = {'application_state': state.value,
'version': versioned_value.version,
'value': versioned_value.value}
states.append(jsonable)
j = deepcopy(e)
j['application_state'] = states
return j
def print_application_state(application_state):
lines = ''
for state, versioned_value in application_state.items():
if state != State.TOKENS:
lines += f'{state.name}:{versioned_value.value}\n'
return lines
def normalize_endpoints(endpoints):
# Cassandra's nodetool uses HashMap under the hood for collecting the map
# from state to the value of that state, and print out them by iterating
# the keys, but the order is not guaranteed to be ordered or consistent, so
# let's extract the state values out and sort them before comparing.
normalized = {}
addrs = None
state = []
def maybe_add_endpoint():
if addrs is not None:
normalized[addrs] = sorted(state)
for line in endpoints.split('\n'):
if line.startswith('/'):
maybe_add_endpoint()
addrs = line
state = []
else:
state.append(line)
maybe_add_endpoint()
return sorted(normalized)
def create_endpoint_info(i):
return {
'addrs': f'127.0.0.{i}',
'is_alive': True,
'update_time': 4096,
'generation': 1707827511,
'version': 400,
'application_state': {
State.DC: VersionedValue(17, "datacenter1"),
State.HOST_ID: VersionedValue(2, 'c850baa6-1f8f-431d-b898-09b646288c13'),
State.TOKENS: VersionedValue(24, 'secret'),
}
}
def format_endpoint_info(endpoint):
states_fmt = print_application_state(endpoint['application_state'])
return """\
/{addrs}
generation:{generation}
heartbeat:{heartbeat}
{more_info}
""".format(addrs=endpoint['addrs'],
generation=endpoint['generation'],
heartbeat=endpoint['version'],
more_info=indent(states_fmt, ' '))
@pytest.mark.parametrize("num_endpoints", [1, 2])
def test_gossipinfo(nodetool, num_endpoints):
endpoints = [create_endpoint_info(i) for i in range(num_endpoints)]
res = nodetool('gossipinfo', expected_requests=[
expected_request('GET', '/failure_detector/endpoints',
response=[endpoint_to_jsonable(endpoint) for endpoint in endpoints])
])
actual_output = res.stdout
expected_output = ''.join(format_endpoint_info(endpoint) for endpoint in endpoints)
assert normalize_endpoints(actual_output) == normalize_endpoints(expected_output)