Exemplo n.º 1
0
 def alter_broker_config(self,
                         values: dict[str, typing.Any],
                         incremental: bool,
                         *,
                         broker: typing.Optional[int] = None):
     kcl = KCL(self._redpanda)
     return kcl.alter_broker_config(values, incremental, broker)
Exemplo n.º 2
0
    def test_incremental_alter_configs(self):
        """
        Central config can also be accessed via Kafka API -- exercise that
        using `kcl`.

        :param incremental: whether to use incremental kafka config API or
                            legacy config API.
        """
        kcl = KCL(self.redpanda)

        # Redpanda only support incremental config changes: the legacy
        # AlterConfig API is a bad user experience
        incremental = True

        # Set a property by its redpanda name
        out = kcl.alter_broker_config(
            {"log_message_timestamp_type": "CreateTime"}, incremental)
        # kcl does not set an error exist status when config set fails, so must
        # read its output text to validate that calls are successful
        assert 'OK' in out

        out = kcl.alter_broker_config(
            {"log_message_timestamp_type": "LogAppendTime"}, incremental)
        assert 'OK' in out
        if incremental:
            kcl.delete_broker_config(["log_message_timestamp_type"],
                                     incremental)
            assert 'OK' in out

        # Set a property by its Kafka-interop names and values
        kafka_props = {
            "log.message.timestamp.type": ["CreateTime", "LogAppendTime"],
            "log.cleanup.policy": ["compact", "delete"],
            "log.compression.type": ["gzip", "snappy", "lz4", "zstd"],
        }
        for property, value_list in kafka_props.items():
            for value in value_list:
                out = kcl.alter_broker_config({property: value}, incremental)
                assert 'OK' in out

        # Set a nonexistent property
        out = kcl.alter_broker_config({"does_not_exist": "avalue"},
                                      incremental)
        assert 'INVALID_CONFIG' in out

        # Set a malformed property
        out = kcl.alter_broker_config(
            {"log_message_timestamp_type": "BadValue"}, incremental)
        assert 'INVALID_CONFIG' in out

        # Set a property on a named broker: should fail because this
        # interface is only for cluster-wide properties
        out = kcl.alter_broker_config(
            {"log_message_timestamp_type": "CreateTime"},
            incremental,
            broker="1")
        assert 'INVALID_CONFIG' in out
        assert "Setting broker properties on named brokers is unsupported" in out
Exemplo n.º 3
0
 def is_topic_present(name):
     kcl = KCL(self.redpanda)
     lines = kcl.list_topics().splitlines()
     self.redpanda.logger.debug(
         f"checking if topic {name} is present in {lines}")
     for l in lines:
         if l.startswith(name):
             return True
     return False
Exemplo n.º 4
0
 def test_list_groups(self):
     kcl = KCL(self.redpanda)
     kcl.produce(self.topic, "msg\n")
     kcl.consume(self.topic, n=1, group="g0")
     kcl.list_groups()
     out = kcl.list_groups()
     assert "COORDINATOR_LOAD_IN_PROGRESS" not in out
Exemplo n.º 5
0
    def test_alter_configs(self):
        """
        We only support incremental config changes.  Check that AlterConfigs requests
        are correctly handled with an 'unsupported' response.
        """

        kcl = KCL(self.redpanda)
        out = kcl.alter_broker_config(
            {"log_message_timestamp_type": "CreateTime"}, incremental=False)
        self.logger.info("AlterConfigs output: {out}")
        assert 'INVALID_CONFIG' in out
        assert "changing broker properties isn't supported via this API" in out
Exemplo n.º 6
0
    def test_querying_remote_partitions(self):
        topic = TopicSpec(redpanda_remote_read=True,
                          redpanda_remote_write=True)
        epoch_offsets = {}
        rpk = RpkTool(self.redpanda)
        self.client().create_topic(topic)
        rpk.alter_topic_config(topic.name, "redpanda.remote.read", 'true')
        rpk.alter_topic_config(topic.name, "redpanda.remote.write", 'true')

        def wait_for_topic():
            wait_until(lambda: len(list(rpk.describe_topic(topic.name))) > 0,
                       30,
                       backoff_sec=2)

        # restart whole cluster 6 times to trigger term rolls
        for i in range(0, 6):
            wait_for_topic()
            produce_until_segments(
                redpanda=self.redpanda,
                topic=topic.name,
                partition_idx=0,
                count=2 * i,
            )
            res = list(rpk.describe_topic(topic.name))
            epoch_offsets[res[0].leader_epoch] = res[0].high_watermark
            self.redpanda.restart_nodes(self.redpanda.nodes)

        self.logger.info(f"ledear epoch high watermarks: {epoch_offsets}")

        wait_for_topic()

        rpk.alter_topic_config(topic.name, TopicSpec.PROPERTY_RETENTION_BYTES,
                               OffsetForLeaderEpochArchivalTest.segment_size)
        wait_for_segments_removal(redpanda=self.redpanda,
                                  topic=topic.name,
                                  partition_idx=0,
                                  count=7)
        kcl = KCL(self.redpanda)

        for epoch, offset in epoch_offsets.items():
            self.logger.info(f"querying partition epoch {epoch} end offsets")
            epoch_end_offset = kcl.offset_for_leader_epoch(
                topics=topic.name, leader_epoch=epoch)[0].epoch_end_offset
            self.logger.info(
                f"epoch {epoch} end_offset: {epoch_end_offset}, expected offset: {offset}"
            )
            assert epoch_end_offset == offset
Exemplo n.º 7
0
    def test_list_groups_has_no_duplicates(self):
        """
        Reproducer for:
        https://github.com/vectorizedio/redpanda/issues/2528
        """
        kcl = KCL(self.redpanda)
        kcl.produce(self.topic, "msg\n")
        # create 4 groups
        kcl.consume(self.topic, n=1, group="g0")
        kcl.consume(self.topic, n=1, group="g1")
        kcl.consume(self.topic, n=1, group="g2")
        kcl.consume(self.topic, n=1, group="g3")
        # transfer kafka_internal/group/0 leadership across the nodes to trigger
        # group state recovery on each node
        for n in self.redpanda.nodes:
            self._transfer_with_retry(namespace="kafka_internal",
                                      topic="group",
                                      partition=0,
                                      target_id=self.redpanda.idx(n))

        # assert that there are no duplicates in
        def _list_groups():
            out = kcl.list_groups()
            groups = []
            for l in out.splitlines():
                # skip header line
                if l.startswith("BROKER"):
                    continue
                parts = l.split()
                groups.append({'node_id': parts[0], 'group': parts[1]})
            return groups

        def _check_no_duplicates():
            groups = _list_groups()
            self.redpanda.logger.debug(f"groups: {groups}")
            return len(groups) == 4

        wait_until(_check_no_duplicates,
                   10,
                   backoff_sec=2,
                   err_msg="found persistent duplicates in groups listing")
Exemplo n.º 8
0
    def simple_fetch_handler_fairness_test(self, type):
        """
        Testing if when fetching from single node all partitions are 
        returned in round robin fashion
        """
        def multiple_topics(count):
            return [
                TopicSpec(partition_count=1, replication_factor=1)
                for _ in range(count)
            ]

        def multiple_partitions(count):
            return [TopicSpec(partition_count=count, replication_factor=1)]

        number_of_partitions = 32
        records_per_partition = 100
        topics = []

        if type == 'multiple-topics':
            topics = multiple_topics(number_of_partitions)
        else:
            topics = multiple_partitions(number_of_partitions)

        # create topics
        self.client().create_topic(specs=topics)
        self.redpanda.logger.info(f"topics: {topics}")
        rpk = RpkTool(self.redpanda)

        # publish 10 messages to each topic/partition

        for s in topics:
            t = s.name
            for p in range(0, s.partition_count):
                for i in range(0, records_per_partition):
                    self.redpanda.logger.info(f"producing to : {t}/{p}")
                    rpk.produce(t,
                                f"k.{t}.{p}.{i}",
                                f"p.{t}.{p}.{i}",
                                partition=p)

        # configure kcl to fetch at most 1 byte in single fetch request,
        # this way we should receive exactly one record per each partition

        consumed_frequency = defaultdict(lambda: 0)
        kcl = KCL(self.redpanda)

        expected_frequency = records_per_partition / 2
        # issue single kcl command that will generate multiple fetch requests
        # in single fetch session, it should include single partition in each
        # fetch response.
        to_consume = int(number_of_partitions * expected_frequency)
        consumed = kcl.consume(topic="topic-.*",
                               n=to_consume,
                               regex=True,
                               fetch_max_bytes=1,
                               group="test-gr-1")

        for c in consumed.split():
            parts = c.split('.')
            consumed_frequency[(parts[1], parts[2])] += 1

        for p, freq in consumed_frequency.items():
            self.redpanda.logger.info(f"consumed {freq} messages from: {p}")
            # assert that frequency is in expected range
            assert freq <= expected_frequency + 0.1 * expected_frequency
            assert freq >= expected_frequency - 0.1 * expected_frequency
    def test_offset_for_leader_epoch(self):
        replication_factors = [1, 3, 5]
        cleanup_policies = [
            TopicSpec.CLEANUP_COMPACT, TopicSpec.CLEANUP_DELETE
        ]
        topics = []

        for i in range(0, 10):
            topics.append(
                TopicSpec(
                    partition_count=random.randint(1, 50),
                    replication_factor=random.choice(replication_factors),
                    cleanup_policy=random.choice(cleanup_policies)))

        topic_names = [t.name for t in topics]
        # create test topics
        self.client().create_topic(topics)
        kcl = KCL(self.redpanda)
        for t in topics:
            self._produce(t.name, 20)

        def list_offsets_map():
            offsets_map = {}
            offsets = kcl.list_offsets(topic_names)
            self.logger.info(f"offsets_list: {offsets}")
            for p in offsets:
                offsets_map[(p.topic, p.partition)] = int(p.end_offset)
            self.logger.info(f"offsets_map: {offsets_map}")
            return offsets_map

        initial_offsets = list_offsets_map()

        leader_epoch_offsets = kcl.offset_for_leader_epoch(topics=topic_names,
                                                           leader_epoch=1)

        for o in leader_epoch_offsets:
            assert initial_offsets[(o.topic,
                                    o.partition)] == o.epoch_end_offset

        # restart all the nodes to force leader election,
        # increase start timeout as partition count may get large
        self.redpanda.restart_nodes(self.redpanda.nodes, start_timeout=30)
        # produce more data
        for t in topics:
            self._produce(t.name, 20)

        # check epoch end offsets for term 1
        leader_epoch_offsets = kcl.offset_for_leader_epoch(topics=topic_names,
                                                           leader_epoch=1)

        for o in leader_epoch_offsets:
            assert initial_offsets[(o.topic,
                                    o.partition)] == o.epoch_end_offset

        last_offsets = list_offsets_map()
        rpk = RpkTool(self.redpanda)
        for t in topics:
            tp_desc = rpk.describe_topic(t.name)
            for p in tp_desc:
                for o in kcl.offset_for_leader_epoch(
                        topics=f"{t.name}:{p.id}",
                        leader_epoch=p.leader_epoch,
                        current_leader_epoch=p.leader_epoch):
                    assert last_offsets[(o.topic,
                                         o.partition)] == o.epoch_end_offset

        # test returning unknown leader epoch error, we use large leader epoch value

        leader_epoch_offsets = kcl.offset_for_leader_epoch(
            topics=topic_names, leader_epoch=1, current_leader_epoch=1000)

        for o in leader_epoch_offsets:
            assert o.error is not None and "UNKNOWN_LEADER_EPOCH" in o.error
Exemplo n.º 10
0
    def test_migrating_consume_offsets(self, failures, cpus):
        '''
        Validates correctness while executing consumer offsets migration
        '''

        # set redpanda logical version to value without __consumer_offsets support
        self.redpanda = RedpandaService(
            self.test_context,
            5,
            resource_settings=ResourceSettings(num_cpus=cpus),
            extra_rp_conf={
                "group_topic_partitions": 16,
                "default_topic_replications": 3,
            },
            environment={"__REDPANDA_LOGICAL_VERSION": 1})

        self.redpanda.start()
        self._client = DefaultClient(self.redpanda)
        # set of failure suppressed nodes - required to make restarts deterministic
        suppressed = set()

        def failure_injector_loop():
            f_injector = FailureInjector(self.redpanda)
            while failures:
                f_type = random.choice(FailureSpec.FAILURE_TYPES)
                length = 0
                node = random.choice(self.redpanda.nodes)
                while self.redpanda.idx(node) in suppressed:
                    node = random.choice(self.redpanda.nodes)

                # allow suspending any node
                if f_type == FailureSpec.FAILURE_SUSPEND:
                    length = random.randint(
                        1,
                        ConsumerOffsetsMigrationTest.max_suspend_duration_sec)

                f_injector.inject_failure(
                    FailureSpec(node=node, type=f_type, length=length))

                delay = random.randint(
                    ConsumerOffsetsMigrationTest.min_inter_failure_time_sec,
                    ConsumerOffsetsMigrationTest.max_inter_failure_time_sec)
                self.redpanda.logger.info(
                    f"waiting {delay} seconds before next failure")
                time.sleep(delay)

        if failures:
            finjector_thread = threading.Thread(target=failure_injector_loop,
                                                args=())
            finjector_thread.daemon = True
            finjector_thread.start()
        spec = TopicSpec(partition_count=6, replication_factor=3)
        self.client().create_topic(spec)
        self.topic = spec.name

        self.start_producer(1, throughput=5000)
        self.start_consumer(1)
        self.await_startup()

        def cluster_is_stable():
            admin = Admin(self.redpanda)
            brokers = admin.get_brokers()
            if len(brokers) < 3:
                return False

            for b in brokers:
                self.logger.debug(f"broker:  {b}")
                if not (b['is_alive'] and 'disk_space' in b):
                    return False

            return True

        kcl = KCL(self.redpanda)

        def _group_present():
            return len(kcl.list_groups().splitlines()) > 1

        # make sure that group is there
        wait_until(_group_present, 10, 1)

        # check that consumer offsets topic is not present
        topics = set(kcl.list_topics())

        assert "__consumer_offsets" not in topics

        # enable consumer offsets support
        self.redpanda.set_environment({"__REDPANDA_LOGICAL_VERSION": 2})
        for n in self.redpanda.nodes:
            id = self.redpanda.idx(n)
            suppressed.add(id)
            self.redpanda.restart_nodes(n, stop_timeout=60)
            suppressed.remove(id)
            # wait for leader balancer to start evening out leadership
            wait_until(cluster_is_stable, 90, backoff_sec=2)

        def _consumer_offsets_present():
            try:
                partitions = list(
                    self.client().describe_topic("__consumer_offsets"))
                return len(partitions) > 0
            except:
                return False

        wait_until(_consumer_offsets_present, timeout_sec=90, backoff_sec=3)

        self.run_validation(min_records=100000,
                            producer_timeout_sec=300,
                            consumer_timeout_sec=180)
Exemplo n.º 11
0
    def test_cluster_is_available_during_upgrade_without_group_topic(self):
        '''
        Validates that cluster is available and healthy during 
        upgrade when `kafka_internal::group` topic is not present
        '''

        # set redpanda logical version to value without __consumer_offsets support
        self.redpanda = RedpandaService(
            self.test_context,
            5,
            extra_rp_conf={
                "group_topic_partitions": 16,
                "default_topic_replications": 3,
            },
            environment={"__REDPANDA_LOGICAL_VERSION": 1})

        self.redpanda.start()
        self._client = DefaultClient(self.redpanda)

        spec = TopicSpec(partition_count=6, replication_factor=3)
        self.client().create_topic(spec)
        self.topic = spec.name

        def cluster_is_stable():
            admin = Admin(self.redpanda)
            brokers = admin.get_brokers()
            if len(brokers) < 3:
                return False

            for b in brokers:
                self.logger.debug(f"broker:  {b}")
                if not (b['is_alive'] and 'disk_space' in b):
                    return False

            return True

        def node_stopped(node_id):
            admin = Admin(self.redpanda)
            brokers = admin.get_brokers()

            for b in brokers:
                self.logger.debug(f"broker:  {b}")
                if b['node_id'] == node_id:
                    return b['is_alive'] == False

            return False

        kcl = KCL(self.redpanda)

        # check that consumer offsets topic is not present
        topics = set(kcl.list_topics())

        assert "__consumer_offsets" not in topics

        # enable consumer offsets support
        self.redpanda.set_environment({"__REDPANDA_LOGICAL_VERSION": 2})

        def get_raft0_follower():
            ctrl = self.redpanda.controller
            node = random.choice(self.redpanda.nodes)
            while self.redpanda.idx(node) == self.redpanda.idx(ctrl):
                node = random.choice(self.redpanda.nodes)

            return node

        # restart node that is not controller
        n = get_raft0_follower()
        self.logger.info(f"restarting node {n.account.hostname}")
        self.redpanda.stop_node(n, timeout=60)
        # wait for leader balancer to start evening out leadership
        wait_until(lambda: node_stopped(self.redpanda.idx(n)),
                   90,
                   backoff_sec=2)
        self.redpanda.start_node(n)
        wait_until(cluster_is_stable, 90, backoff_sec=2)
Exemplo n.º 12
0
 def delete_broker_config(self, keys: list[str], incremental: bool):
     kcl = KCL(self._redpanda)
     return kcl.delete_broker_config(keys, incremental)