You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by hg...@apache.org on 2025/10/28 10:33:56 UTC
(iggy) 01/01: test
This is an automated email from the ASF dual-hosted git repository.
hgruszecki pushed a commit to branch io_uring_tpc_squashmerge
in repository https://gitbox.apache.org/repos/asf/iggy.git
commit ba179728314534b50d7a6d3ce97f1c36dd8c5926
Author: Hubert Gruszecki <h....@gmail.com>
AuthorDate: Tue Oct 28 11:31:52 2025 +0100
test
---
.github/workflows/ci-publish-go.yml | 67 +
Cargo.lock | 1131 +++++++--------
Cargo.toml | 48 +-
DEPENDENCIES.md | 5 +-
.../iggy-pvc.yaml => Dockerfile.connectors.ci | 53 +-
...c.yaml => Dockerfile.connectors.ci.dockerignore | 39 +-
...iggy-pvc.yaml => Dockerfile.mcp.ci.dockerignore | 39 +-
bdd/docker-compose.yml | 19 +-
bdd/go/tests/tcp_test/stream_steps.go | 1 -
bdd/python/tests/test_basic_messaging.py | 35 +-
bdd/rust/tests/steps/streams.rs | 16 +-
bdd/rust/tests/steps/topics.rs | 16 +-
bdd/scenarios/basic_messaging.feature | 12 +-
core/ai/mcp/src/service/mod.rs | 9 +-
core/ai/mcp/src/service/requests.rs | 12 +-
core/bench/report/src/types/transport.rs | 3 +
.../bench/src/actors/consumer/client/high_level.rs | 9 +-
core/bench/src/actors/consumer/client/interface.rs | 2 +-
core/bench/src/actors/consumer/client/low_level.rs | 17 +-
.../actors/consumer/typed_benchmark_consumer.rs | 2 +-
core/bench/src/actors/producer/client/interface.rs | 2 +-
core/bench/src/actors/producer/client/low_level.rs | 8 +-
.../actors/producer/typed_benchmark_producer.rs | 2 +-
.../typed_banchmark_producing_consumer.rs | 4 +-
core/bench/src/args/common.rs | 12 +-
core/bench/src/args/defaults.rs | 3 +-
core/bench/src/args/transport.rs | 42 +
core/bench/src/benchmarks/benchmark.rs | 30 +-
core/bench/src/benchmarks/common.rs | 50 +-
core/bench/src/utils/client_factory.rs | 4 +
core/bench/src/utils/mod.rs | 1 +
core/bench/src/utils/server_starter.rs | 18 +
.../create_consumer_group.rs | 15 +-
.../src/cli/binary_streams/create_stream.rs | 11 +-
.../src/cli/binary_topics/create_topic.rs | 31 +-
.../client/binary_clients/consumer_group_client.rs | 1 -
.../src/client/binary_clients/stream_client.rs | 6 +-
.../src/client/binary_clients/topic_client.rs | 1 -
.../src/client/binary_consumer_groups/mod.rs | 2 -
.../src/client/binary_streams/mod.rs | 7 +-
.../src/client/binary_topics/mod.rs | 2 -
core/binary_protocol/src/utils/mapper.rs | 2 +-
core/cli/src/args/consumer_offset.rs | 4 +-
core/cli/src/args/message.rs | 4 +-
core/cli/src/args/permissions/mod.rs | 2 +-
core/cli/src/args/permissions/stream.rs | 4 +-
core/cli/src/args/permissions/topic.rs | 4 +-
core/cli/src/main.rs | 6 +-
core/common/Cargo.toml | 1 +
core/common/src/certificates.rs | 1 +
.../consumer_groups/create_consumer_group.rs | 47 +-
.../consumer_offsets/delete_consumer_offset.rs | 2 +-
.../consumer_offsets/get_consumer_offset.rs | 37 +-
.../consumer_offsets/store_consumer_offset.rs | 41 +-
core/common/src/commands/messages/poll_messages.rs | 46 +-
core/common/src/commands/streams/create_stream.rs | 41 +-
core/common/src/commands/topics/create_topic.rs | 61 +-
core/common/src/commands/users/defaults.rs | 2 +-
core/common/src/error/iggy_error.rs | 84 +-
core/common/src/lib.rs | 5 +-
core/common/src/locking/fast_async_lock.rs | 4 +-
core/common/src/locking/mod.rs | 10 +-
core/common/src/locking/tokio_lock.rs | 7 +-
core/common/src/types/args/mod.rs | 89 +-
.../configuration/auth_config/connection_string.rs | 6 +-
core/common/src/types/configuration/mod.rs | 1 +
core/common/src/types/configuration/transport.rs | 6 +
.../types/configuration/websocket_config/mod.rs | 25 +-
.../websocket_config/websocket_client_config.rs | 192 +++
.../websocket_client_config_builder.rs | 158 +++
.../websocket_client_reconnection_config.rs | 61 +
.../websocket_connection_string_options.rs | 209 +++
core/common/src/types/confirmation/mod.rs | 54 -
core/common/src/types/identifier/mod.rs | 9 +-
core/common/src/types/mod.rs | 1 -
.../src/types/permissions/permissions_global.rs | 12 +-
core/common/src/utils/crypto.rs | 8 +-
core/configs/server.toml | 36 +-
core/integration/Cargo.toml | 1 +
core/integration/src/lib.rs | 3 +
core/integration/src/quic_client.rs | 2 +
core/integration/src/test_mcp_server.rs | 94 +-
core/integration/src/test_server.rs | 151 +-
.../src/{quic_client.rs => websocket_client.rs} | 22 +-
core/integration/tests/archiver/disk.rs | 129 --
core/integration/tests/archiver/mod.rs | 59 -
core/integration/tests/archiver/s3.rs | 38 -
.../tests/cli/client/test_client_get_command.rs | 2 +-
core/integration/tests/cli/common/mod.rs | 4 +-
.../test_consumer_group_create_command.rs | 17 +-
.../test_consumer_group_delete_command.rs | 12 +-
.../test_consumer_group_get_command.rs | 14 +-
.../test_consumer_group_list_command.rs | 194 +--
.../test_consumer_offset_get_command.rs | 163 +--
.../test_consumer_offset_set_command.rs | 11 +-
.../tests/cli/context/test_context_applied.rs | 9 +-
.../tests/cli/general/test_help_command.rs | 17 +-
.../cli/message/test_message_flush_command.rs | 128 +-
.../tests/cli/message/test_message_poll_command.rs | 164 +--
.../message/test_message_poll_to_file_command.rs | 87 +-
.../tests/cli/message/test_message_send_command.rs | 256 ++--
.../message/test_message_send_from_file_command.rs | 140 +-
.../cli/partition/test_partition_create_command.rs | 94 +-
.../cli/partition/test_partition_delete_command.rs | 103 +-
.../tests/cli/stream/test_stream_create_command.rs | 27 +-
.../tests/cli/stream/test_stream_delete_command.rs | 16 +-
.../tests/cli/stream/test_stream_get_command.rs | 24 +-
.../tests/cli/stream/test_stream_list_command.rs | 14 +-
.../tests/cli/stream/test_stream_purge_command.rs | 29 +-
.../tests/cli/stream/test_stream_update_command.rs | 25 +-
.../tests/cli/system/test_me_command.rs | 5 +
.../tests/cli/system/test_snapshot_cmd.rs | 2 +-
.../tests/cli/system/test_stats_command.rs | 12 +-
.../tests/cli/topic/test_topic_create_command.rs | 45 +-
.../tests/cli/topic/test_topic_delete_command.rs | 35 +-
.../tests/cli/topic/test_topic_get_command.rs | 39 +-
.../tests/cli/topic/test_topic_list_command.rs | 29 +-
.../tests/cli/topic/test_topic_purge_command.rs | 49 +-
.../tests/cli/topic/test_topic_update_command.rs | 60 +-
.../tests/cli/user/test_user_create_command.rs | 6 +-
.../tests/cli/user/test_user_delete_command.rs | 4 +-
.../tests/cli/user/test_user_get_command.rs | 8 +-
.../cli/user/test_user_permissions_command.rs | 8 +-
core/integration/tests/config_provider/mod.rs | 43 +-
core/integration/tests/connectors/mod.rs | 3 +-
.../data_integrity/verify_after_server_restart.rs | 94 +-
core/integration/tests/mcp/mod.rs | 27 +-
core/integration/tests/mod.rs | 1 -
core/integration/tests/sdk/producer/background.rs | 30 +-
core/integration/tests/sdk/producer/mod.rs | 14 +-
core/integration/tests/server/cg.rs | 3 +-
.../tests/server/concurrent_addition.rs | 128 ++
core/integration/tests/server/general.rs | 18 +-
core/integration/tests/server/mod.rs | 20 +-
.../tests/server/scenarios/concurrent_scenario.rs | 759 +++++++++++
.../scenarios/consumer_group_join_scenario.rs | 66 +-
...h_multiple_clients_polling_messages_scenario.rs | 69 +-
...with_single_client_polling_messages_scenario.rs | 48 +-
.../server/scenarios/create_message_payload.rs | 20 +-
.../server/scenarios/delete_segments_scenario.rs | 46 +-
.../tests/server/scenarios/encryption_scenario.rs | 50 +-
.../server/scenarios/message_headers_scenario.rs | 19 +-
.../server/scenarios/message_size_scenario.rs | 20 +-
core/integration/tests/server/scenarios/mod.rs | 28 +-
.../scenarios/stream_size_validation_scenario.rs | 3 +-
.../tests/server/scenarios/system_scenario.rs | 255 ++--
.../tests/server/scenarios/tcp_tls_scenario.rs | 26 +-
.../tests/server/scenarios/user_scenario.rs | 12 +-
...p_tls_scenario.rs => websocket_tls_scenario.rs} | 37 +-
core/integration/tests/server/specific.rs | 49 +-
core/integration/tests/state/file.rs | 25 +-
core/integration/tests/state/mod.rs | 11 +-
core/integration/tests/state/system.rs | 36 +-
.../tests/streaming/common/test_setup.rs | 92 +-
.../integration/tests/streaming/consumer_offset.rs | 83 --
core/integration/tests/streaming/get_by_offset.rs | 144 +-
.../tests/streaming/get_by_timestamp.rs | 97 +-
core/integration/tests/streaming/messages.rs | 164 ---
core/integration/tests/streaming/mod.rs | 132 +-
core/integration/tests/streaming/partition.rs | 226 ---
core/integration/tests/streaming/segment.rs | 704 ----------
core/integration/tests/streaming/snapshot.rs | 6 +-
core/integration/tests/streaming/stream.rs | 189 ---
core/integration/tests/streaming/system.rs | 134 --
core/integration/tests/streaming/topic.rs | 270 ----
core/integration/tests/streaming/topic_messages.rs | 247 ----
core/sdk/Cargo.toml | 2 +
core/sdk/src/client_provider.rs | 49 +-
core/sdk/src/client_wrappers/binary_client.rs | 4 +
.../src/client_wrappers/binary_cluster_client.rs | 1 +
.../binary_consumer_group_client.rs | 40 +-
.../binary_consumer_offset_client.rs | 15 +
.../src/client_wrappers/binary_message_client.rs | 23 +
.../src/client_wrappers/binary_partition_client.rs | 10 +
.../binary_personal_access_token_client.rs | 8 +
.../src/client_wrappers/binary_segment_client.rs | 5 +
.../src/client_wrappers/binary_stream_client.rs | 20 +-
.../src/client_wrappers/binary_system_client.rs | 7 +
.../sdk/src/client_wrappers/binary_topic_client.rs | 35 +-
core/sdk/src/client_wrappers/binary_user_client.rs | 19 +
core/sdk/src/client_wrappers/client_wrapper.rs | 2 +
core/sdk/src/clients/binary_cluster.rs | 4 +-
core/sdk/src/clients/binary_consumer_group.rs | 8 +-
core/sdk/src/clients/binary_consumer_offset.rs | 2 +-
core/sdk/src/clients/binary_message.rs | 2 +-
core/sdk/src/clients/binary_partitions.rs | 2 +-
.../src/clients/binary_personal_access_tokens.rs | 2 +-
core/sdk/src/clients/binary_segments.rs | 2 +-
core/sdk/src/clients/binary_streams.rs | 14 +-
core/sdk/src/clients/binary_system.rs | 2 +-
core/sdk/src/clients/binary_topics.rs | 4 +-
core/sdk/src/clients/binary_users.rs | 2 +-
core/sdk/src/clients/client.rs | 21 +-
core/sdk/src/clients/client_builder.rs | 73 +-
core/sdk/src/clients/consumer.rs | 25 +-
core/sdk/src/clients/consumer_builder.rs | 6 +-
core/sdk/src/clients/producer.rs | 13 +-
core/sdk/src/clients/producer_builder.rs | 6 +-
core/sdk/src/http/binary_consumer_groups.rs | 2 -
core/sdk/src/http/binary_streams.rs | 7 +-
core/sdk/src/http/binary_topics.rs | 2 -
core/sdk/src/http/http_client.rs | 6 +-
core/sdk/src/lib.rs | 1 +
core/sdk/src/prelude.rs | 4 +-
.../src/stream_builder/build/build_stream_topic.rs | 7 +-
core/sdk/src/tcp/tcp_client.rs | 5 +-
core/sdk/src/tcp/tcp_tls_connection_stream.rs | 2 +-
core/sdk/src/tcp/tcp_tls_verifier.rs | 13 +-
.../channels/commands => sdk/src/websocket}/mod.rs | 11 +-
core/sdk/src/websocket/websocket_client.rs | 665 +++++++++
.../src/websocket/websocket_connection_stream.rs | 213 +++
.../websocket_stream.rs} | 23 +-
core/sdk/src/websocket/websocket_stream_kind.rs | 59 +
.../websocket/websocket_tls_connection_stream.rs | 218 +++
core/server/Cargo.toml | 33 +-
core/server/server.http | 2 +-
core/server/src/archiver/disk.rs | 99 --
core/server/src/archiver/mod.rs | 136 --
core/server/src/archiver/s3.rs | 194 ---
core/server/src/binary/command.rs | 5 +-
.../cluster/get_cluster_metadata_handler.rs | 10 +-
.../create_consumer_group_handler.rs | 57 +-
.../delete_consumer_group_handler.rs | 69 +-
.../consumer_groups/get_consumer_group_handler.rs | 41 +-
.../consumer_groups/get_consumer_groups_handler.rs | 44 +-
.../consumer_groups/join_consumer_group_handler.rs | 11 +-
.../leave_consumer_group_handler.rs | 14 +-
.../delete_consumer_offset_handler.rs | 8 +-
.../get_consumer_offset_handler.rs | 10 +-
.../store_consumer_offset_handler.rs | 9 +-
.../messages/flush_unsaved_buffer_handler.rs | 20 +-
.../handlers/messages/poll_messages_handler.rs | 72 +-
.../handlers/messages/send_messages_handler.rs | 58 +-
.../partitions/create_partitions_handler.rs | 48 +-
.../partitions/delete_partitions_handler.rs | 41 +-
.../create_personal_access_token_handler.rs | 23 +-
.../delete_personal_access_token_handler.rs | 19 +-
.../get_personal_access_tokens_handler.rs | 11 +-
.../login_with_personal_access_token_handler.rs | 9 +-
.../handlers/segments/delete_segments_handler.rs | 88 +-
.../handlers/streams/create_stream_handler.rs | 121 +-
.../handlers/streams/delete_stream_handler.rs | 38 +-
.../binary/handlers/streams/get_stream_handler.rs | 39 +-
.../binary/handlers/streams/get_streams_handler.rs | 27 +-
.../handlers/streams/purge_stream_handler.rs | 20 +-
.../handlers/streams/update_stream_handler.rs | 28 +-
.../binary/handlers/system/get_client_handler.rs | 10 +-
.../binary/handlers/system/get_clients_handler.rs | 10 +-
.../src/binary/handlers/system/get_me_handler.rs | 11 +-
.../src/binary/handlers/system/get_snapshot.rs | 8 +-
.../binary/handlers/system/get_stats_handler.rs | 52 +-
.../src/binary/handlers/system/ping_handler.rs | 11 +-
.../binary/handlers/topics/create_topic_handler.rs | 191 ++-
.../binary/handlers/topics/delete_topic_handler.rs | 49 +-
.../binary/handlers/topics/get_topic_handler.rs | 39 +-
.../binary/handlers/topics/get_topics_handler.rs | 35 +-
.../binary/handlers/topics/purge_topic_handler.rs | 21 +-
.../binary/handlers/topics/update_topic_handler.rs | 72 +-
.../handlers/users/change_password_handler.rs | 25 +-
.../binary/handlers/users/create_user_handler.rs | 168 ++-
.../binary/handlers/users/delete_user_handler.rs | 23 +-
.../src/binary/handlers/users/get_user_handler.rs | 11 +-
.../src/binary/handlers/users/get_users_handler.rs | 11 +-
.../binary/handlers/users/login_user_handler.rs | 26 +-
.../binary/handlers/users/logout_user_handler.rs | 13 +-
.../handlers/users/update_permissions_handler.rs | 22 +-
.../binary/handlers/users/update_user_handler.rs | 26 +-
core/server/src/binary/handlers/utils.rs | 15 +-
core/server/src/binary/mapper.rs | 205 +--
core/server/src/binary/sender.rs | 61 +-
core/server/src/bootstrap.rs | 709 ++++++++++
core/server/src/channels/commands/archive_state.rs | 139 --
.../commands/clean_personal_access_tokens.rs | 137 --
.../src/channels/commands/maintain_messages.rs | 555 --------
core/server/src/channels/commands/print_sysinfo.rs | 123 --
core/server/src/channels/commands/save_messages.rs | 118 --
.../src/channels/commands/verify_heartbeats.rs | 150 --
core/server/src/channels/handler.rs | 46 -
core/server/src/channels/server_command.rs | 40 -
.../src/compat/index_rebuilding/index_rebuilder.rs | 49 +-
core/server/src/configs/cluster.rs | 3 +-
core/server/src/configs/config_provider.rs | 442 ++++++
core/server/src/configs/defaults.rs | 78 +-
core/server/src/configs/displays.rs | 36 +-
core/server/src/configs/mod.rs | 3 +
core/server/src/configs/quic.rs | 9 +
core/server/src/configs/server.rs | 102 +-
core/server/src/configs/sharding.rs | 116 ++
core/server/src/configs/system.rs | 101 +-
core/server/src/configs/validators.rs | 104 +-
core/server/src/configs/websocket.rs | 107 ++
core/server/src/http/consumer_groups.rs | 280 +++-
core/server/src/http/consumer_offsets.rs | 30 +-
core/server/src/http/diagnostics.rs | 7 +-
core/server/src/http/error.rs | 5 -
core/server/src/http/http_server.rs | 150 +-
core/server/src/http/http_shard_wrapper.rs | 294 ++++
core/server/src/http/jwt/cleaner.rs | 41 -
core/server/src/http/jwt/jwt_manager.rs | 8 +-
core/server/src/http/jwt/mod.rs | 1 -
core/server/src/http/jwt/storage.rs | 53 +-
core/server/src/http/mapper.rs | 297 ++--
core/server/src/http/messages.rs | 81 +-
core/server/src/http/metrics.rs | 2 +-
core/server/src/http/mod.rs | 10 +-
core/server/src/http/partitions.rs | 110 +-
core/server/src/http/personal_access_tokens.rs | 75 +-
core/server/src/http/shared.rs | 4 +-
core/server/src/http/streams.rs | 350 +++--
core/server/src/http/system.rs | 61 +-
core/server/src/http/topics.rs | 466 +++++--
core/server/src/http/users.rs | 367 +++--
core/server/src/io/fs_locks.rs | 48 +
core/server/src/io/fs_utils.rs | 105 ++
core/server/src/io/mod.rs | 3 +
core/server/src/io/storage.rs | 131 ++
core/server/src/lib.rs | 20 +-
core/server/src/log/logger.rs | 8 +-
core/server/src/log/mod.rs | 6 +-
core/server/src/log/runtime.rs | 87 ++
core/server/src/log/tokio_console.rs | 53 -
core/server/src/main.rs | 440 ++++--
core/server/src/quic/listener.rs | 170 ++-
core/server/src/quic/mod.rs | 1 +
core/server/src/quic/quic_sender.rs | 29 +-
core/server/src/quic/quic_server.rs | 144 +-
core/server/src/quic/quic_socket.rs | 66 +
core/server/src/server_error.rs | 15 +-
core/server/src/shard/builder.rs | 176 +++
core/server/src/shard/communication.rs | 189 +++
core/server/src/shard/handlers.rs | 404 ++++++
core/server/src/shard/mod.rs | 314 +++++
core/server/src/shard/namespace.rs | 121 ++
.../{streaming/systems => shard/system}/clients.rs | 59 +-
core/server/src/shard/system/cluster.rs | 76 ++
core/server/src/shard/system/consumer_groups.rs | 306 +++++
core/server/src/shard/system/consumer_offsets.rs | 323 +++++
.../{streaming/systems => shard/system}/info.rs | 54 -
core/server/src/shard/system/messages.rs | 478 +++++++
.../src/{streaming/systems => shard/system}/mod.rs | 4 +-
core/server/src/shard/system/partitions.rs | 328 +++++
.../system}/personal_access_tokens.rs | 138 +-
core/server/src/shard/system/segments.rs | 115 ++
.../systems => shard/system}/snapshot/mod.rs | 138 +-
.../systems => shard/system}/snapshot/procdump.rs | 18 +-
core/server/src/shard/system/stats.rs | 136 ++
.../{streaming/systems => shard/system}/storage.rs | 32 +-
core/server/src/shard/system/streams.rs | 221 +++
core/server/src/shard/system/topics.rs | 348 +++++
core/server/src/shard/system/users.rs | 452 ++++++
core/server/src/shard/system/utils.rs | 162 +++
core/server/src/shard/task_registry/builders.rs | 25 +
.../src/shard/task_registry/builders/continuous.rs | 92 ++
.../src/shard/task_registry/builders/oneshot.rs | 103 ++
.../src/shard/task_registry/builders/periodic.rs | 118 ++
core/server/src/shard/task_registry/mod.rs | 6 +
core/server/src/shard/task_registry/registry.rs | 581 ++++++++
core/server/src/shard/task_registry/shutdown.rs | 233 ++++
.../tasks/continuous/http_server.rs} | 45 +-
.../src/shard/tasks/continuous/message_pump.rs | 75 +
.../src/{log => shard/tasks/continuous}/mod.rs | 14 +-
.../tasks/continuous/quic_server.rs} | 33 +-
.../tasks/continuous/tcp_server.rs} | 24 +-
.../tasks/continuous/websocket_server.rs} | 35 +-
core/server/src/{channels => shard/tasks}/mod.rs | 6 +-
.../src/shard/tasks/oneshot/config_writer.rs | 109 ++
core/server/src/shard/tasks/oneshot/mod.rs | 3 +
.../src/shard/tasks/periodic/heartbeat_verifier.rs | 87 ++
.../src/shard/tasks/periodic/jwt_token_cleaner.rs | 61 +
.../src/shard/tasks/periodic/message_saver.rs | 124 ++
.../partitions.rs => shard/tasks/periodic/mod.rs} | 21 +-
.../periodic/personal_access_token_cleaner.rs | 76 ++
.../src/shard/tasks/periodic/sysinfo_printer.rs | 94 ++
core/server/src/shard/transmission/connector.rs | 107 ++
core/server/src/shard/transmission/event.rs | 121 ++
core/server/src/shard/transmission/frame.rs | 67 +
.../segments.rs => shard/transmission/id.rs} | 31 +-
core/server/src/shard/transmission/message.rs | 119 ++
.../src/{channels => shard/transmission}/mod.rs | 8 +-
core/server/src/slab/consumer_groups.rs | 156 +++
core/server/src/slab/helpers.rs | 53 +
core/server/src/slab/mod.rs | 26 +
core/server/src/slab/partitions.rs | 226 +++
core/server/src/slab/streams.rs | 1439 ++++++++++++++++++++
core/server/src/slab/topics.rs | 259 ++++
core/server/src/slab/traits_ext.rs | 100 ++
core/server/src/slab/users.rs | 190 +++
core/server/src/state/file.rs | 77 +-
core/server/src/state/mod.rs | 53 +-
core/server/src/state/system.rs | 104 +-
.../server/src/streaming/clients/client_manager.rs | 241 ++--
.../deduplication/message_deduplicator.rs | 53 +-
core/server/src/streaming/diagnostics/metrics.rs | 79 +-
core/server/src/streaming/mod.rs | 6 +-
.../src/streaming/partitions/consumer_offset.rs | 51 +
.../src/streaming/partitions/consumer_offsets.rs | 214 ---
core/server/src/streaming/partitions/helpers.rs | 525 +++++++
core/server/src/streaming/partitions/journal.rs | 118 ++
core/server/src/streaming/partitions/log.rs | 159 +++
core/server/src/streaming/partitions/messages.rs | 766 -----------
core/server/src/streaming/partitions/mod.rs | 7 +-
core/server/src/streaming/partitions/partition.rs | 669 +++++----
.../server/src/streaming/partitions/persistence.rs | 114 --
core/server/src/streaming/partitions/segments.rs | 15 +-
core/server/src/streaming/partitions/storage.rs | 646 +++------
core/server/src/streaming/persistence/mod.rs | 1 -
core/server/src/streaming/persistence/persister.rs | 80 +-
core/server/src/streaming/persistence/task.rs | 133 --
core/server/src/streaming/polling_consumer.rs | 16 +-
.../src/streaming/segments/indexes/index_reader.rs | 60 +-
.../src/streaming/segments/indexes/index_writer.rs | 45 +-
.../src/streaming/segments/indexes/indexes_mut.rs | 9 +-
.../src/streaming/segments/memory_journal.rs | 0
.../streaming/segments/messages/messages_reader.rs | 63 +-
.../streaming/segments/messages/messages_writer.rs | 132 +-
core/server/src/streaming/segments/messages/mod.rs | 41 +-
.../streaming/segments/messages/persister_task.rs | 215 ---
.../src/streaming/segments/messages_accumulator.rs | 196 ---
core/server/src/streaming/segments/mod.rs | 8 +-
.../src/streaming/segments/reading_messages.rs | 380 ------
core/server/src/streaming/segments/segment.rs | 550 +-------
core/server/src/streaming/segments/storage.rs | 90 ++
.../streaming/segments/types/messages_batch_mut.rs | 7 +-
.../streaming/segments/types/messages_batch_set.rs | 8 -
.../src/streaming/segments/writing_messages.rs | 182 ---
core/server/src/streaming/session.rs | 46 +-
core/server/src/streaming/stats/mod.rs | 332 +++++
core/server/src/streaming/storage.rs | 178 +--
core/server/src/streaming/streams/helpers.rs | 90 ++
core/server/src/streaming/streams/messages.rs | 26 -
core/server/src/streaming/streams/mod.rs | 6 +-
core/server/src/streaming/streams/persistence.rs | 74 -
core/server/src/streaming/streams/segments.rs | 26 -
core/server/src/streaming/streams/storage.rs | 254 +---
core/server/src/streaming/streams/stream.rs | 293 ++--
core/server/src/streaming/streams/topics.rs | 338 -----
.../src/streaming/systems/consumer_groups.rs | 321 -----
.../src/streaming/systems/consumer_offsets.rs | 104 --
core/server/src/streaming/systems/messages.rs | 266 ----
core/server/src/streaming/systems/partitions.rs | 113 --
core/server/src/streaming/systems/segments.rs | 105 --
core/server/src/streaming/systems/stats.rs | 113 --
core/server/src/streaming/systems/streams.rs | 522 -------
core/server/src/streaming/systems/system.rs | 280 ----
core/server/src/streaming/systems/topics.rs | 300 ----
core/server/src/streaming/systems/users.rs | 530 -------
core/server/src/streaming/topics/consumer_group.rs | 421 +++---
.../server/src/streaming/topics/consumer_groups.rs | 430 ------
.../src/streaming/topics/consumer_offsets.rs | 136 --
core/server/src/streaming/topics/helpers.rs | 284 ++++
core/server/src/streaming/topics/messages.rs | 361 -----
core/server/src/streaming/topics/mod.rs | 7 +-
core/server/src/streaming/topics/partitions.rs | 128 --
core/server/src/streaming/topics/persistence.rs | 71 -
core/server/src/streaming/topics/storage.rs | 341 +----
core/server/src/streaming/topics/topic.rs | 610 +++++----
core/server/src/streaming/traits.rs | 28 +
core/server/src/streaming/users/permissioner.rs | 6 +-
.../users/permissioner_rules/consumer_groups.rs | 24 +-
.../users/permissioner_rules/consumer_offsets.rs | 12 +-
.../streaming/users/permissioner_rules/messages.rs | 8 +-
.../users/permissioner_rules/partitions.rs | 8 +-
.../streaming/users/permissioner_rules/segments.rs | 4 +-
.../streaming/users/permissioner_rules/streams.rs | 10 +-
.../streaming/users/permissioner_rules/topics.rs | 30 +-
core/server/src/streaming/users/user.rs | 4 +-
core/server/src/streaming/utils/file.rs | 17 +-
core/server/src/streaming/utils/head_tail_buf.rs | 55 -
core/server/src/streaming/utils/memory_pool.rs | 47 +-
core/server/src/streaming/utils/mod.rs | 2 +-
core/server/src/streaming/utils/pooled_buffer.rs | 43 +-
core/server/src/streaming/utils/ptr.rs | 53 +
core/server/src/tcp/connection_handler.rs | 69 +-
core/server/src/tcp/mod.rs | 2 +-
core/server/src/tcp/sender.rs | 66 +-
core/server/src/tcp/tcp_listener.rs | 194 ++-
core/server/src/tcp/tcp_sender.rs | 9 +-
core/server/src/tcp/tcp_server.rs | 42 +-
core/server/src/tcp/tcp_socket.rs | 32 +-
core/server/src/tcp/tcp_tls_listener.rs | 273 ++--
core/server/src/tcp/tcp_tls_sender.rs | 40 +-
core/server/src/versioning.rs | 2 +-
core/server/src/websocket/connection_handler.rs | 161 +++
core/server/src/websocket/mod.rs | 8 +
core/server/src/websocket/websocket_listener.rs | 185 +++
core/server/src/websocket/websocket_sender.rs | 200 +++
core/server/src/websocket/websocket_server.rs | 60 +
.../server/src/websocket/websocket_tls_listener.rs | 270 ++++
core/server/src/websocket/websocket_tls_sender.rs | 186 +++
core/tools/src/data-seeder/seeder.rs | 65 +-
examples/rust/src/getting-started/producer/main.rs | 76 +-
examples/rust/src/multi-tenant/consumer/main.rs | 4 +-
examples/rust/src/multi-tenant/producer/main.rs | 4 +-
examples/rust/src/shared/args.rs | 52 +
examples/rust/src/shared/system.rs | 3 +-
helm/README.md | 5 +
helm/charts/iggy-server/.helmignore | 23 +
.../iggy-pvc.yaml => iggy-server/Chart.yaml} | 39 +-
helm/charts/iggy-server/README.md | 53 +
helm/charts/iggy-server/templates/NOTES.txt | 22 +
helm/charts/iggy-server/templates/_helpers.tpl | 67 +
helm/charts/iggy-server/templates/deployment.yaml | 92 ++
helm/charts/iggy-server/templates/hpa.yaml | 61 +
helm/charts/iggy-server/templates/ingress.yaml | 78 ++
.../templates/service.yaml} | 41 +-
.../templates/serviceaccount.yaml} | 24 +-
.../templates/servicemonitor.yaml} | 41 +-
helm/charts/iggy-server/values.yaml | 126 ++
helm/charts/iggy-ui/.helmignore | 23 +
.../templates/iggy-pvc.yaml => iggy-ui/Chart.yaml} | 39 +-
helm/charts/iggy-ui/README.md | 47 +
helm/charts/iggy-ui/templates/NOTES.txt | 22 +
helm/charts/iggy-ui/templates/_helpers.tpl | 67 +
helm/charts/iggy-ui/templates/deployment.yaml | 83 ++
helm/charts/iggy-ui/templates/ingress.yaml | 78 ++
.../templates/service.yaml} | 33 +-
.../templates/serviceaccount.yaml} | 24 +-
helm/charts/iggy-ui/values.yaml | 105 ++
justfile | 3 -
DEPENDENCIES.md => scripts/DEPENDENCIES.md | 653 ++++-----
520 files changed, 28096 insertions(+), 20023 deletions(-)
diff --git a/.github/workflows/ci-publish-go.yml b/.github/workflows/ci-publish-go.yml
new file mode 100644
index 000000000..97187d193
--- /dev/null
+++ b/.github/workflows/ci-publish-go.yml
@@ -0,0 +1,67 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: ci-publish-go
+on:
+ push:
+ tags:
+ - "foreign/go/v*"
+
+jobs:
+ validate:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Extract tag name
+ id: extract
+ run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
+
+ - name: Validate tag format
+ run: |
+ if [[ ! "$TAG" =~ ^foreign/go/v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]]; then
+ echo "Tag $TAG does not match required format: foreign/go/vX.Y.Z"
+ exit 1
+ fi
+ echo "Valid Go module tag: $TAG"
+
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Check go.mod module path in foreign/go
+ run: |
+ MOD_PATH=$(grep '^module ' foreign/go/go.mod | awk '{print $2}')
+ if [[ "$MOD_PATH" != "github.com/${{ github.repository }}/foreign/go" ]]; then
+ echo "go.mod module path ($MOD_PATH) does not match expected path: github.com/${{ github.repository }}/foreign/go"
+ exit 1
+ fi
+ echo "go.mod module path is correct: $MOD_PATH"
+
+ - name: Check if go.mod version matches tag
+ run: |
+ TAG_VERSION="${TAG#foreign/go/}"
+ # Check if go.mod contains this version in replace or require (not strictly necessary, but can be used for extra checks)
+ echo "Tag version is $TAG_VERSION (no strict version field in go.mod to check for single module)"
+ # (No error here, just informative)
+
+ - name: Run go mod tidy
+ working-directory: foreign/go
+ run: |
+ go mod tidy
+ git diff --exit-code || (echo 'Please run "go mod tidy" and commit the changes before tagging.' && exit 1)
+
+ - name: Run Go tests
+ working-directory: foreign/go
+ run: go test -v ./...
diff --git a/Cargo.lock b/Cargo.lock
index 1179da095..9df42cb0b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -75,7 +75,7 @@ dependencies = [
"derive_more 2.0.1",
"encoding_rs",
"flate2",
- "foldhash 0.1.5",
+ "foldhash",
"futures-core",
"h2 0.3.27",
"http 0.2.12",
@@ -189,7 +189,7 @@ dependencies = [
"cookie",
"derive_more 2.0.1",
"encoding_rs",
- "foldhash 0.1.5",
+ "foldhash",
"futures-core",
"futures-util",
"impl-more",
@@ -405,6 +405,12 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+[[package]]
+name = "arcshift"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809ce7e2c99d17c161c0ca1673665bab3a3e1aa277b0f793abffc66b57c32d9f"
+
[[package]]
name = "argon2"
version = "0.5.3"
@@ -445,6 +451,22 @@ dependencies = [
"wait-timeout",
]
+[[package]]
+name = "astral-tokio-tar"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec179a06c1769b1e42e1e2cbe74c7dcdb3d6383c838454d063eaac5bbb7ebbe5"
+dependencies = [
+ "filetime",
+ "futures-core",
+ "libc",
+ "portable-atomic",
+ "rustc-hash",
+ "tokio",
+ "tokio-stream",
+ "xattr",
+]
+
[[package]]
name = "async-broadcast"
version = "0.7.2"
@@ -457,6 +479,18 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "async-channel"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
[[package]]
name = "async-compression"
version = "0.4.32"
@@ -553,6 +587,12 @@ dependencies = [
"syn 2.0.108",
]
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
[[package]]
name = "async-trait"
version = "0.1.89"
@@ -612,45 +652,12 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
-[[package]]
-name = "attohttpc"
-version = "0.30.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9"
-dependencies = [
- "base64 0.22.1",
- "http 1.3.1",
- "log",
- "rustls",
- "serde",
- "serde_json",
- "url",
- "webpki-roots 1.0.3",
-]
-
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
-[[package]]
-name = "aws-creds"
-version = "0.39.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b13804829a843b3f26e151c97acbb315ee1177a2724690edfcd28f1894146200"
-dependencies = [
- "attohttpc",
- "home",
- "log",
- "quick-xml",
- "rust-ini",
- "serde",
- "thiserror 2.0.17",
- "time",
- "url",
-]
-
[[package]]
name = "aws-lc-rs"
version = "1.14.1"
@@ -674,15 +681,6 @@ dependencies = [
"fs_extra",
]
-[[package]]
-name = "aws-region"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5532f65342f789f9c1b7078ea9c9cd9293cd62dcc284fa99adc4a1c9ba43469c"
-dependencies = [
- "thiserror 2.0.17",
-]
-
[[package]]
name = "axum"
version = "0.7.9"
@@ -717,6 +715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
dependencies = [
"axum-core 0.5.5",
+ "axum-macros",
"bytes",
"form_urlencoded",
"futures-util",
@@ -782,6 +781,17 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "axum-macros"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.108",
+]
+
[[package]]
name = "axum-server"
version = "0.7.2"
@@ -804,12 +814,6 @@ dependencies = [
"tower-service",
]
-[[package]]
-name = "base16ct"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
-
[[package]]
name = "base64"
version = "0.21.7"
@@ -1016,9 +1020,9 @@ dependencies = [
[[package]]
name = "bollard"
-version = "0.19.1"
+version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899ca34eb6924d6ec2a77c6f7f5c7339e60fd68235eaf91edd5a15f12958bb06"
+checksum = "ec7646ee90964aa59e9f832a67182791396a19a5b1d76eb17599a8310a7e2e09"
dependencies = [
"async-stream",
"base64 0.22.1",
@@ -1055,7 +1059,7 @@ dependencies = [
"tokio",
"tokio-stream",
"tokio-util",
- "tonic 0.13.1",
+ "tonic 0.14.2",
"tower-service",
"url",
"winapi",
@@ -1063,27 +1067,28 @@ dependencies = [
[[package]]
name = "bollard-buildkit-proto"
-version = "0.6.1"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40b3e79f8bd0f25f32660e3402afca46fd91bebaf135af017326d905651f8107"
+checksum = "85a885520bf6249ab931a764ffdb87b0ceef48e6e7d807cfdb21b751e086e1ad"
dependencies = [
- "prost 0.13.5",
- "prost-types 0.13.5",
- "tonic 0.13.1",
+ "prost 0.14.1",
+ "prost-types 0.14.1",
+ "tonic 0.14.2",
+ "tonic-prost",
"ureq",
]
[[package]]
name = "bollard-stubs"
-version = "1.48.3-rc.28.0.4"
+version = "1.49.1-rc.28.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64ea257e555d16a2c01e5593f40b73865cdf12efbceda33c6d14a2d8d1490368"
+checksum = "5731fe885755e92beff1950774068e0cae67ea6ec7587381536fca84f1779623"
dependencies = [
"base64 0.22.1",
"bollard-buildkit-proto",
"bytes",
"chrono",
- "prost 0.13.5",
+ "prost 0.14.1",
"serde",
"serde_json",
"serde_repr",
@@ -1167,9 +1172,9 @@ dependencies = [
[[package]]
name = "bstr"
-version = "1.12.0"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
+checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
dependencies = [
"memchr",
"regex-automata",
@@ -1289,15 +1294,6 @@ dependencies = [
"thiserror 2.0.17",
]
-[[package]]
-name = "castaway"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
-dependencies = [
- "rustversion",
-]
-
[[package]]
name = "cc"
version = "1.2.43"
@@ -1505,16 +1501,196 @@ dependencies = [
]
[[package]]
-name = "compact_str"
-version = "0.7.1"
+name = "compio"
+version = "0.15.0"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "compio-buf",
+ "compio-driver",
+ "compio-fs",
+ "compio-io",
+ "compio-log 0.1.0 (git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be)",
+ "compio-macros",
+ "compio-net",
+ "compio-quic",
+ "compio-runtime",
+ "compio-tls",
+]
+
+[[package]]
+name = "compio-buf"
+version = "0.6.0"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "arrayvec",
+ "bytes",
+ "libc",
+]
+
+[[package]]
+name = "compio-driver"
+version = "0.8.1"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "cfg-if",
+ "cfg_aliases",
+ "compio-buf",
+ "compio-log 0.1.0 (git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be)",
+ "crossbeam-channel",
+ "crossbeam-queue",
+ "futures-util",
+ "io-uring",
+ "io_uring_buf_ring",
+ "libc",
+ "once_cell",
+ "paste",
+ "polling",
+ "slab",
+ "socket2 0.6.1",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "compio-fs"
+version = "0.8.0"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "cfg-if",
+ "cfg_aliases",
+ "compio-buf",
+ "compio-driver",
+ "compio-io",
+ "compio-runtime",
+ "libc",
+ "os_pipe",
+ "widestring",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "compio-io"
+version = "0.7.0"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "compio-buf",
+ "futures-util",
+ "paste",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "compio-log"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
+checksum = "fc4e560213c1996b618da369b7c9109564b41af9033802ae534465c4ee4e132f"
+dependencies = [
+ "tracing",
+]
+
+[[package]]
+name = "compio-log"
+version = "0.1.0"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "tracing",
+]
+
+[[package]]
+name = "compio-macros"
+version = "0.1.2"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "proc-macro-crate 3.4.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.108",
+]
+
+[[package]]
+name = "compio-net"
+version = "0.8.0"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
dependencies = [
- "castaway",
"cfg-if",
- "itoa",
- "ryu",
- "static_assertions",
+ "compio-buf",
+ "compio-driver",
+ "compio-io",
+ "compio-runtime",
+ "either",
+ "libc",
+ "once_cell",
+ "socket2 0.6.1",
+ "widestring",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "compio-quic"
+version = "0.4.0"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "cfg_aliases",
+ "compio-buf",
+ "compio-io",
+ "compio-log 0.1.0 (git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be)",
+ "compio-net",
+ "compio-runtime",
+ "flume",
+ "futures-util",
+ "libc",
+ "quinn-proto",
+ "rustc-hash",
+ "rustls",
+ "thiserror 2.0.17",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "compio-runtime"
+version = "0.8.1"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "async-task",
+ "cfg-if",
+ "compio-buf",
+ "compio-driver",
+ "compio-log 0.1.0 (git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be)",
+ "core_affinity",
+ "crossbeam-queue",
+ "futures-util",
+ "libc",
+ "once_cell",
+ "scoped-tls",
+ "slab",
+ "socket2 0.6.1",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "compio-tls"
+version = "0.6.0"
+source = "git+https://github.com/compio-rs/compio.git?rev=fe4243f0b6811ebc325afd081c9b087b4d9817be#fe4243f0b6811ebc325afd081c9b087b4d9817be"
+dependencies = [
+ "compio-buf",
+ "compio-io",
+ "native-tls",
+ "rustls",
+]
+
+[[package]]
+name = "compio-ws"
+version = "0.1.0"
+source = "git+https://github.com/krishvishal/compio-ws#ca9240b6b0eb2206e8ae050580a053c5e9d0e877"
+dependencies = [
+ "compio",
+ "compio-buf",
+ "compio-io",
+ "compio-net",
+ "compio-runtime",
+ "compio-tls",
+ "rustls",
+ "rustls-pemfile",
+ "tungstenite",
]
[[package]]
@@ -1614,26 +1790,6 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
-[[package]]
-name = "const-random"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
-dependencies = [
- "const-random-macro",
-]
-
-[[package]]
-name = "const-random-macro"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
-dependencies = [
- "getrandom 0.2.16",
- "once_cell",
- "tiny-keccak",
-]
-
[[package]]
name = "constant_time_eq"
version = "0.3.1"
@@ -1669,6 +1825,16 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "core-foundation"
version = "0.10.1"
@@ -1685,6 +1851,17 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "core_affinity"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a034b3a7b624016c6e13f5df875747cc25f884156aad2abd12b6c46797971342"
+dependencies = [
+ "libc",
+ "num_cpus",
+ "winapi",
+]
+
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -1803,24 +1980,6 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "crunchy"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
-
-[[package]]
-name = "crypto-bigint"
-version = "0.5.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
-dependencies = [
- "generic-array",
- "rand_core 0.6.4",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -1857,6 +2016,17 @@ dependencies = [
"cipher",
]
+[[package]]
+name = "ctrlc"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "881c5d0a13b2f1498e2306e82cbada78390e152d4b1378fb28a84f4dcd0dc4f3"
+dependencies = [
+ "dispatch",
+ "nix",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "cucumber"
version = "0.21.1"
@@ -1917,30 +2087,62 @@ dependencies = [
]
[[package]]
-name = "curve25519-dalek"
-version = "4.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+name = "cyper"
+version = "0.4.0"
+source = "git+https://github.com/krishvishal/cyper.git?rev=cd75e266df6ab0a9b9474eb7dda1735650d17db6#cd75e266df6ab0a9b9474eb7dda1735650d17db6"
dependencies = [
- "cfg-if",
- "cpufeatures",
- "curve25519-dalek-derive",
- "digest",
- "fiat-crypto",
- "rustc_version",
- "subtle",
- "zeroize",
+ "async-stream",
+ "base64 0.22.1",
+ "compio",
+ "cyper-core",
+ "encoding_rs",
+ "futures-util",
+ "http 1.3.1",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "mime",
+ "send_wrapper",
+ "serde",
+ "serde_urlencoded",
+ "thiserror 2.0.17",
+ "url",
]
[[package]]
-name = "curve25519-dalek-derive"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+name = "cyper-axum"
+version = "0.4.0"
+source = "git+https://github.com/krishvishal/cyper.git?rev=cd75e266df6ab0a9b9474eb7dda1735650d17db6#cd75e266df6ab0a9b9474eb7dda1735650d17db6"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.108",
+ "axum 0.8.6",
+ "axum-core 0.5.5",
+ "compio",
+ "compio-log 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cyper-core",
+ "futures-channel",
+ "futures-util",
+ "hyper",
+ "hyper-util",
+ "send_wrapper",
+ "socket2 0.6.1",
+ "tokio",
+ "tower 0.5.2",
+ "tower-service",
+]
+
+[[package]]
+name = "cyper-core"
+version = "0.4.0"
+source = "git+https://github.com/krishvishal/cyper.git?rev=cd75e266df6ab0a9b9474eb7dda1735650d17db6#cd75e266df6ab0a9b9474eb7dda1735650d17db6"
+dependencies = [
+ "cfg-if",
+ "compio",
+ "futures-util",
+ "hyper",
+ "hyper-util",
+ "rustls-platform-verifier",
+ "send_wrapper",
+ "tower-service",
]
[[package]]
@@ -2027,6 +2229,12 @@ dependencies = [
"parking_lot_core 0.9.12",
]
+[[package]]
+name = "data-encoding"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
+
[[package]]
name = "dbus"
version = "0.9.9"
@@ -2212,6 +2420,12 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -2246,15 +2460,6 @@ dependencies = [
"syn 2.0.108",
]
-[[package]]
-name = "dlv-list"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
-dependencies = [
- "const-random",
-]
-
[[package]]
name = "doc-comment"
version = "0.3.4"
@@ -2332,44 +2537,6 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
-[[package]]
-name = "ecdsa"
-version = "0.16.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
-dependencies = [
- "der",
- "digest",
- "elliptic-curve",
- "rfc6979",
- "signature",
- "spki",
-]
-
-[[package]]
-name = "ed25519"
-version = "2.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
-dependencies = [
- "pkcs8",
- "signature",
-]
-
-[[package]]
-name = "ed25519-dalek"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
-dependencies = [
- "curve25519-dalek",
- "ed25519",
- "serde",
- "sha2",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "either"
version = "1.15.0"
@@ -2379,27 +2546,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "elliptic-curve"
-version = "0.13.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
-dependencies = [
- "base16ct",
- "crypto-bigint",
- "digest",
- "ff",
- "generic-array",
- "group",
- "hkdf",
- "pem-rfc7468",
- "pkcs8",
- "rand_core 0.6.4",
- "sec1",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "embedded-io"
version = "0.4.0"
@@ -2601,22 +2747,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
-[[package]]
-name = "ff"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
-dependencies = [
- "rand_core 0.6.4",
- "subtle",
-]
-
-[[package]]
-name = "fiat-crypto"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
-
[[package]]
name = "figlet-rs"
version = "0.1.5"
@@ -2719,12 +2849,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
-[[package]]
-name = "foldhash"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
-
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -2913,7 +3037,6 @@ checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [
"typenum",
"version_check",
- "zeroize",
]
[[package]]
@@ -3445,17 +3568,6 @@ dependencies = [
"web-time",
]
-[[package]]
-name = "group"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
-dependencies = [
- "ff",
- "rand_core 0.6.4",
- "subtle",
-]
-
[[package]]
name = "h2"
version = "0.3.27"
@@ -3496,11 +3608,11 @@ dependencies = [
[[package]]
name = "halfbrown"
-version = "0.4.0"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c7ed2f2edad8a14c8186b847909a41fbb9c3eafa44f88bd891114ed5019da09"
+checksum = "aa2c385c6df70fd180bbb673d93039dbd2cd34e41d782600bdf6e1ca7bce39aa"
dependencies = [
- "hashbrown 0.16.0",
+ "hashbrown 0.15.5",
"serde",
]
@@ -3529,6 +3641,12 @@ dependencies = [
"byteorder",
]
+[[package]]
+name = "hash32"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e4e11d13d42fc8d55326b323c63978c75721fbbb695a6e6686765bcb8b33917"
+
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -3552,7 +3670,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
- "foldhash 0.1.5",
+ "foldhash",
]
[[package]]
@@ -3560,11 +3678,6 @@ name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
-dependencies = [
- "allocator-api2",
- "equivalent",
- "foldhash 0.2.0",
-]
[[package]]
name = "hashlink"
@@ -3595,7 +3708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
- "hash32",
+ "hash32 0.2.1",
"rustc_version",
"serde",
"spin",
@@ -4010,8 +4123,10 @@ dependencies = [
"serde",
"tokio",
"tokio-rustls",
+ "tokio-tungstenite",
"tracing",
"trait-variant",
+ "tungstenite",
"webpki-roots 1.0.3",
]
@@ -4179,6 +4294,7 @@ dependencies = [
"chrono",
"clap",
"comfy-table",
+ "compio-ws",
"crc32fast",
"derive_more 2.0.1",
"fast-async-mutex",
@@ -4453,6 +4569,7 @@ dependencies = [
"async-trait",
"bytes",
"chrono",
+ "compio",
"ctor",
"derive_more 2.0.1",
"env_logger",
@@ -4490,6 +4607,28 @@ dependencies = [
"rustversion",
]
+[[package]]
+name = "io-uring"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
+dependencies = [
+ "bitflags 2.10.0",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "io_uring_buf_ring"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a8867874ff5758b47c1dac069e6e86541432f9da8be9111c5e94154134f07d0"
+dependencies = [
+ "bytes",
+ "io-uring",
+ "rustix",
+]
+
[[package]]
name = "ipnet"
version = "2.11.0"
@@ -4594,9 +4733,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.81"
+version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
+checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -4617,29 +4756,6 @@ dependencies = [
"simple_asn1",
]
-[[package]]
-name = "jsonwebtoken"
-version = "10.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d119c6924272d16f0ab9ce41f7aa0bfef9340c00b0bb7ca3dd3b263d4a9150b"
-dependencies = [
- "base64 0.22.1",
- "ed25519-dalek",
- "getrandom 0.2.16",
- "hmac",
- "js-sys",
- "p256",
- "p384",
- "pem",
- "rand 0.8.5",
- "rsa",
- "serde",
- "serde_json",
- "sha2",
- "signature",
- "simple_asn1",
-]
-
[[package]]
name = "jwalk"
version = "0.8.1"
@@ -5010,17 +5126,6 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
-[[package]]
-name = "maybe-async"
-version = "0.2.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.108",
-]
-
[[package]]
name = "md-5"
version = "0.10.6"
@@ -5031,12 +5136,6 @@ dependencies = [
"digest",
]
-[[package]]
-name = "md5"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
-
[[package]]
name = "memchr"
version = "2.7.6"
@@ -5090,15 +5189,6 @@ dependencies = [
"unicase",
]
-[[package]]
-name = "minidom"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e394a0e3c7ccc2daea3dffabe82f09857b6b510cb25af87d54bf3e910ac1642d"
-dependencies = [
- "rxml",
-]
-
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -5183,6 +5273,23 @@ dependencies = [
"getrandom 0.2.16",
]
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework 2.11.1",
+ "security-framework-sys",
+ "tempfile",
+]
+
[[package]]
name = "never-say-never"
version = "6.6.666"
@@ -5475,7 +5582,7 @@ dependencies = [
"hyper-rustls",
"hyper-timeout",
"hyper-util",
- "jsonwebtoken 9.3.1",
+ "jsonwebtoken",
"once_cell",
"percent-encoding",
"pin-project",
@@ -5657,7 +5764,6 @@ dependencies = [
"rand 0.9.2",
"thiserror 2.0.17",
"tokio",
- "tokio-stream",
]
[[package]]
@@ -5667,37 +5773,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
-name = "ordered-multimap"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
-dependencies = [
- "dlv-list",
- "hashbrown 0.14.5",
-]
-
-[[package]]
-name = "p256"
-version = "0.13.2"
+name = "os_pipe"
+version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "sha2",
+ "libc",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "p384"
-version = "0.13.1"
+name = "papaya"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
+checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f"
dependencies = [
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "sha2",
+ "equivalent",
+ "seize",
]
[[package]]
@@ -6004,6 +6096,20 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "polonius-the-crab"
version = "0.2.1"
@@ -6120,15 +6226,6 @@ dependencies = [
"syn 2.0.108",
]
-[[package]]
-name = "primeorder"
-version = "0.13.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
-dependencies = [
- "elliptic-curve",
-]
-
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
@@ -6374,9 +6471,9 @@ dependencies = [
[[package]]
name = "quick-xml"
-version = "0.38.3"
+version = "0.37.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89"
+checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
dependencies = [
"memchr",
"serde",
@@ -6570,15 +6667,6 @@ dependencies = [
"bitflags 1.3.2",
]
-[[package]]
-name = "redox_syscall"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
-dependencies = [
- "bitflags 1.3.2",
-]
-
[[package]]
name = "redox_syscall"
version = "0.5.18"
@@ -6757,16 +6845,6 @@ dependencies = [
"rand 0.8.5",
]
-[[package]]
-name = "rfc6979"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
-dependencies = [
- "hmac",
- "subtle",
-]
-
[[package]]
name = "ring"
version = "0.17.14"
@@ -6781,6 +6859,12 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "ringbuffer"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57b0b88a509053cbfd535726dcaaceee631313cef981266119527a1d110f6d2b"
+
[[package]]
name = "rkyv"
version = "0.7.45"
@@ -6881,51 +6965,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "rust-ini"
-version = "0.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
-dependencies = [
- "cfg-if",
- "ordered-multimap",
-]
-
-[[package]]
-name = "rust-s3"
-version = "0.37.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94f9b973bd4097f5bb47e5827dcb9fb5dc17e93879e46badc27d2a4e9a4e5588"
-dependencies = [
- "async-trait",
- "aws-creds",
- "aws-region",
- "base64 0.22.1",
- "bytes",
- "cfg-if",
- "futures-util",
- "hex",
- "hmac",
- "http 1.3.1",
- "log",
- "maybe-async",
- "md5",
- "minidom",
- "percent-encoding",
- "quick-xml",
- "reqwest",
- "serde",
- "serde_derive",
- "serde_json",
- "sha2",
- "sysinfo 0.37.2",
- "thiserror 2.0.17",
- "time",
- "tokio",
- "tokio-stream",
- "url",
-]
-
[[package]]
name = "rust_decimal"
version = "1.39.0"
@@ -6995,7 +7034,7 @@ dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
- "security-framework",
+ "security-framework 3.5.1",
]
[[package]]
@@ -7009,9 +7048,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
-version = "1.12.0"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
dependencies = [
"web-time",
"zeroize",
@@ -7023,7 +7062,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
- "core-foundation",
+ "core-foundation 0.10.1",
"core-foundation-sys",
"jni",
"log",
@@ -7032,7 +7071,7 @@ dependencies = [
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
- "security-framework",
+ "security-framework 3.5.1",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
@@ -7063,22 +7102,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
-name = "rxml"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee"
-dependencies = [
- "bytes",
- "rxml_validation",
-]
-
-[[package]]
-name = "rxml_validation"
-version = "0.11.0"
+name = "rusty-s3"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4"
+checksum = "8f51a5a6b15f25d3e10c068039ee13befb6110fcb36c2b26317bcbdc23484d96"
dependencies = [
- "compact_str",
+ "base64 0.22.1",
+ "hmac",
+ "md-5",
+ "percent-encoding",
+ "quick-xml",
+ "serde",
+ "serde_json",
+ "sha2",
+ "time",
+ "url",
+ "zeroize",
]
[[package]]
@@ -7152,6 +7191,12 @@ dependencies = [
"syn 2.0.108",
]
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -7183,26 +7228,25 @@ dependencies = [
]
[[package]]
-name = "sec1"
-version = "0.7.3"
+name = "secrecy"
+version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
- "base16ct",
- "der",
- "generic-array",
- "pkcs8",
- "subtle",
"zeroize",
]
[[package]]
-name = "secrecy"
-version = "0.10.3"
+name = "security-framework"
+version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
- "zeroize",
+ "bitflags 2.10.0",
+ "core-foundation 0.9.4",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
]
[[package]]
@@ -7212,7 +7256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
dependencies = [
"bitflags 2.10.0",
- "core-foundation",
+ "core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
@@ -7228,6 +7272,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "seize"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "semver"
version = "1.0.27"
@@ -7238,6 +7292,15 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "send_wrapper"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
+dependencies = [
+ "futures-core",
+]
+
[[package]]
name = "serde"
version = "1.0.228"
@@ -7441,7 +7504,9 @@ version = "0.5.0"
dependencies = [
"ahash 0.8.12",
"anyhow",
+ "arcshift",
"argon2",
+ "async-channel",
"async_zip",
"axum 0.8.6",
"axum-server",
@@ -7450,8 +7515,17 @@ dependencies = [
"bytes",
"chrono",
"clap",
+ "compio",
+ "compio-io",
+ "compio-net",
+ "compio-quic",
+ "compio-tls",
+ "compio-ws",
"console-subscriber",
"crossbeam",
+ "ctrlc",
+ "cyper",
+ "cyper-axum",
"dashmap",
"derive_more 2.0.1",
"dotenvy",
@@ -7462,12 +7536,12 @@ dependencies = [
"figment",
"flume",
"futures",
+ "hash32 1.0.0",
"human-repr",
"iggy_common",
- "jsonwebtoken 10.1.0",
+ "jsonwebtoken",
"lending-iterator",
"mimalloc",
- "mockall",
"moka",
"nix",
"once_cell",
@@ -7476,31 +7550,35 @@ dependencies = [
"opentelemetry-otlp",
"opentelemetry-semantic-conventions",
"opentelemetry_sdk",
+ "papaya",
"prometheus-client",
"quinn",
"rand 0.9.2",
"reqwest",
"ring",
- "rust-s3",
+ "ringbuffer",
"rustls",
"rustls-pemfile",
+ "rusty-s3",
+ "send_wrapper",
"serde",
"serde_with",
"serial_test",
+ "slab",
+ "socket2 0.6.1",
"static-toml",
"strum",
"sysinfo 0.37.2",
"tempfile",
+ "test-case",
"thiserror 2.0.17",
- "tokio",
- "tokio-rustls",
- "tokio-util",
"toml 0.9.8",
"tower-http",
"tracing",
"tracing-appender",
"tracing-opentelemetry",
"tracing-subscriber",
+ "tungstenite",
"twox-hash",
"ulid",
"uuid",
@@ -7571,10 +7649,11 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simd-json"
-version = "0.17.0"
+version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4255126f310d2ba20048db6321c81ab376f6a6735608bf11f0785c41f01f64e3"
+checksum = "c962f626b54771990066e5435ec8331d1462576cd2d1e62f24076ae014f92112"
dependencies = [
+ "getrandom 0.3.4",
"halfbrown",
"ref-cast",
"serde",
@@ -7939,12 +8018,6 @@ dependencies = [
"toml 0.8.23",
]
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
[[package]]
name = "stringprep"
version = "0.1.5"
@@ -8190,13 +8263,13 @@ dependencies = [
[[package]]
name = "testcontainers"
-version = "0.25.0"
+version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b92bce247dc9260a19808321e11b51ea6a0293d02b48ab1c6578960610cfa2a7"
+checksum = "3f3ac71069f20ecfa60c396316c283fbf35e6833a53dff551a31b5458da05edc"
dependencies = [
+ "astral-tokio-tar",
"async-trait",
"bollard",
- "bollard-stubs",
"bytes",
"docker_credential",
"either",
@@ -8212,7 +8285,6 @@ dependencies = [
"thiserror 2.0.17",
"tokio",
"tokio-stream",
- "tokio-tar",
"tokio-util",
"ulid",
"url",
@@ -8320,15 +8392,6 @@ dependencies = [
"time-core",
]
-[[package]]
-name = "tiny-keccak"
-version = "2.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
-dependencies = [
- "crunchy",
-]
-
[[package]]
name = "tinystr"
version = "0.8.1"
@@ -8405,18 +8468,19 @@ dependencies = [
]
[[package]]
-name = "tokio-tar"
-version = "0.3.1"
+name = "tokio-tungstenite"
+version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75"
+checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1"
dependencies = [
- "filetime",
- "futures-core",
- "libc",
- "redox_syscall 0.3.5",
+ "futures-util",
+ "log",
+ "rustls",
+ "rustls-pki-types",
"tokio",
- "tokio-stream",
- "xattr",
+ "tokio-rustls",
+ "tungstenite",
+ "webpki-roots 0.26.11",
]
[[package]]
@@ -8566,35 +8630,6 @@ dependencies = [
"tracing",
]
-[[package]]
-name = "tonic"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9"
-dependencies = [
- "async-trait",
- "axum 0.8.6",
- "base64 0.22.1",
- "bytes",
- "h2 0.4.12",
- "http 1.3.1",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-timeout",
- "hyper-util",
- "percent-encoding",
- "pin-project",
- "prost 0.13.5",
- "socket2 0.5.10",
- "tokio",
- "tokio-stream",
- "tower 0.5.2",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
[[package]]
name = "tonic"
version = "0.14.2"
@@ -8602,8 +8637,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203"
dependencies = [
"async-trait",
+ "axum 0.8.6",
"base64 0.22.1",
"bytes",
+ "h2 0.4.12",
"http 1.3.1",
"http-body",
"http-body-util",
@@ -8612,6 +8649,7 @@ dependencies = [
"hyper-util",
"percent-encoding",
"pin-project",
+ "socket2 0.6.1",
"sync_wrapper",
"tokio",
"tokio-stream",
@@ -8823,6 +8861,25 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+[[package]]
+name = "tungstenite"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d"
+dependencies = [
+ "bytes",
+ "data-encoding",
+ "http 1.3.1",
+ "httparse",
+ "log",
+ "rand 0.9.2",
+ "rustls",
+ "rustls-pki-types",
+ "sha1",
+ "thiserror 2.0.17",
+ "utf-8",
+]
+
[[package]]
name = "twox-hash"
version = "2.1.2"
@@ -8976,17 +9033,31 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "ureq"
-version = "2.12.1"
+version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
+checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537"
dependencies = [
"base64 0.22.1",
"log",
- "once_cell",
+ "percent-encoding",
"rustls",
+ "rustls-pemfile",
"rustls-pki-types",
- "url",
- "webpki-roots 0.26.11",
+ "ureq-proto",
+ "utf-8",
+ "webpki-roots 1.0.3",
+]
+
+[[package]]
+name = "ureq-proto"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2"
+dependencies = [
+ "base64 0.22.1",
+ "http 1.3.1",
+ "httparse",
+ "log",
]
[[package]]
@@ -9007,6 +9078,12 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
[[package]]
name = "utf8-width"
version = "0.1.7"
@@ -9053,9 +9130,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "value-trait"
-version = "0.12.1"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e80f0c733af0720a501b3905d22e2f97662d8eacfe082a75ed7ffb5ab08cb59"
+checksum = "0508fce11ad19e0aab49ce20b6bec7f8f82902ded31df1c9fc61b90f0eb396b8"
dependencies = [
"float-cmp",
"halfbrown",
@@ -9175,9 +9252,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
-version = "0.2.104"
+version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
+checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [
"cfg-if",
"once_cell",
@@ -9186,25 +9263,11 @@ dependencies = [
"wasm-bindgen-shared",
]
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
-dependencies = [
- "bumpalo",
- "log",
- "proc-macro2",
- "quote",
- "syn 2.0.108",
- "wasm-bindgen-shared",
-]
-
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.54"
+version = "0.4.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
+checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
dependencies = [
"cfg-if",
"js-sys",
@@ -9215,9 +9278,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.104"
+version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
+checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -9225,22 +9288,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.104"
+version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
+checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
dependencies = [
+ "bumpalo",
"proc-macro2",
"quote",
"syn 2.0.108",
- "wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.104"
+version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
+checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
dependencies = [
"unicode-ident",
]
@@ -9275,9 +9338,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.81"
+version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
+checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -9331,6 +9394,12 @@ dependencies = [
"wasite",
]
+[[package]]
+name = "widestring"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
+
[[package]]
name = "winapi"
version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index 9214191cb..c21d1c50e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -60,7 +60,7 @@ async_zip = { version = "0.0.18", features = [
"deflate",
"zstd",
] }
-axum = "0.8.6"
+axum = { version = "0.8.6", features = ["macros"] }
axum-server = { version = "0.7.2", features = ["tls-rustls"] }
base64 = "0.22.1"
bench-dashboard-frontend = { path = "core/bench/dashboard/frontend" }
@@ -97,6 +97,7 @@ env_logger = "0.11.8"
figlet-rs = "0.1.5"
figment = { version = "0.10.19", features = ["toml", "env"] }
flume = "0.11.1"
+async-channel = "2.3.1"
futures = "0.3.31"
futures-util = "0.3.31"
human-repr = "1.1.0"
@@ -114,17 +115,18 @@ mockall = "0.13.1"
nonzero_lit = "0.1.2"
once_cell = "1.21.3"
passterm = "=2.0.1"
-postcard = { version = "1.1.3", features = ["alloc"] }
predicates = "3.1.3"
-quinn = "0.11.9"
-rand = "0.9.2"
+quinn = "0.11.8"
+postcard = { version = "1.1.1", features = ["alloc"] }
+rand = "0.9.1"
regex = "1.12.2"
-reqwest = { version = "0.12.24", default-features = false, features = [
+reqwest = { version = "0.12.20", default-features = false, features = [
"json",
"rustls-tls",
] }
reqwest-middleware = { version = "0.4.2", features = ["json"] }
reqwest-retry = "0.7.0"
+serde_yml = "0.0.12"
rust-s3 = { version = "0.37.0", default-features = false, features = [
"tokio-rustls-tls",
"tags",
@@ -136,17 +138,39 @@ serde_with = { version = "3.15.1", features = ["base64", "macros"] }
serde_yaml_ng = "0.10.0"
serial_test = "3.2.0"
server = { path = "core/server" }
-simd-json = { version = "0.17.0", features = ["serde_impl"] }
+simd-json = { version = "0.15.1", features = ["serde_impl"] }
strum = { version = "0.27.2", features = ["derive"] }
strum_macros = "0.27.2"
-sysinfo = "0.37.2"
-tempfile = "3.23.0"
+sysinfo = "0.37.0"
+tempfile = "3.21.0"
test-case = "3.3.1"
-thiserror = "2.0.17"
-tokio = { version = "1.48.0", features = ["full"] }
-tokio-rustls = "0.26.4"
+thiserror = "2.0.16"
+tokio = { version = "1.47.1", features = ["full"] }
+compio = { git = "https://github.com/compio-rs/compio.git", rev = "fe4243f0b6811ebc325afd081c9b087b4d9817be", features = [
+ "runtime",
+ "macros",
+ "io-uring",
+ "time",
+ "rustls",
+] }
+cyper = { git = "https://github.com/krishvishal/cyper.git", rev = "cd75e266df6ab0a9b9474eb7dda1735650d17db6", features = [
+ "rustls",
+], default-features = false }
+cyper-axum = { git = "https://github.com/krishvishal/cyper", rev = "cd75e266df6ab0a9b9474eb7dda1735650d17db6" }
+compio-net = { git = "https://github.com/compio-rs/compio.git", rev = "fe4243f0b6811ebc325afd081c9b087b4d9817be" }
+compio-quic = { git = "https://github.com/compio-rs/compio.git", rev = "fe4243f0b6811ebc325afd081c9b087b4d9817be" }
+compio-tls = { git = "https://github.com/compio-rs/compio.git", rev = "fe4243f0b6811ebc325afd081c9b087b4d9817be", features = [
+ "rustls",
+] }
+compio-io = { git = "https://github.com/compio-rs/compio.git", rev = "fe4243f0b6811ebc325afd081c9b087b4d9817be" }
+compio-ws = { git = "https://github.com/krishvishal/compio-ws", features = [
+ "rustls",
+] }
+tungstenite = "0.27.0"
+tokio-tungstenite = { version = "0.27", features = ["rustls-tls-webpki-roots"] }
+tokio-rustls = "0.26.2"
tokio-util = { version = "0.7.16", features = ["compat"] }
-toml = "0.9.8"
+toml = "0.9.5"
tower-http = { version = "0.6.6", features = [
"add-extension",
"cors",
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index 503423dfc..983f95ab9 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -276,8 +276,8 @@ getrandom: 0.3.4, "Apache-2.0 OR MIT",
ghash: 0.5.1, "Apache-2.0 OR MIT",
gherkin: 0.14.0, "Apache-2.0 OR MIT",
git2: 0.20.2, "Apache-2.0 OR MIT",
-glob: 0.3.3, "Apache-2.0 OR MIT",
-globset: 0.4.18, "MIT OR Unlicense",
+glob: 0.3.2, "Apache-2.0 OR MIT",
+globset: 0.4.16, "MIT OR Unlicense",
globwalk: 0.9.1, "MIT",
gloo: 0.8.1, "Apache-2.0 OR MIT",
gloo: 0.10.0, "Apache-2.0 OR MIT",
@@ -479,7 +479,6 @@ num-integer: 0.1.46, "Apache-2.0 OR MIT",
num-iter: 0.1.45, "Apache-2.0 OR MIT",
num-modular: 0.6.1, "Apache-2.0",
num-order: 1.2.0, "Apache-2.0",
-num-rational: 0.4.2, "Apache-2.0 OR MIT",
num-traits: 0.2.19, "Apache-2.0 OR MIT",
num_cpus: 1.17.0, "Apache-2.0 OR MIT",
num_threads: 0.1.7, "Apache-2.0 OR MIT",
diff --git a/helm/charts/iggy/templates/iggy-pvc.yaml b/Dockerfile.connectors.ci
similarity index 51%
copy from helm/charts/iggy/templates/iggy-pvc.yaml
copy to Dockerfile.connectors.ci
index c1a5e8cb5..83d9986a5 100644
--- a/helm/charts/iggy/templates/iggy-pvc.yaml
+++ b/Dockerfile.connectors.ci
@@ -15,29 +15,30 @@
# specific language governing permissions and limitations
# under the License.
-{{- if and .Values.server.persistence.enabled (not .Values.server.persistence.existingClaim) }}
-kind: PersistentVolumeClaim
-apiVersion: v1
-metadata:
- name: {{ template "iggy.fullname" . }}
- labels:
- {{- include "iggy.labels" . | nindent 8 }}
- annotations:
- helm.sh/resource-policy: keep
- {{- with .Values.server.persistence.annotations }}
- {{- toYaml . | nindent 4 }}
- {{- end }}
-spec:
- accessModes:
- - {{ .Values.server.persistence.accessMode | quote }}
- resources:
- requests:
- storage: {{ .Values.server.persistence.size | quote }}
- {{- with .Values.server.persistence.storageClass }}
- {{- if (eq "-" .) }}
- storageClassName: ""
- {{- else }}
- storageClassName: "{{ . }}"
- {{- end }}
- {{- end }}
-{{- end }}
+<<<<<<<< HEAD:Dockerfile.connectors.ci
+FROM alpine:latest
+
+ARG IGGY_CONNECTORS_PATH
+RUN test -n "$IGGY_CONNECTORS_PATH" || (echo "IGGY_CONNECTORS_PATH not set" && false)
+
+WORKDIR /iggy
+
+COPY ./core/connectors/runtime/config.toml ./config.toml
+COPY ${IGGY_CONNECTORS_PATH} ./
+
+RUN chmod +x ./iggy-connectors
+
+RUN ln -sf /iggy/iggy-connectors /usr/bin/iggy-connectors
+
+CMD ["/iggy/iggy-connectors"]
+========
+FROM mcr.microsoft.com/dotnet/sdk:8.0
+
+COPY ./foreign/csharp ./foreign/csharp
+COPY ./bdd/scenarios ./bdd/scenarios
+
+WORKDIR /foreign/csharp/Iggy_SDK.Tests.BDD
+RUN dotnet build -c Release
+
+CMD ["dotnet", "test"]
+>>>>>>>> master:bdd/csharp/Dockerfile
diff --git a/helm/charts/iggy/templates/iggy-pvc.yaml b/Dockerfile.connectors.ci.dockerignore
similarity index 51%
copy from helm/charts/iggy/templates/iggy-pvc.yaml
copy to Dockerfile.connectors.ci.dockerignore
index c1a5e8cb5..e2fdd5f0d 100644
--- a/helm/charts/iggy/templates/iggy-pvc.yaml
+++ b/Dockerfile.connectors.ci.dockerignore
@@ -15,29 +15,16 @@
# specific language governing permissions and limitations
# under the License.
-{{- if and .Values.server.persistence.enabled (not .Values.server.persistence.existingClaim) }}
-kind: PersistentVolumeClaim
-apiVersion: v1
-metadata:
- name: {{ template "iggy.fullname" . }}
- labels:
- {{- include "iggy.labels" . | nindent 8 }}
- annotations:
- helm.sh/resource-policy: keep
- {{- with .Values.server.persistence.annotations }}
- {{- toYaml . | nindent 4 }}
- {{- end }}
-spec:
- accessModes:
- - {{ .Values.server.persistence.accessMode | quote }}
- resources:
- requests:
- storage: {{ .Values.server.persistence.size | quote }}
- {{- with .Values.server.persistence.storageClass }}
- {{- if (eq "-" .) }}
- storageClassName: ""
- {{- else }}
- storageClassName: "{{ . }}"
- {{- end }}
- {{- end }}
-{{- end }}
+.config
+.github
+/assets
+/local_data
+/licenses
+/certs
+/helm
+/web
+Dockerfile
+docker-compose.yml
+.dockerignore
+.git
+.gitignore
diff --git a/helm/charts/iggy/templates/iggy-pvc.yaml b/Dockerfile.mcp.ci.dockerignore
similarity index 51%
copy from helm/charts/iggy/templates/iggy-pvc.yaml
copy to Dockerfile.mcp.ci.dockerignore
index c1a5e8cb5..e2fdd5f0d 100644
--- a/helm/charts/iggy/templates/iggy-pvc.yaml
+++ b/Dockerfile.mcp.ci.dockerignore
@@ -15,29 +15,16 @@
# specific language governing permissions and limitations
# under the License.
-{{- if and .Values.server.persistence.enabled (not .Values.server.persistence.existingClaim) }}
-kind: PersistentVolumeClaim
-apiVersion: v1
-metadata:
- name: {{ template "iggy.fullname" . }}
- labels:
- {{- include "iggy.labels" . | nindent 8 }}
- annotations:
- helm.sh/resource-policy: keep
- {{- with .Values.server.persistence.annotations }}
- {{- toYaml . | nindent 4 }}
- {{- end }}
-spec:
- accessModes:
- - {{ .Values.server.persistence.accessMode | quote }}
- resources:
- requests:
- storage: {{ .Values.server.persistence.size | quote }}
- {{- with .Values.server.persistence.storageClass }}
- {{- if (eq "-" .) }}
- storageClassName: ""
- {{- else }}
- storageClassName: "{{ . }}"
- {{- end }}
- {{- end }}
-{{- end }}
+.config
+.github
+/assets
+/local_data
+/licenses
+/certs
+/helm
+/web
+Dockerfile
+docker-compose.yml
+.dockerignore
+.git
+.gitignore
diff --git a/bdd/docker-compose.yml b/bdd/docker-compose.yml
index ab85afff3..86bbc9fa9 100644
--- a/bdd/docker-compose.yml
+++ b/bdd/docker-compose.yml
@@ -27,7 +27,21 @@ services:
PREBUILT_IGGY_CLI: ${IGGY_CLI_PATH:-target/debug/iggy}
LIBC: glibc
PROFILE: debug
- command: ["--fresh"]
+ command: ["--fresh", "--with-default-root-credentials"]
+ cap_add:
+ - SYS_NICE
+ security_opt:
+ - seccomp:unconfined
+ ulimits:
+ memlock:
+ soft: -1
+ hard: -1
+ healthcheck:
+ test: ["CMD", "/usr/local/bin/iggy", "ping"]
+ interval: 1s
+ timeout: 3s
+ retries: 30
+ start_period: 2s
environment:
- IGGY_ROOT_USERNAME=iggy
- IGGY_ROOT_PASSWORD=iggy
@@ -105,7 +119,8 @@ services:
context: ..
dockerfile: bdd/node/Dockerfile
depends_on:
- - iggy-server
+ iggy-server:
+ condition: service_healthy
environment:
- IGGY_ROOT_USERNAME=iggy
- IGGY_ROOT_PASSWORD=iggy
diff --git a/bdd/go/tests/tcp_test/stream_steps.go b/bdd/go/tests/tcp_test/stream_steps.go
index 50f0f4dfa..32729b273 100644
--- a/bdd/go/tests/tcp_test/stream_steps.go
+++ b/bdd/go/tests/tcp_test/stream_steps.go
@@ -19,7 +19,6 @@ package tcp_test
import (
"fmt"
-
iggcon "github.com/apache/iggy/foreign/go/contracts"
ierror "github.com/apache/iggy/foreign/go/errors"
"github.com/apache/iggy/foreign/go/iggycli"
diff --git a/bdd/python/tests/test_basic_messaging.py b/bdd/python/tests/test_basic_messaging.py
index 9abd177c9..ea5cc8ed5 100644
--- a/bdd/python/tests/test_basic_messaging.py
+++ b/bdd/python/tests/test_basic_messaging.py
@@ -65,12 +65,12 @@ def no_streams_in_system(context):
pass
-@when(parsers.parse('I create a stream with ID {stream_id:d} and name {stream_name}'))
-def create_stream(context, stream_id, stream_name):
- """Create a stream with specified ID and name"""
+@when(parsers.parse('I create a stream with name {stream_name}'))
+def create_stream(context, stream_name):
+ """Create a stream with specified name"""
async def _create():
- await context.client.create_stream(name=stream_name, stream_id=stream_id)
- context.last_stream_id = stream_id
+ stream = await context.client.create_stream(name=stream_name)
+ context.last_stream_id = stream.id
context.last_stream_name = stream_name
asyncio.run(_create())
@@ -86,29 +86,27 @@ def stream_created_successfully(context):
asyncio.run(_verify())
-@then(parsers.parse('the stream should have ID {stream_id:d} and name {stream_name}'))
-def verify_stream_properties(context, stream_id, stream_name):
- """Verify stream has correct ID and name"""
+@then(parsers.parse('the stream should have name {stream_name}'))
+def verify_stream_properties(context, stream_name):
+ """Verify stream has correct name"""
async def _verify():
stream = await context.client.get_stream(stream_name)
assert stream is not None
- assert stream.id == stream_id
assert stream.name == stream_name
asyncio.run(_verify())
-@when(parsers.parse('I create a topic with ID {topic_id:d} and name {topic_name} in stream {stream_id:d} with {partitions:d} partitions'))
-def create_topic(context, topic_id, topic_name, stream_id, partitions):
+@when(parsers.parse('I create a topic with name {topic_name} in stream {stream_id:d} with {partitions:d} partitions'))
+def create_topic(context, topic_name, stream_id, partitions):
"""Create a topic with specified parameters"""
async def _create():
- await context.client.create_topic(
+ topic = await context.client.create_topic(
stream=stream_id,
name=topic_name,
- partitions_count=partitions,
- topic_id=topic_id
+ partitions_count=partitions
)
- context.last_topic_id = topic_id
+ context.last_topic_id = topic.id
context.last_topic_name = topic_name
context.last_topic_partitions = partitions
@@ -125,13 +123,12 @@ def topic_created_successfully(context):
asyncio.run(_verify())
-@then(parsers.parse('the topic should have ID {topic_id:d} and name {topic_name}'))
-def verify_topic_properties(context, topic_id, topic_name):
- """Verify topic has correct ID and name"""
+@then(parsers.parse('the topic should have name {topic_name}'))
+def verify_topic_properties(context, topic_name):
+ """Verify topic has correct name"""
async def _verify():
topic = await context.client.get_topic(context.last_stream_id, topic_name)
assert topic is not None
- assert topic.id == topic_id
assert topic.name == topic_name
asyncio.run(_verify())
diff --git a/bdd/rust/tests/steps/streams.rs b/bdd/rust/tests/steps/streams.rs
index f64f680ec..f24a67919 100644
--- a/bdd/rust/tests/steps/streams.rs
+++ b/bdd/rust/tests/steps/streams.rs
@@ -33,11 +33,11 @@ pub async fn given_no_streams(world: &mut GlobalContext) {
);
}
-#[when(regex = r"^I create a stream with ID (\d+) and name (.+)$")]
-pub async fn when_create_stream(world: &mut GlobalContext, stream_id: u32, stream_name: String) {
+#[when(regex = r"^I create a stream with name (.+)$")]
+pub async fn when_create_stream(world: &mut GlobalContext, stream_name: String) {
let client = world.client.as_ref().expect("Client should be available");
let stream = client
- .create_stream(&stream_name, Some(stream_id))
+ .create_stream(&stream_name)
.await
.expect("Should be able to create stream");
@@ -53,18 +53,12 @@ pub async fn then_stream_created_successfully(world: &mut GlobalContext) {
);
}
-#[then(regex = r"^the stream should have ID (\d+) and name (.+)$")]
-pub async fn then_stream_has_id_and_name(
- world: &mut GlobalContext,
- expected_id: u32,
- expected_name: String,
-) {
- let stream_id = world.last_stream_id.expect("Stream should exist");
+#[then(regex = r"^the stream should have name (.+)$")]
+pub async fn then_stream_has_name(world: &mut GlobalContext, expected_name: String) {
let stream_name = world
.last_stream_name
.as_ref()
.expect("Stream should exist");
- assert_eq!(stream_id, expected_id, "Stream should have expected ID");
assert_eq!(
stream_name, &expected_name,
"Stream should have expected name"
diff --git a/bdd/rust/tests/steps/topics.rs b/bdd/rust/tests/steps/topics.rs
index 93557663b..63150f326 100644
--- a/bdd/rust/tests/steps/topics.rs
+++ b/bdd/rust/tests/steps/topics.rs
@@ -20,12 +20,9 @@ use crate::common::global_context::GlobalContext;
use cucumber::{then, when};
use iggy::prelude::{CompressionAlgorithm, Identifier, IggyExpiry, MaxTopicSize, TopicClient};
-#[when(
- regex = r"^I create a topic with ID (\d+) and name (.+) in stream (\d+) with (\d+) partitions$"
-)]
+#[when(regex = r"^I create a topic with name (.+) in stream (\d+) with (\d+) partitions$")]
pub async fn when_create_topic(
world: &mut GlobalContext,
- topic_id: u32,
topic_name: String,
stream_id: u32,
partitions_count: u32,
@@ -38,7 +35,6 @@ pub async fn when_create_topic(
partitions_count,
CompressionAlgorithm::default(),
None,
- Some(topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
@@ -58,15 +54,9 @@ pub async fn then_topic_created_successfully(world: &mut GlobalContext) {
);
}
-#[then(regex = r"^the topic should have ID (\d+) and name (.+)$")]
-pub async fn then_topic_has_id_and_name(
- world: &mut GlobalContext,
- expected_id: u32,
- expected_name: String,
-) {
- let topic_id = world.last_topic_id.expect("Topic should exist");
+#[then(regex = r"^the topic should have name (.+)$")]
+pub async fn then_topic_has_name(world: &mut GlobalContext, expected_name: String) {
let topic_name = world.last_topic_name.as_ref().expect("Topic should exist");
- assert_eq!(topic_id, expected_id, "Topic should have expected ID");
assert_eq!(
topic_name, &expected_name,
"Topic should have expected name"
diff --git a/bdd/scenarios/basic_messaging.feature b/bdd/scenarios/basic_messaging.feature
index 1709626e1..c13109dc8 100644
--- a/bdd/scenarios/basic_messaging.feature
+++ b/bdd/scenarios/basic_messaging.feature
@@ -26,19 +26,19 @@ Feature: Basic Messaging Operations
Scenario: Create stream and send messages
Given I have no streams in the system
- When I create a stream with ID 1 and name "test-stream"
+ When I create a stream with name "test-stream"
Then the stream should be created successfully
- And the stream should have ID 1 and name "test-stream"
+ And the stream should have name "test-stream"
- When I create a topic with ID 1 and name "test-topic" in stream 1 with 3 partitions
+ When I create a topic with name "test-topic" in stream 0 with 3 partitions
Then the topic should be created successfully
- And the topic should have ID 1 and name "test-topic"
+ And the topic should have name "test-topic"
And the topic should have 3 partitions
- When I send 10 messages to stream 1, topic 1, partition 1
+ When I send 10 messages to stream 0, topic 0, partition 0
Then all messages should be sent successfully
- When I poll messages from stream 1, topic 1, partition 1 starting from offset 0
+ When I poll messages from stream 0, topic 0, partition 0 starting from offset 0
Then I should receive 10 messages
And the messages should have sequential offsets from 0 to 9
And each message should have the expected payload content
diff --git a/core/ai/mcp/src/service/mod.rs b/core/ai/mcp/src/service/mod.rs
index 24d28c0c0..166e839d0 100644
--- a/core/ai/mcp/src/service/mod.rs
+++ b/core/ai/mcp/src/service/mod.rs
@@ -79,10 +79,10 @@ impl IggyService {
#[tool(description = "Create stream")]
pub async fn create_stream(
&self,
- Parameters(CreateStream { name, stream_id }): Parameters<CreateStream>,
+ Parameters(CreateStream { name }): Parameters<CreateStream>,
) -> Result<CallToolResult, ErrorData> {
self.permissions.ensure_create()?;
- request(self.client.create_stream(&name, stream_id).await)
+ request(self.client.create_stream(&name).await)
}
#[tool(description = "Update stream")]
@@ -146,7 +146,6 @@ impl IggyService {
partitions_count,
compression_algorithm,
replication_factor,
- topic_id,
message_expiry,
max_size,
}): Parameters<CreateTopic>,
@@ -167,7 +166,6 @@ impl IggyService {
partitions_count,
compression_algorithm,
replication_factor,
- topic_id,
message_expiry,
max_size,
)
@@ -501,13 +499,12 @@ impl IggyService {
stream_id,
topic_id,
name,
- group_id,
}): Parameters<CreateConsumerGroup>,
) -> Result<CallToolResult, ErrorData> {
self.permissions.ensure_create()?;
request(
self.client
- .create_consumer_group(&id(&stream_id)?, &id(&topic_id)?, &name, group_id)
+ .create_consumer_group(&id(&stream_id)?, &id(&topic_id)?, &name)
.await,
)
}
diff --git a/core/ai/mcp/src/service/requests.rs b/core/ai/mcp/src/service/requests.rs
index 1a8aea5c1..a7cbfb05d 100644
--- a/core/ai/mcp/src/service/requests.rs
+++ b/core/ai/mcp/src/service/requests.rs
@@ -32,8 +32,6 @@ pub struct GetStream {
pub struct CreateStream {
#[schemars(description = "stream name (required, must be unique)")]
pub name: String,
- #[schemars(description = "stream identifier (numeric, optional)")]
- pub stream_id: Option<u32>,
}
#[derive(Debug, Deserialize, JsonSchema)]
@@ -88,9 +86,6 @@ pub struct CreateTopic {
#[schemars(description = "replication factor (optional, must be greater than 0)")]
pub replication_factor: Option<u8>,
- #[schemars(description = "topic identifier (numeric, optional)")]
- pub topic_id: Option<u32>,
-
#[schemars(description = "message expiry (optional)")]
pub message_expiry: Option<String>,
@@ -282,9 +277,6 @@ pub struct CreateConsumerGroup {
#[schemars(description = "consumer group name (required, must be unique)")]
pub name: String,
-
- #[schemars(description = "consumer group identifier (optional, number)")]
- pub group_id: Option<u32>,
}
#[derive(Debug, Deserialize, JsonSchema)]
@@ -419,7 +411,7 @@ pub struct Permissions {
pub global: Option<GlobalPermissions>,
#[schemars(description = "stream permissions (optional)")]
- pub streams: Option<HashMap<u32, StreamPermissions>>,
+ pub streams: Option<HashMap<usize, StreamPermissions>>,
}
#[derive(Debug, Deserialize, JsonSchema)]
@@ -476,7 +468,7 @@ pub struct StreamPermissions {
pub send_messages: Option<bool>,
#[schemars(description = "topics permissions (optional)")]
- pub topics: Option<HashMap<u32, TopicPermissions>>,
+ pub topics: Option<HashMap<usize, TopicPermissions>>,
}
#[derive(Debug, Deserialize, JsonSchema)]
diff --git a/core/bench/report/src/types/transport.rs b/core/bench/report/src/types/transport.rs
index 73f036646..36bb5074d 100644
--- a/core/bench/report/src/types/transport.rs
+++ b/core/bench/report/src/types/transport.rs
@@ -31,4 +31,7 @@ pub enum BenchmarkTransport {
#[display("QUIC")]
#[serde(rename = "quic")]
Quic,
+ #[display("WEBSOCKET")]
+ #[serde(rename = "websocket")]
+ WebSocket,
}
diff --git a/core/bench/src/actors/consumer/client/high_level.rs b/core/bench/src/actors/consumer/client/high_level.rs
index 362fd8085..152b0ffbf 100644
--- a/core/bench/src/actors/consumer/client/high_level.rs
+++ b/core/bench/src/actors/consumer/client/high_level.rs
@@ -101,18 +101,17 @@ impl ConsumerClient for HighLevelConsumerClient {
impl BenchmarkInit for HighLevelConsumerClient {
async fn setup(&mut self) -> Result<(), IggyError> {
- let topic_id = 1;
+ let topic_id_str = "topic-1";
let client = self.client_factory.create_client().await;
let client = IggyClient::create(client, None, None);
login_root(&client).await;
let stream_id_str = self.config.stream_id.to_string();
- let topic_id_str = topic_id.to_string();
let mut consumer = if let Some(cg_id) = self.config.consumer_group_id {
let consumer_group_name = format!("cg_{cg_id}");
client
- .consumer_group(&consumer_group_name, &stream_id_str, &topic_id_str)?
+ .consumer_group(&consumer_group_name, &stream_id_str, topic_id_str)?
.batch_length(self.config.messages_per_batch.get())
.auto_commit(AutoCommit::When(AutoCommitWhen::PollingMessages))
.create_consumer_group_if_not_exists()
@@ -126,8 +125,8 @@ impl BenchmarkInit for HighLevelConsumerClient {
.consumer(
&format!("hl_consumer_{}", self.config.consumer_id),
&stream_id_str,
- &topic_id_str,
- 1,
+ topic_id_str,
+ 0,
)?
.polling_strategy(PollingStrategy::offset(0))
.batch_length(self.config.messages_per_batch.get())
diff --git a/core/bench/src/actors/consumer/client/interface.rs b/core/bench/src/actors/consumer/client/interface.rs
index d1179a732..2dd004512 100644
--- a/core/bench/src/actors/consumer/client/interface.rs
+++ b/core/bench/src/actors/consumer/client/interface.rs
@@ -25,7 +25,7 @@ use crate::actors::{ApiLabel, BatchMetrics, BenchmarkInit};
pub struct BenchmarkConsumerConfig {
pub consumer_id: u32,
pub consumer_group_id: Option<u32>,
- pub stream_id: u32,
+ pub stream_id: String,
pub messages_per_batch: BenchmarkNumericParameter,
pub warmup_time: IggyDuration,
pub polling_kind: PollingKind,
diff --git a/core/bench/src/actors/consumer/client/low_level.rs b/core/bench/src/actors/consumer/client/low_level.rs
index 3217a6037..27feb3a4e 100644
--- a/core/bench/src/actors/consumer/client/low_level.rs
+++ b/core/bench/src/actors/consumer/client/low_level.rs
@@ -89,7 +89,7 @@ impl ConsumerClient for LowLevelConsumerClient {
if polled.messages.is_empty() {
return Ok(None);
}
- let message_count = u32::try_from(polled.messages.len()).unwrap();
+ let messages_count = polled.messages.len() as u64;
let latency = if self.config.origin_timestamp_latency_calculation {
let now = IggyTimestamp::now().as_micros();
Duration::from_micros(now - polled.messages[0].header.origin_timestamp)
@@ -100,10 +100,13 @@ impl ConsumerClient for LowLevelConsumerClient {
let user_bytes = batch_user_size_bytes(&polled);
let total_bytes = batch_total_size_bytes(&polled);
- self.offset += polled.messages.len() as u64;
+ self.offset += messages_count;
+ if self.polling_strategy.kind == PollingKind::Offset {
+ self.polling_strategy.value += messages_count;
+ }
Ok(Some(BatchMetrics {
- messages: message_count,
+ messages: messages_count.try_into().unwrap(),
user_data_bytes: user_bytes,
total_bytes,
latency,
@@ -113,15 +116,15 @@ impl ConsumerClient for LowLevelConsumerClient {
impl BenchmarkInit for LowLevelConsumerClient {
async fn setup(&mut self) -> Result<(), IggyError> {
- let topic_id = 1u32;
- let default_partition_id = 1u32;
+ let topic_id_str = "topic-1";
+ let default_partition_id = 0u32;
let client = self.client_factory.create_client().await;
let client = IggyClient::create(client, None, None);
login_root(&client).await;
- let stream_id = self.config.stream_id.try_into().unwrap();
- let topic_id = topic_id.try_into().unwrap();
+ let stream_id: Identifier = self.config.stream_id.as_str().try_into().unwrap();
+ let topic_id = topic_id_str.try_into().unwrap();
let partition_id = if self.config.consumer_group_id.is_some() {
None
} else {
diff --git a/core/bench/src/actors/consumer/typed_benchmark_consumer.rs b/core/bench/src/actors/consumer/typed_benchmark_consumer.rs
index fca492ea3..723adc35b 100644
--- a/core/bench/src/actors/consumer/typed_benchmark_consumer.rs
+++ b/core/bench/src/actors/consumer/typed_benchmark_consumer.rs
@@ -48,7 +48,7 @@ impl TypedBenchmarkConsumer {
benchmark_kind: BenchmarkKind,
consumer_id: u32,
consumer_group_id: Option<u32>,
- stream_id: u32,
+ stream_id: String,
messages_per_batch: BenchmarkNumericParameter,
finish_condition: Arc<BenchmarkFinishCondition>,
warmup_time: IggyDuration,
diff --git a/core/bench/src/actors/producer/client/interface.rs b/core/bench/src/actors/producer/client/interface.rs
index f2dd68232..d48f7ec31 100644
--- a/core/bench/src/actors/producer/client/interface.rs
+++ b/core/bench/src/actors/producer/client/interface.rs
@@ -26,7 +26,7 @@ use iggy::prelude::*;
#[derive(Debug, Clone)]
pub struct BenchmarkProducerConfig {
pub producer_id: u32,
- pub stream_id: u32,
+ pub stream_id: String,
pub partitions: u32,
pub messages_per_batch: BenchmarkNumericParameter,
pub message_size: BenchmarkNumericParameter,
diff --git a/core/bench/src/actors/producer/client/low_level.rs b/core/bench/src/actors/producer/client/low_level.rs
index c657f3545..fc2f483ed 100644
--- a/core/bench/src/actors/producer/client/low_level.rs
+++ b/core/bench/src/actors/producer/client/low_level.rs
@@ -49,7 +49,7 @@ impl LowLevelProducerClient {
client: None,
stream_id: Identifier::default(),
topic_id: Identifier::default(),
- partitioning: Partitioning::partition_id(1),
+ partitioning: Partitioning::partition_id(0),
}
}
}
@@ -87,7 +87,7 @@ impl ProducerClient for LowLevelProducerClient {
impl BenchmarkInit for LowLevelProducerClient {
async fn setup(&mut self) -> Result<(), IggyError> {
- let default_partition_id = 1;
+ let default_partition_id = 0u32;
let partitions = self.config.partitions;
let client = self.client_factory.create_client().await;
@@ -102,8 +102,8 @@ impl BenchmarkInit for LowLevelProducerClient {
self.client = Some(client);
self.partitioning = partitioning;
- self.stream_id = self.config.stream_id.try_into()?;
- self.topic_id = Identifier::numeric(1)?;
+ self.stream_id = self.config.stream_id.as_str().try_into()?;
+ self.topic_id = Identifier::from_str_value("topic-1")?;
Ok(())
}
}
diff --git a/core/bench/src/actors/producer/typed_benchmark_producer.rs b/core/bench/src/actors/producer/typed_benchmark_producer.rs
index ee2de4833..2a27fd33a 100644
--- a/core/bench/src/actors/producer/typed_benchmark_producer.rs
+++ b/core/bench/src/actors/producer/typed_benchmark_producer.rs
@@ -46,7 +46,7 @@ impl TypedBenchmarkProducer {
client_factory: Arc<dyn ClientFactory>,
benchmark_kind: BenchmarkKind,
producer_id: u32,
- stream_id: u32,
+ stream_id: String,
partitions: u32,
messages_per_batch: BenchmarkNumericParameter,
message_size: BenchmarkNumericParameter,
diff --git a/core/bench/src/actors/producing_consumer/typed_banchmark_producing_consumer.rs b/core/bench/src/actors/producing_consumer/typed_banchmark_producing_consumer.rs
index 26e3e0fb6..ac7e121b6 100644
--- a/core/bench/src/actors/producing_consumer/typed_banchmark_producing_consumer.rs
+++ b/core/bench/src/actors/producing_consumer/typed_banchmark_producing_consumer.rs
@@ -52,7 +52,7 @@ impl TypedBenchmarkProducingConsumer {
benchmark_kind: BenchmarkKind,
actor_id: u32,
consumer_group_id: Option<u32>,
- stream_id: u32,
+ stream_id: String,
partitions_count: u32,
messages_per_batch: BenchmarkNumericParameter,
message_size: BenchmarkNumericParameter,
@@ -67,7 +67,7 @@ impl TypedBenchmarkProducingConsumer {
) -> Self {
let producer_config = BenchmarkProducerConfig {
producer_id: actor_id,
- stream_id,
+ stream_id: stream_id.clone(),
partitions: partitions_count,
messages_per_batch,
message_size,
diff --git a/core/bench/src/args/common.rs b/core/bench/src/args/common.rs
index f7b1c4465..6dd36b20c 100644
--- a/core/bench/src/args/common.rs
+++ b/core/bench/src/args/common.rs
@@ -23,8 +23,7 @@ use super::{
defaults::{
DEFAULT_MESSAGE_BATCHES, DEFAULT_MESSAGE_SIZE, DEFAULT_MESSAGES_PER_BATCH,
DEFAULT_MOVING_AVERAGE_WINDOW, DEFAULT_PERFORM_CLEANUP, DEFAULT_SAMPLING_TIME,
- DEFAULT_SERVER_STDOUT_VISIBILITY, DEFAULT_SKIP_SERVER_START, DEFAULT_START_STREAM_ID,
- DEFAULT_WARMUP_TIME,
+ DEFAULT_SERVER_STDOUT_VISIBILITY, DEFAULT_SKIP_SERVER_START, DEFAULT_WARMUP_TIME,
},
transport::BenchmarkTransportCommand,
};
@@ -64,10 +63,6 @@ pub struct IggyBenchArgs {
#[arg(long, short = 'T', group = "data_to_process")]
pub total_data: Option<IggyByteSize>,
- /// Start stream id
- #[arg(long, short = 'S', default_value_t = DEFAULT_START_STREAM_ID)]
- pub start_stream_id: NonZeroU32,
-
/// Optional total rate limit (aggregate, for all actors)
/// Accepts human-readable formats like "50KB", "10MB", or "1GB"
#[arg(long, short = 'r', verbatim_doc_comment)]
@@ -137,10 +132,6 @@ impl IggyBenchArgs {
.server_address()
}
- pub const fn start_stream_id(&self) -> u32 {
- self.start_stream_id.get()
- }
-
pub fn validate(&mut self) {
let server_address = self.server_address().parse::<SocketAddr>().unwrap();
if (self.cleanup || self.verbose) && !server_address.ip().is_loopback() {
@@ -369,6 +360,7 @@ impl IggyBenchArgs {
BenchmarkTransportCommand::Tcp(_) => "tcp",
BenchmarkTransportCommand::Quic(_) => "quic",
BenchmarkTransportCommand::Http(_) => "http",
+ BenchmarkTransportCommand::WebSocket(_) => "ws",
};
let actors = match &self.benchmark_kind {
diff --git a/core/bench/src/args/defaults.rs b/core/bench/src/args/defaults.rs
index 2e3f2b1e1..7695782fb 100644
--- a/core/bench/src/args/defaults.rs
+++ b/core/bench/src/args/defaults.rs
@@ -29,11 +29,12 @@ pub const DEFAULT_QUIC_SERVER_ADDRESS: &str = "127.0.0.1:8080";
pub const DEFAULT_QUIC_SERVER_NAME: &str = "localhost";
pub const DEFAULT_QUIC_VALIDATE_CERTIFICATE: bool = false;
+pub const DEFAULT_WEBSOCKET_SERVER_ADDRESS: &str = "127.0.0.1:8092";
+
pub const DEFAULT_MESSAGES_PER_BATCH: NonZeroU32 = u32!(1000);
pub const DEFAULT_MESSAGE_BATCHES: NonZeroU32 = u32!(1000);
pub const DEFAULT_MESSAGE_SIZE: NonZeroU32 = u32!(1000);
pub const DEFAULT_TOTAL_MESSAGES_SIZE: IggyByteSize = IggyByteSize::new(8_000_000);
-pub const DEFAULT_START_STREAM_ID: NonZeroU32 = u32!(3_000_000);
pub const DEFAULT_PINNED_NUMBER_OF_STREAMS: NonZeroU32 = u32!(8);
pub const DEFAULT_BALANCED_NUMBER_OF_STREAMS: NonZeroU32 = u32!(1);
diff --git a/core/bench/src/args/transport.rs b/core/bench/src/args/transport.rs
index 7fa8ed42a..ab23ac356 100644
--- a/core/bench/src/args/transport.rs
+++ b/core/bench/src/args/transport.rs
@@ -19,6 +19,7 @@
use super::defaults::{
DEFAULT_HTTP_SERVER_ADDRESS, DEFAULT_QUIC_CLIENT_ADDRESS, DEFAULT_QUIC_SERVER_ADDRESS,
DEFAULT_QUIC_SERVER_NAME, DEFAULT_QUIC_VALIDATE_CERTIFICATE, DEFAULT_TCP_SERVER_ADDRESS,
+ DEFAULT_WEBSOCKET_SERVER_ADDRESS,
};
use super::{output::BenchmarkOutputCommand, props::BenchmarkTransportProps};
use clap::{Parser, Subcommand};
@@ -30,6 +31,8 @@ pub enum BenchmarkTransportCommand {
Http(HttpArgs),
Tcp(TcpArgs),
Quic(QuicArgs),
+ #[command(alias = "ws")]
+ WebSocket(WebSocketArgs),
}
impl Serialize for BenchmarkTransportCommand {
@@ -41,6 +44,7 @@ impl Serialize for BenchmarkTransportCommand {
Self::Http(_) => "http",
Self::Tcp(_) => "tcp",
Self::Quic(_) => "quic",
+ Self::WebSocket(_) => "websocket",
};
serializer.serialize_str(variant_str)
}
@@ -72,6 +76,7 @@ impl BenchmarkTransportProps for BenchmarkTransportCommand {
Self::Http(args) => args,
Self::Tcp(args) => args,
Self::Quic(args) => args,
+ Self::WebSocket(args) => args,
}
}
@@ -222,3 +227,40 @@ impl BenchmarkTransportProps for QuicArgs {
self.output.as_ref()
}
}
+
+#[derive(Parser, Debug, Clone)]
+pub struct WebSocketArgs {
+ /// Address of the WebSocket iggy-server
+ #[arg(long, default_value_t = DEFAULT_WEBSOCKET_SERVER_ADDRESS.to_owned())]
+ pub server_address: String,
+
+ /// Optional output command, used to output results (charts, raw json data) to a directory
+ #[command(subcommand)]
+ pub output: Option<BenchmarkOutputCommand>,
+}
+
+impl BenchmarkTransportProps for WebSocketArgs {
+ fn transport(&self) -> &TransportProtocol {
+ &TransportProtocol::WebSocket
+ }
+
+ fn server_address(&self) -> &str {
+ &self.server_address
+ }
+
+ fn validate_certificate(&self) -> bool {
+ panic!("Cannot validate certificate for WebSocket transport!")
+ }
+
+ fn client_address(&self) -> &str {
+ panic!("Setting client address for WebSocket transport is not supported!")
+ }
+
+ fn nodelay(&self) -> bool {
+ panic!("Setting nodelay for WebSocket transport is not supported!")
+ }
+
+ fn output_command(&self) -> Option<&BenchmarkOutputCommand> {
+ self.output.as_ref()
+ }
+}
diff --git a/core/bench/src/benchmarks/benchmark.rs b/core/bench/src/benchmarks/benchmark.rs
index 9b96a1b8c..31f1828c3 100644
--- a/core/bench/src/benchmarks/benchmark.rs
+++ b/core/bench/src/benchmarks/benchmark.rs
@@ -95,39 +95,36 @@ pub trait Benchmarkable: Send {
/// Initializes the streams and topics for the benchmark.
/// This method is called before the benchmark is executed.
async fn init_streams(&self) -> Result<(), IggyError> {
- let start_stream_id = self.args().start_stream_id();
let number_of_streams = self.args().streams();
- let topic_id: u32 = 1;
let partitions_count: u32 = self.args().number_of_partitions();
let client = self.client_factory().create_client().await;
let client = IggyClient::create(client, None, None);
login_root(&client).await;
let streams = client.get_streams().await?;
for i in 1..=number_of_streams {
- let stream_id = start_stream_id + i;
- if streams.iter().all(|s| s.id != stream_id) {
- info!("Creating the test stream {}", stream_id);
- let name = format!("stream {stream_id}");
- client.create_stream(&name, Some(stream_id)).await?;
- let name = format!("topic {topic_id}");
+ let stream_name = format!("bench-stream-{i}");
+ let stream_id: Identifier = stream_name.as_str().try_into()?;
+ if streams.iter().all(|s| s.name != stream_name) {
+ info!("Creating the test stream '{}'", stream_name);
+ client.create_stream(&stream_name).await?;
+ let topic_name = "topic-1".to_string();
let max_topic_size = self
.args()
.max_topic_size()
.map_or(MaxTopicSize::Unlimited, MaxTopicSize::Custom);
info!(
- "Creating the test topic {} for stream {} with max topic size: {:?}",
- topic_id, stream_id, max_topic_size
+ "Creating the test topic '{}' for stream '{}' with max topic size: {:?}",
+ topic_name, stream_name, max_topic_size
);
client
.create_topic(
- &stream_id.try_into()?,
- &name,
+ &stream_id,
+ &topic_name,
partitions_count,
CompressionAlgorithm::default(),
None,
- None,
IggyExpiry::NeverExpire,
max_topic_size,
)
@@ -138,17 +135,16 @@ pub trait Benchmarkable: Send {
}
async fn check_streams(&self) -> Result<(), IggyError> {
- let start_stream_id = self.args().start_stream_id();
let number_of_streams = self.args().streams();
let client = self.client_factory().create_client().await;
let client = IggyClient::create(client, None, None);
login_root(&client).await;
let streams = client.get_streams().await?;
for i in 1..=number_of_streams {
- let stream_id = start_stream_id + i;
- if streams.iter().all(|s| s.id != stream_id) {
+ let stream_name = format!("bench-stream-{i}");
+ if streams.iter().all(|s| s.name != stream_name) {
return Err(IggyError::ResourceNotFound(format!(
- "Streams for testing are not properly initialized. Stream with id: {stream_id} is missing."
+ "Streams for testing are not properly initialized. Stream '{stream_name}' is missing."
)));
}
}
diff --git a/core/bench/src/benchmarks/common.rs b/core/bench/src/benchmarks/common.rs
index d5436b562..da6878c0d 100644
--- a/core/bench/src/benchmarks/common.rs
+++ b/core/bench/src/benchmarks/common.rs
@@ -72,8 +72,6 @@ pub async fn init_consumer_groups(
client_factory: &Arc<dyn ClientFactory>,
args: &IggyBenchArgs,
) -> Result<(), IggyError> {
- let start_stream_id = args.start_stream_id();
- let topic_id: u32 = 1;
let client = client_factory.create_client().await;
let client = IggyClient::create(client, None, None);
let cg_count = args.number_of_consumer_groups();
@@ -81,31 +79,19 @@ pub async fn init_consumer_groups(
login_root(&client).await;
for i in 1..=cg_count {
let consumer_group_id = CONSUMER_GROUP_BASE_ID + i;
- let stream_id = start_stream_id + i;
+ let stream_name = format!("bench-stream-{i}");
+ let stream_id: Identifier = stream_name.as_str().try_into()?;
+ let topic_id: Identifier = "topic-1".try_into()?;
let consumer_group_name = format!("{CONSUMER_GROUP_NAME_PREFIX}-{consumer_group_id}");
info!(
- "Creating test consumer group: name={}, id={}, stream={}, topic={}",
- consumer_group_name, consumer_group_id, stream_id, topic_id
+ "Creating test consumer group: name={}, stream={}, topic={}",
+ consumer_group_name, stream_name, topic_id
);
- match client
- .create_consumer_group(
- &stream_id.try_into().unwrap(),
- &topic_id.try_into().unwrap(),
- &consumer_group_name,
- Some(consumer_group_id),
- )
+ if let Err(err) = client
+ .create_consumer_group(&stream_id, &topic_id, &consumer_group_name)
.await
{
- Err(IggyError::ConsumerGroupIdAlreadyExists(_, _)) => {
- info!(
- "Consumer group with id {} already exists",
- consumer_group_id
- );
- }
- Err(err) => {
- error!("Error when creating consumer group {consumer_group_id}: {err}");
- }
- Ok(_) => {}
+ error!("Error when creating consumer group {consumer_group_id}: {err}");
}
}
Ok(())
@@ -117,7 +103,6 @@ pub fn build_producer_futures(
) -> Vec<impl Future<Output = Result<BenchmarkIndividualMetrics, IggyError>> + Send + use<>> {
let streams = args.streams();
let partitions = args.number_of_partitions();
- let start_stream_id = args.start_stream_id();
let producers = args.producers();
let actors = args.producers() + args.consumers();
let warmup_time = args.warmup_time();
@@ -141,7 +126,8 @@ pub fn build_producer_futures(
BenchmarkFinishCondition::new(args, BenchmarkFinishConditionMode::PerProducer)
};
- let stream_id = start_stream_id + 1 + (producer_id % streams);
+ let stream_idx = 1 + ((producer_id - 1) % streams);
+ let stream_id = format!("bench-stream-{stream_idx}");
async move {
let producer = TypedBenchmarkProducer::new(
@@ -169,7 +155,6 @@ pub fn build_consumer_futures(
client_factory: &Arc<dyn ClientFactory>,
args: &IggyBenchArgs,
) -> Vec<impl Future<Output = Result<BenchmarkIndividualMetrics, IggyError>> + Send + use<>> {
- let start_stream_id = args.start_stream_id();
let cg_count = args.number_of_consumer_groups();
let consumers = args.consumers();
let actors = args.producers() + args.consumers();
@@ -204,11 +189,12 @@ pub fn build_consumer_futures(
} else {
BenchmarkFinishCondition::new(args, BenchmarkFinishConditionMode::PerConsumer)
};
- let stream_id = if cg_count > 0 {
- start_stream_id + 1 + (consumer_id % cg_count)
+ let stream_idx = if cg_count > 0 {
+ 1 + ((consumer_id - 1) % cg_count)
} else {
- start_stream_id + consumer_id
+ consumer_id
};
+ let stream_id = format!("bench-stream-{stream_idx}");
let consumer_group_id = if cg_count > 0 {
Some(CONSUMER_GROUP_BASE_ID + 1 + (consumer_id % cg_count))
} else {
@@ -249,14 +235,14 @@ pub fn build_producing_consumers_futures(
let warmup_time = args.warmup_time();
let messages_per_batch = args.messages_per_batch();
let message_size = args.message_size();
- let start_stream_id = args.start_stream_id();
let polling_kind = PollingKind::Offset;
(1..=producing_consumers)
.map(|actor_id| {
let client_factory_clone = client_factory.clone();
let args_clone = args.clone();
- let stream_id = start_stream_id + 1 + (actor_id % streams);
+ let stream_idx = 1 + ((actor_id - 1) % streams);
+ let stream_id = format!("bench-stream-{stream_idx}");
let send_finish_condition = BenchmarkFinishCondition::new(
&args,
@@ -320,7 +306,6 @@ pub fn build_producing_consumer_groups_futures(
let warmup_time = args.warmup_time();
let messages_per_batch = args.messages_per_batch();
let message_size = args.message_size();
- let start_stream_id = args.start_stream_id();
let start_consumer_group_id = CONSUMER_GROUP_BASE_ID;
let polling_kind = PollingKind::Next;
let use_high_level_api = args.high_level_api();
@@ -334,7 +319,8 @@ pub fn build_producing_consumer_groups_futures(
.map(|actor_id| {
let client_factory_clone = client_factory.clone();
let args_clone = args.clone();
- let stream_id = start_stream_id + 1 + (actor_id % cg_count);
+ let stream_idx = 1 + ((actor_id - 1) % cg_count);
+ let stream_id = format!("bench-stream-{stream_idx}");
let should_produce = actor_id <= producers;
let should_consume = actor_id <= consumers;
diff --git a/core/bench/src/utils/client_factory.rs b/core/bench/src/utils/client_factory.rs
index 010d276a5..513d22300 100644
--- a/core/bench/src/utils/client_factory.rs
+++ b/core/bench/src/utils/client_factory.rs
@@ -23,6 +23,7 @@ use integration::http_client::HttpClientFactory;
use integration::quic_client::QuicClientFactory;
use integration::tcp_client::TcpClientFactory;
use integration::test_server::ClientFactory;
+use integration::websocket_client::WebSocketClientFactory;
use std::sync::Arc;
pub fn create_client_factory(args: &IggyBenchArgs) -> Arc<dyn ClientFactory> {
@@ -48,5 +49,8 @@ pub fn create_client_factory(args: &IggyBenchArgs) -> Arc<dyn ClientFactory> {
TransportProtocol::Quic => Arc::new(QuicClientFactory {
server_addr: args.server_address().to_owned(),
}),
+ TransportProtocol::WebSocket => Arc::new(WebSocketClientFactory {
+ server_addr: args.server_address().to_owned(),
+ }),
}
}
diff --git a/core/bench/src/utils/mod.rs b/core/bench/src/utils/mod.rs
index 8c07a9f1b..2559a6a3a 100644
--- a/core/bench/src/utils/mod.rs
+++ b/core/bench/src/utils/mod.rs
@@ -117,6 +117,7 @@ pub fn params_from_args_and_metrics(
TransportProtocol::Tcp => BenchmarkTransport::Tcp,
TransportProtocol::Quic => BenchmarkTransport::Quic,
TransportProtocol::Http => BenchmarkTransport::Http,
+ TransportProtocol::WebSocket => BenchmarkTransport::WebSocket,
};
let server_address = args.server_address().to_string();
let remark = args.remark();
diff --git a/core/bench/src/utils/server_starter.rs b/core/bench/src/utils/server_starter.rs
index 049e84924..1ee66d9b6 100644
--- a/core/bench/src/utils/server_starter.rs
+++ b/core/bench/src/utils/server_starter.rs
@@ -30,6 +30,7 @@ struct ServerConfig {
http: ConfigAddress,
tcp: ConfigAddress,
quic: ConfigAddress,
+ websocket: ConfigAddress,
}
#[derive(Debug, Deserialize)]
@@ -150,6 +151,23 @@ async fn evaluate_server_start_condition(args: &IggyBenchArgs) -> (bool, HashMap
envs,
)
}
+ TransportProtocol::WebSocket => {
+ let args_websocket_address = args.server_address().parse::<SocketAddr>().unwrap();
+ let config_websocket_address = default_config
+ .websocket
+ .address
+ .parse::<SocketAddr>()
+ .unwrap();
+ let envs = HashMap::from([(
+ "IGGY_WEBSOCKET_ADDRESS".to_owned(),
+ default_config.websocket.address.clone(),
+ )]);
+ (
+ addresses_are_equivalent(&args_websocket_address, &config_websocket_address)
+ && !is_tcp_addr_in_use(&args_websocket_address).await,
+ envs,
+ )
+ }
}
}
diff --git a/core/binary_protocol/src/cli/binary_consumer_groups/create_consumer_group.rs b/core/binary_protocol/src/cli/binary_consumer_groups/create_consumer_group.rs
index e206b070e..e8fb0d876 100644
--- a/core/binary_protocol/src/cli/binary_consumer_groups/create_consumer_group.rs
+++ b/core/binary_protocol/src/cli/binary_consumer_groups/create_consumer_group.rs
@@ -29,27 +29,18 @@ pub struct CreateConsumerGroupCmd {
}
impl CreateConsumerGroupCmd {
- pub fn new(
- stream_id: Identifier,
- topic_id: Identifier,
- name: String,
- group_id: Option<u32>,
- ) -> Self {
+ pub fn new(stream_id: Identifier, topic_id: Identifier, name: String) -> Self {
Self {
create_consumer_group: CreateConsumerGroup {
stream_id,
topic_id,
name,
- group_id,
},
}
}
fn get_group_id_info(&self) -> String {
- match self.create_consumer_group.group_id {
- Some(group_id) => format!("ID: {group_id}"),
- None => "ID auto incremented".to_string(),
- }
+ "ID auto incremented".to_string()
}
}
@@ -67,7 +58,7 @@ impl CliCommand for CreateConsumerGroupCmd {
async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
client
- .create_consumer_group(&self.create_consumer_group.stream_id, &self.create_consumer_group.topic_id, &self.create_consumer_group.name, self.create_consumer_group.group_id)
+ .create_consumer_group(&self.create_consumer_group.stream_id, &self.create_consumer_group.topic_id, &self.create_consumer_group.name)
.await
.with_context(|| {
format!(
diff --git a/core/binary_protocol/src/cli/binary_streams/create_stream.rs b/core/binary_protocol/src/cli/binary_streams/create_stream.rs
index 46ed1d74d..9fe750f87 100644
--- a/core/binary_protocol/src/cli/binary_streams/create_stream.rs
+++ b/core/binary_protocol/src/cli/binary_streams/create_stream.rs
@@ -28,17 +28,14 @@ pub struct CreateStreamCmd {
}
impl CreateStreamCmd {
- pub fn new(stream_id: Option<u32>, name: String) -> Self {
+ pub fn new(name: String) -> Self {
Self {
- create_stream: CreateStream { stream_id, name },
+ create_stream: CreateStream { name },
}
}
fn get_stream_id_info(&self) -> String {
- match self.create_stream.stream_id {
- Some(stream_id) => format!("ID: {stream_id}"),
- None => "ID auto incremented".to_string(),
- }
+ "ID auto incremented".to_string()
}
}
@@ -54,7 +51,7 @@ impl CliCommand for CreateStreamCmd {
async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
client
- .create_stream(&self.create_stream.name, self.create_stream.stream_id)
+ .create_stream(&self.create_stream.name)
.await
.with_context(|| {
format!(
diff --git a/core/binary_protocol/src/cli/binary_topics/create_topic.rs b/core/binary_protocol/src/cli/binary_topics/create_topic.rs
index 56a823877..ff4ef9fd6 100644
--- a/core/binary_protocol/src/cli/binary_topics/create_topic.rs
+++ b/core/binary_protocol/src/cli/binary_topics/create_topic.rs
@@ -36,7 +36,6 @@ impl CreateTopicCmd {
#[allow(clippy::too_many_arguments)]
pub fn new(
stream_id: Identifier,
- topic_id: Option<u32>,
partitions_count: u32,
compression_algorithm: CompressionAlgorithm,
name: String,
@@ -47,7 +46,6 @@ impl CreateTopicCmd {
Self {
create_topic: CreateTopic {
stream_id,
- topic_id,
partitions_count,
compression_algorithm,
name,
@@ -60,13 +58,6 @@ impl CreateTopicCmd {
replication_factor,
}
}
-
- fn get_topic_id_info(&self) -> String {
- match self.create_topic.topic_id {
- Some(topic_id) => format!("ID: {topic_id}"),
- None => "ID auto incremented".to_string(),
- }
- }
}
#[async_trait]
@@ -77,19 +68,28 @@ impl CliCommand for CreateTopicCmd {
async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
client
- .create_topic(&self.create_topic.stream_id, &self.create_topic.name, self.create_topic.partitions_count, self.create_topic.compression_algorithm, self.create_topic.replication_factor, self.create_topic.topic_id, self.create_topic.message_expiry, self.create_topic.max_topic_size)
+ .create_topic(
+ &self.create_topic.stream_id,
+ &self.create_topic.name,
+ self.create_topic.partitions_count,
+ self.create_topic.compression_algorithm,
+ self.create_topic.replication_factor,
+ self.create_topic.message_expiry,
+ self.create_topic.max_topic_size,
+ )
.await
.with_context(|| {
format!(
- "Problem creating topic (ID: {}, name: {}, partitions count: {}) in stream with ID: {}",
- self.create_topic.topic_id.unwrap_or(0), self.create_topic.name, self.create_topic.partitions_count, self.create_topic.stream_id
+ "Problem creating topic (name: {}, partitions count: {}) in stream with ID: {}",
+ self.create_topic.name,
+ self.create_topic.partitions_count,
+ self.create_topic.stream_id
)
})?;
event!(target: PRINT_TARGET, Level::INFO,
- "Topic with name: {}, {}, partitions count: {}, compression algorithm: {}, message expiry: {}, max topic size: {}, replication factor: {} created in stream with ID: {}",
+ "Topic with name: {}, partitions count: {}, compression algorithm: {}, message expiry: {}, max topic size: {}, replication factor: {} created in stream with ID: {}",
self.create_topic.name,
- self.get_topic_id_info(),
self.create_topic.partitions_count,
self.create_topic.compression_algorithm,
self.message_expiry,
@@ -104,7 +104,6 @@ impl CliCommand for CreateTopicCmd {
impl fmt::Display for CreateTopicCmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let topic_id = self.get_topic_id_info();
let topic_name = &self.create_topic.name;
let compression_algorithm = &self.create_topic.compression_algorithm;
let message_expiry = &self.message_expiry;
@@ -114,7 +113,7 @@ impl fmt::Display for CreateTopicCmd {
write!(
f,
- "create topic with name: {topic_name}, {topic_id}, message expiry: {message_expiry}, compression algorithm: {compression_algorithm}, \
+ "create topic with name: {topic_name}, message expiry: {message_expiry}, compression algorithm: {compression_algorithm}, \
max topic size: {max_topic_size}, replication factor: {replication_factor} in stream with ID: {stream_id}",
)
}
diff --git a/core/binary_protocol/src/client/binary_clients/consumer_group_client.rs b/core/binary_protocol/src/client/binary_clients/consumer_group_client.rs
index d379ca566..49f3b373f 100644
--- a/core/binary_protocol/src/client/binary_clients/consumer_group_client.rs
+++ b/core/binary_protocol/src/client/binary_clients/consumer_group_client.rs
@@ -47,7 +47,6 @@ pub trait ConsumerGroupClient {
stream_id: &Identifier,
topic_id: &Identifier,
name: &str,
- group_id: Option<u32>,
) -> Result<ConsumerGroupDetails, IggyError>;
/// Delete a consumer group by unique ID or name for the given stream and topic by unique IDs or names.
///
diff --git a/core/binary_protocol/src/client/binary_clients/stream_client.rs b/core/binary_protocol/src/client/binary_clients/stream_client.rs
index bf4a7db42..b56c44b3d 100644
--- a/core/binary_protocol/src/client/binary_clients/stream_client.rs
+++ b/core/binary_protocol/src/client/binary_clients/stream_client.rs
@@ -33,11 +33,7 @@ pub trait StreamClient {
/// Create a new stream.
///
/// Authentication is required, and the permission to manage the streams.
- async fn create_stream(
- &self,
- name: &str,
- stream_id: Option<u32>,
- ) -> Result<StreamDetails, IggyError>;
+ async fn create_stream(&self, name: &str) -> Result<StreamDetails, IggyError>;
/// Update a stream by unique ID or name.
///
/// Authentication is required, and the permission to manage the streams.
diff --git a/core/binary_protocol/src/client/binary_clients/topic_client.rs b/core/binary_protocol/src/client/binary_clients/topic_client.rs
index 3e89de038..bc2c0b201 100644
--- a/core/binary_protocol/src/client/binary_clients/topic_client.rs
+++ b/core/binary_protocol/src/client/binary_clients/topic_client.rs
@@ -48,7 +48,6 @@ pub trait TopicClient {
partitions_count: u32,
compression_algorithm: CompressionAlgorithm,
replication_factor: Option<u8>,
- topic_id: Option<u32>,
message_expiry: IggyExpiry,
max_topic_size: MaxTopicSize,
) -> Result<TopicDetails, IggyError>;
diff --git a/core/binary_protocol/src/client/binary_consumer_groups/mod.rs b/core/binary_protocol/src/client/binary_consumer_groups/mod.rs
index bad644d37..6495665fc 100644
--- a/core/binary_protocol/src/client/binary_consumer_groups/mod.rs
+++ b/core/binary_protocol/src/client/binary_consumer_groups/mod.rs
@@ -71,7 +71,6 @@ impl<B: BinaryClient> ConsumerGroupClient for B {
stream_id: &Identifier,
topic_id: &Identifier,
name: &str,
- group_id: Option<u32>,
) -> Result<ConsumerGroupDetails, IggyError> {
fail_if_not_authenticated(self).await?;
let response = self
@@ -79,7 +78,6 @@ impl<B: BinaryClient> ConsumerGroupClient for B {
stream_id: stream_id.clone(),
topic_id: topic_id.clone(),
name: name.to_string(),
- group_id,
})
.await?;
mapper::map_consumer_group(response)
diff --git a/core/binary_protocol/src/client/binary_streams/mod.rs b/core/binary_protocol/src/client/binary_streams/mod.rs
index b48a017c9..ebb9ab6cd 100644
--- a/core/binary_protocol/src/client/binary_streams/mod.rs
+++ b/core/binary_protocol/src/client/binary_streams/mod.rs
@@ -49,16 +49,11 @@ impl<B: BinaryClient> StreamClient for B {
mapper::map_streams(response)
}
- async fn create_stream(
- &self,
- name: &str,
- stream_id: Option<u32>,
- ) -> Result<StreamDetails, IggyError> {
+ async fn create_stream(&self, name: &str) -> Result<StreamDetails, IggyError> {
fail_if_not_authenticated(self).await?;
let response = self
.send_with_response(&CreateStream {
name: name.to_string(),
- stream_id,
})
.await?;
mapper::map_stream(response)
diff --git a/core/binary_protocol/src/client/binary_topics/mod.rs b/core/binary_protocol/src/client/binary_topics/mod.rs
index 5d1575b7e..7ddb4205a 100644
--- a/core/binary_protocol/src/client/binary_topics/mod.rs
+++ b/core/binary_protocol/src/client/binary_topics/mod.rs
@@ -67,7 +67,6 @@ impl<B: BinaryClient> TopicClient for B {
partitions_count: u32,
compression_algorithm: CompressionAlgorithm,
replication_factor: Option<u8>,
- topic_id: Option<u32>,
message_expiry: IggyExpiry,
max_topic_size: MaxTopicSize,
) -> Result<TopicDetails, IggyError> {
@@ -79,7 +78,6 @@ impl<B: BinaryClient> TopicClient for B {
partitions_count,
compression_algorithm,
replication_factor,
- topic_id,
message_expiry,
max_topic_size,
})
diff --git a/core/binary_protocol/src/utils/mapper.rs b/core/binary_protocol/src/utils/mapper.rs
index 725932c5c..356d65931 100644
--- a/core/binary_protocol/src/utils/mapper.rs
+++ b/core/binary_protocol/src/utils/mapper.rs
@@ -898,7 +898,7 @@ fn map_to_client_info(
.map_err(|_| IggyError::InvalidNumberEncoding)?,
);
let user_id = match user_id {
- 0 => None,
+ u32::MAX => None,
_ => Some(user_id),
};
diff --git a/core/cli/src/args/consumer_offset.rs b/core/cli/src/args/consumer_offset.rs
index bbb031589..26781b9f5 100644
--- a/core/cli/src/args/consumer_offset.rs
+++ b/core/cli/src/args/consumer_offset.rs
@@ -77,7 +77,7 @@ pub(crate) struct ConsumerOffsetGetArgs {
#[arg(value_parser = clap::value_parser!(Identifier))]
pub(crate) topic_id: Identifier,
/// Partitions ID for which consumer offset is retrieved
- #[arg(value_parser = clap::value_parser!(u32).range(1..))]
+ #[arg(value_parser = clap::value_parser!(u32).range(0..))]
pub(crate) partition_id: u32,
/// Consumer kind: "consumer" for regular consumer, "consumer_group" for consumer group
#[arg(short = 'k', long = "kind", default_value = "consumer", value_enum)]
@@ -103,7 +103,7 @@ pub(crate) struct ConsumerOffsetSetArgs {
#[arg(value_parser = clap::value_parser!(Identifier))]
pub(crate) topic_id: Identifier,
/// Partitions ID for which consumer offset is set
- #[arg(value_parser = clap::value_parser!(u32).range(1..))]
+ #[arg(value_parser = clap::value_parser!(u32).range(0..))]
pub(crate) partition_id: u32,
/// Offset to set
pub(crate) offset: u64,
diff --git a/core/cli/src/args/message.rs b/core/cli/src/args/message.rs
index 08d69348a..da5e742e6 100644
--- a/core/cli/src/args/message.rs
+++ b/core/cli/src/args/message.rs
@@ -146,7 +146,7 @@ pub(crate) struct PollMessagesArgs {
#[arg(value_parser = clap::value_parser!(Identifier))]
pub(crate) topic_id: Identifier,
/// Partition ID from which message will be polled
- #[arg(value_parser = clap::value_parser!(u32).range(1..))]
+ #[arg(value_parser = clap::value_parser!(u32).range(0..))]
pub(crate) partition_id: u32,
/// Number of messages to poll
#[clap(verbatim_doc_comment)]
@@ -219,7 +219,7 @@ pub(crate) struct FlushMessagesArgs {
#[arg(value_parser = clap::value_parser!(Identifier))]
pub(crate) topic_id: Identifier,
/// Partition ID for which messages will be flushed
- #[arg(value_parser = clap::value_parser!(u32).range(1..))]
+ #[arg(value_parser = clap::value_parser!(u32).range(0..))]
pub(crate) partition_id: u32,
/// fsync flushed data to disk
///
diff --git a/core/cli/src/args/permissions/mod.rs b/core/cli/src/args/permissions/mod.rs
index 732a4f526..6655181f4 100644
--- a/core/cli/src/args/permissions/mod.rs
+++ b/core/cli/src/args/permissions/mod.rs
@@ -49,7 +49,7 @@ impl From<PermissionsArgs> for Option<Permissions> {
.stream
.into_iter()
.map(|s| (s.stream_id, s.into()))
- .collect::<AHashMap<u32, StreamPermissions>>();
+ .collect::<AHashMap<usize, StreamPermissions>>();
match (value.global, stream_permissions.is_empty()) {
(Some(global), true) => Some(Permissions {
diff --git a/core/cli/src/args/permissions/stream.rs b/core/cli/src/args/permissions/stream.rs
index fac5c2381..b371d8720 100644
--- a/core/cli/src/args/permissions/stream.rs
+++ b/core/cli/src/args/permissions/stream.rs
@@ -58,7 +58,7 @@ impl FromStr for StreamPermission {
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct StreamPermissionsArg {
- pub(crate) stream_id: u32,
+ pub(crate) stream_id: usize,
pub(crate) permissions: StreamPermissions,
}
@@ -70,7 +70,7 @@ impl From<StreamPermissionsArg> for StreamPermissions {
impl StreamPermissionsArg {
pub(super) fn new(
- stream_id: u32,
+ stream_id: usize,
stream_permissions: Vec<StreamPermission>,
topic_permissions: Vec<TopicPermissionsArg>,
) -> Self {
diff --git a/core/cli/src/args/permissions/topic.rs b/core/cli/src/args/permissions/topic.rs
index c1b31594d..cdc83c2ec 100644
--- a/core/cli/src/args/permissions/topic.rs
+++ b/core/cli/src/args/permissions/topic.rs
@@ -52,7 +52,7 @@ impl FromStr for TopicPermission {
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct TopicPermissionsArg {
- pub(crate) topic_id: u32,
+ pub(crate) topic_id: usize,
pub(crate) permissions: TopicPermissions,
}
@@ -63,7 +63,7 @@ impl From<TopicPermissionsArg> for TopicPermissions {
}
impl TopicPermissionsArg {
- fn new(topic_id: u32, topic_permissions: Vec<TopicPermission>) -> Self {
+ fn new(topic_id: usize, topic_permissions: Vec<TopicPermission>) -> Self {
let mut result = Self {
topic_id,
permissions: TopicPermissions::default(),
diff --git a/core/cli/src/main.rs b/core/cli/src/main.rs
index d21536bba..21b60aaa1 100644
--- a/core/cli/src/main.rs
+++ b/core/cli/src/main.rs
@@ -106,9 +106,7 @@ fn get_command(
#[warn(clippy::let_and_return)]
match command {
Command::Stream(command) => match command {
- StreamAction::Create(args) => {
- Box::new(CreateStreamCmd::new(args.stream_id, args.name.clone()))
- }
+ StreamAction::Create(args) => Box::new(CreateStreamCmd::new(args.name.clone())),
StreamAction::Delete(args) => Box::new(DeleteStreamCmd::new(args.stream_id.clone())),
StreamAction::Update(args) => Box::new(UpdateStreamCmd::new(
args.stream_id.clone(),
@@ -121,7 +119,6 @@ fn get_command(
Command::Topic(command) => match command {
TopicAction::Create(args) => Box::new(CreateTopicCmd::new(
args.stream_id.clone(),
- args.topic_id,
args.partitions_count,
args.compression_algorithm,
args.name.clone(),
@@ -252,7 +249,6 @@ fn get_command(
create_args.stream_id.clone(),
create_args.topic_id.clone(),
create_args.name.clone(),
- create_args.group_id,
)),
ConsumerGroupAction::Delete(delete_args) => Box::new(DeleteConsumerGroupCmd::new(
delete_args.stream_id.clone(),
diff --git a/core/common/Cargo.toml b/core/common/Cargo.toml
index 2aa4ade37..39bfde4bf 100644
--- a/core/common/Cargo.toml
+++ b/core/common/Cargo.toml
@@ -40,6 +40,7 @@ bon = { workspace = true }
byte-unit = { workspace = true }
bytes = { workspace = true }
chrono = { workspace = true }
+compio-ws = { workspace = true }
clap = { workspace = true }
comfy-table = { workspace = true }
crc32fast = { workspace = true }
diff --git a/core/common/src/certificates.rs b/core/common/src/certificates.rs
index f697b776d..cba7dc1e9 100644
--- a/core/common/src/certificates.rs
+++ b/core/common/src/certificates.rs
@@ -27,5 +27,6 @@ pub fn generate_self_signed_certificate(
let cert_der = cert.cert.der();
let key_der = cert.signing_key.serialize_der();
let key = PrivateKeyDer::try_from(key_der)?;
+
Ok((vec![cert_der.clone()], key))
}
diff --git a/core/common/src/commands/consumer_groups/create_consumer_group.rs b/core/common/src/commands/consumer_groups/create_consumer_group.rs
index 265b06592..c5bd3e912 100644
--- a/core/common/src/commands/consumer_groups/create_consumer_group.rs
+++ b/core/common/src/commands/consumer_groups/create_consumer_group.rs
@@ -32,7 +32,6 @@ use std::str::from_utf8;
/// It has additional payload:
/// - `stream_id` - unique stream ID (numeric or name).
/// - `topic_id` - unique topic ID (numeric or name).
-/// - `group_id` - unique consumer group ID.
/// - `name` - unique consumer group name, max length is 255 characters.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CreateConsumerGroup {
@@ -42,8 +41,6 @@ pub struct CreateConsumerGroup {
/// Unique topic ID (numeric or name).
#[serde(skip)]
pub topic_id: Identifier,
- /// Unique consumer group ID (numeric), if None is provided then the server will automatically assign it.
- pub group_id: Option<u32>,
/// Unique consumer group name, max length is 255 characters.
pub name: String,
}
@@ -59,7 +56,6 @@ impl Default for CreateConsumerGroup {
CreateConsumerGroup {
stream_id: Identifier::default(),
topic_id: Identifier::default(),
- group_id: None,
name: "consumer_group_1".to_string(),
}
}
@@ -67,12 +63,6 @@ impl Default for CreateConsumerGroup {
impl Validatable<IggyError> for CreateConsumerGroup {
fn validate(&self) -> Result<(), IggyError> {
- if let Some(group_id) = self.group_id
- && group_id == 0
- {
- return Err(IggyError::InvalidConsumerGroupId);
- }
-
if self.name.is_empty() || self.name.len() > MAX_NAME_LENGTH {
return Err(IggyError::InvalidConsumerGroupName);
}
@@ -86,11 +76,10 @@ impl BytesSerializable for CreateConsumerGroup {
let stream_id_bytes = self.stream_id.to_bytes();
let topic_id_bytes = self.topic_id.to_bytes();
let mut bytes = BytesMut::with_capacity(
- 4 + stream_id_bytes.len() + topic_id_bytes.len() + self.name.len(),
+ 1 + stream_id_bytes.len() + topic_id_bytes.len() + self.name.len(),
);
bytes.put_slice(&stream_id_bytes);
bytes.put_slice(&topic_id_bytes);
- bytes.put_u32_le(self.group_id.unwrap_or(0));
#[allow(clippy::cast_possible_truncation)]
bytes.put_u8(self.name.len() as u8);
bytes.put_slice(self.name.as_bytes());
@@ -98,7 +87,7 @@ impl BytesSerializable for CreateConsumerGroup {
}
fn from_bytes(bytes: Bytes) -> Result<CreateConsumerGroup, IggyError> {
- if bytes.len() < 10 {
+ if bytes.len() < 6 {
return Err(IggyError::InvalidCommand);
}
@@ -107,20 +96,13 @@ impl BytesSerializable for CreateConsumerGroup {
position += stream_id.get_size_bytes().as_bytes_usize();
let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
position += topic_id.get_size_bytes().as_bytes_usize();
- let group_id = u32::from_le_bytes(
- bytes[position..position + 4]
- .try_into()
- .map_err(|_| IggyError::InvalidNumberEncoding)?,
- );
- let group_id = if group_id == 0 { None } else { Some(group_id) };
- let name_length = bytes[position + 4];
- let name = from_utf8(&bytes[position + 5..position + 5 + name_length as usize])
+ let name_length = bytes[position];
+ let name = from_utf8(&bytes[position + 1..position + 1 + name_length as usize])
.map_err(|_| IggyError::InvalidUtf8)?
.to_string();
let command = CreateConsumerGroup {
stream_id,
topic_id,
- group_id,
name,
};
Ok(command)
@@ -129,14 +111,7 @@ impl BytesSerializable for CreateConsumerGroup {
impl Display for CreateConsumerGroup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "{}|{}|{}|{}",
- self.stream_id,
- self.topic_id,
- self.group_id.unwrap_or(0),
- self.name
- )
+ write!(f, "{}|{}|{}", self.stream_id, self.topic_id, self.name)
}
}
@@ -149,7 +124,6 @@ mod tests {
let command = CreateConsumerGroup {
stream_id: Identifier::numeric(1).unwrap(),
topic_id: Identifier::numeric(2).unwrap(),
- group_id: Some(3),
name: "test".to_string(),
};
@@ -159,14 +133,12 @@ mod tests {
position += stream_id.get_size_bytes().as_bytes_usize();
let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
position += topic_id.get_size_bytes().as_bytes_usize();
- let group_id = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
- let name_length = bytes[position + 4];
- let name = from_utf8(&bytes[position + 5..position + 5 + name_length as usize]).unwrap();
+ let name_length = bytes[position];
+ let name = from_utf8(&bytes[position + 1..position + 1 + name_length as usize]).unwrap();
assert!(!bytes.is_empty());
assert_eq!(stream_id, command.stream_id);
assert_eq!(topic_id, command.topic_id);
- assert_eq!(group_id, command.group_id.unwrap());
assert_eq!(name, command.name);
}
@@ -174,15 +146,13 @@ mod tests {
fn should_be_deserialized_from_bytes() {
let stream_id = Identifier::numeric(1).unwrap();
let topic_id = Identifier::numeric(2).unwrap();
- let group_id = 3u32;
let name = "test".to_string();
let stream_id_bytes = stream_id.to_bytes();
let topic_id_bytes = topic_id.to_bytes();
let mut bytes =
- BytesMut::with_capacity(4 + stream_id_bytes.len() + topic_id_bytes.len() + name.len());
+ BytesMut::with_capacity(1 + stream_id_bytes.len() + topic_id_bytes.len() + name.len());
bytes.put_slice(&stream_id_bytes);
bytes.put_slice(&topic_id_bytes);
- bytes.put_u32_le(group_id);
#[allow(clippy::cast_possible_truncation)]
bytes.put_u8(name.len() as u8);
bytes.put_slice(name.as_bytes());
@@ -192,7 +162,6 @@ mod tests {
let command = command.unwrap();
assert_eq!(command.stream_id, stream_id);
assert_eq!(command.topic_id, topic_id);
- assert_eq!(command.group_id.unwrap(), group_id);
assert_eq!(command.name, name);
}
}
diff --git a/core/common/src/commands/consumer_offsets/delete_consumer_offset.rs b/core/common/src/commands/consumer_offsets/delete_consumer_offset.rs
index aab6c096d..8cfeac24d 100644
--- a/core/common/src/commands/consumer_offsets/delete_consumer_offset.rs
+++ b/core/common/src/commands/consumer_offsets/delete_consumer_offset.rs
@@ -54,7 +54,7 @@ impl Default for DeleteConsumerOffset {
consumer: Consumer::default(),
stream_id: Identifier::default(),
topic_id: Identifier::default(),
- partition_id: Some(1),
+ partition_id: Some(0),
}
}
}
diff --git a/core/common/src/commands/consumer_offsets/get_consumer_offset.rs b/core/common/src/commands/consumer_offsets/get_consumer_offset.rs
index c2a3dda8e..0f542e09f 100644
--- a/core/common/src/commands/consumer_offsets/get_consumer_offset.rs
+++ b/core/common/src/commands/consumer_offsets/get_consumer_offset.rs
@@ -67,7 +67,7 @@ impl Command for GetConsumerOffset {
}
fn default_partition_id() -> Option<u32> {
- Some(1)
+ Some(0)
}
impl Validatable<IggyError> for GetConsumerOffset {
@@ -82,21 +82,24 @@ impl BytesSerializable for GetConsumerOffset {
let stream_id_bytes = self.stream_id.to_bytes();
let topic_id_bytes = self.topic_id.to_bytes();
let mut bytes = BytesMut::with_capacity(
- 4 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
+ 5 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
);
bytes.put_slice(&consumer_bytes);
bytes.put_slice(&stream_id_bytes);
bytes.put_slice(&topic_id_bytes);
+ // Encode partition_id with a flag byte: 1 = Some, 0 = None
if let Some(partition_id) = self.partition_id {
+ bytes.put_u8(1);
bytes.put_u32_le(partition_id);
} else {
- bytes.put_u32_le(0);
+ bytes.put_u8(0);
+ bytes.put_u32_le(0); // Padding to keep structure consistent
}
bytes.freeze()
}
fn from_bytes(bytes: Bytes) -> Result<GetConsumerOffset, IggyError> {
- if bytes.len() < 15 {
+ if bytes.len() < 16 {
return Err(IggyError::InvalidCommand);
}
@@ -112,15 +115,17 @@ impl BytesSerializable for GetConsumerOffset {
position += stream_id.get_size_bytes().as_bytes_usize();
let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
position += topic_id.get_size_bytes().as_bytes_usize();
- let partition_id = u32::from_le_bytes(
- bytes[position..position + 4]
+ // Decode partition_id with flag byte: 1 = Some, 0 = None
+ let has_partition_id = bytes[position];
+ let partition_id_value = u32::from_le_bytes(
+ bytes[position + 1..position + 5]
.try_into()
.map_err(|_| IggyError::InvalidNumberEncoding)?,
);
- let partition_id = if partition_id == 0 {
- None
+ let partition_id = if has_partition_id == 1 {
+ Some(partition_id_value)
} else {
- Some(partition_id)
+ None
};
let command = GetConsumerOffset {
consumer,
@@ -171,13 +176,20 @@ mod tests {
position += stream_id.get_size_bytes().as_bytes_usize();
let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
position += topic_id.get_size_bytes().as_bytes_usize();
- let partition_id = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
+ let has_partition_id = bytes[position];
+ let partition_id =
+ u32::from_le_bytes(bytes[position + 1..position + 5].try_into().unwrap());
+ let partition_id = if has_partition_id == 1 {
+ Some(partition_id)
+ } else {
+ None
+ };
assert!(!bytes.is_empty());
assert_eq!(consumer, command.consumer);
assert_eq!(stream_id, command.stream_id);
assert_eq!(topic_id, command.topic_id);
- assert_eq!(Some(partition_id), command.partition_id);
+ assert_eq!(partition_id, command.partition_id);
}
#[test]
@@ -191,11 +203,12 @@ mod tests {
let stream_id_bytes = stream_id.to_bytes();
let topic_id_bytes = topic_id.to_bytes();
let mut bytes = BytesMut::with_capacity(
- 4 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
+ 5 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
);
bytes.put_slice(&consumer_bytes);
bytes.put_slice(&stream_id_bytes);
bytes.put_slice(&topic_id_bytes);
+ bytes.put_u8(1); // Flag: partition_id is Some
bytes.put_u32_le(partition_id);
let command = GetConsumerOffset::from_bytes(bytes.freeze());
diff --git a/core/common/src/commands/consumer_offsets/store_consumer_offset.rs b/core/common/src/commands/consumer_offsets/store_consumer_offset.rs
index ad3c4eae6..c7705fbfb 100644
--- a/core/common/src/commands/consumer_offsets/store_consumer_offset.rs
+++ b/core/common/src/commands/consumer_offsets/store_consumer_offset.rs
@@ -57,7 +57,7 @@ impl Default for StoreConsumerOffset {
consumer: Consumer::default(),
stream_id: Identifier::default(),
topic_id: Identifier::default(),
- partition_id: Some(1),
+ partition_id: Some(0),
offset: 0,
}
}
@@ -81,22 +81,25 @@ impl BytesSerializable for StoreConsumerOffset {
let stream_id_bytes = self.stream_id.to_bytes();
let topic_id_bytes = self.topic_id.to_bytes();
let mut bytes = BytesMut::with_capacity(
- 12 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
+ 13 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
);
bytes.put_slice(&consumer_bytes);
bytes.put_slice(&stream_id_bytes);
bytes.put_slice(&topic_id_bytes);
+ // Encode partition_id with a flag byte: 1 = Some, 0 = None
if let Some(partition_id) = self.partition_id {
+ bytes.put_u8(1);
bytes.put_u32_le(partition_id);
} else {
- bytes.put_u32_le(0);
+ bytes.put_u8(0);
+ bytes.put_u32_le(0); // Padding to keep structure consistent
}
bytes.put_u64_le(self.offset);
bytes.freeze()
}
fn from_bytes(bytes: Bytes) -> Result<StoreConsumerOffset, IggyError> {
- if bytes.len() < 23 {
+ if bytes.len() < 24 {
return Err(IggyError::InvalidCommand);
}
@@ -112,18 +115,20 @@ impl BytesSerializable for StoreConsumerOffset {
position += stream_id.get_size_bytes().as_bytes_usize();
let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
position += topic_id.get_size_bytes().as_bytes_usize();
- let partition_id = u32::from_le_bytes(
- bytes[position..position + 4]
+ // Decode partition_id with flag byte: 1 = Some, 0 = None
+ let has_partition_id = bytes[position];
+ let partition_id_value = u32::from_le_bytes(
+ bytes[position + 1..position + 5]
.try_into()
.map_err(|_| IggyError::InvalidNumberEncoding)?,
);
- let partition_id = if partition_id == 0 {
- None
+ let partition_id = if has_partition_id == 1 {
+ Some(partition_id_value)
} else {
- Some(partition_id)
+ None
};
let offset = u64::from_le_bytes(
- bytes[position + 4..position + 12]
+ bytes[position + 5..position + 13]
.try_into()
.map_err(|_| IggyError::InvalidNumberEncoding)?,
);
@@ -179,14 +184,21 @@ mod tests {
position += stream_id.get_size_bytes().as_bytes_usize();
let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
position += topic_id.get_size_bytes().as_bytes_usize();
- let partition_id = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
- let offset = u64::from_le_bytes(bytes[position + 4..position + 12].try_into().unwrap());
+ let has_partition_id = bytes[position];
+ let partition_id =
+ u32::from_le_bytes(bytes[position + 1..position + 5].try_into().unwrap());
+ let partition_id = if has_partition_id == 1 {
+ Some(partition_id)
+ } else {
+ None
+ };
+ let offset = u64::from_le_bytes(bytes[position + 5..position + 13].try_into().unwrap());
assert!(!bytes.is_empty());
assert_eq!(consumer, command.consumer);
assert_eq!(stream_id, command.stream_id);
assert_eq!(topic_id, command.topic_id);
- assert_eq!(Some(partition_id), command.partition_id);
+ assert_eq!(partition_id, command.partition_id);
assert_eq!(offset, command.offset);
}
@@ -202,11 +214,12 @@ mod tests {
let stream_id_bytes = stream_id.to_bytes();
let topic_id_bytes = topic_id.to_bytes();
let mut bytes = BytesMut::with_capacity(
- 12 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
+ 13 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
);
bytes.put_slice(&consumer_bytes);
bytes.put_slice(&stream_id_bytes);
bytes.put_slice(&topic_id_bytes);
+ bytes.put_u8(1); // Flag: partition_id is Some
bytes.put_u32_le(partition_id);
bytes.put_u64_le(offset);
diff --git a/core/common/src/commands/messages/poll_messages.rs b/core/common/src/commands/messages/poll_messages.rs
index 3f4045bbd..c52b335c8 100644
--- a/core/common/src/commands/messages/poll_messages.rs
+++ b/core/common/src/commands/messages/poll_messages.rs
@@ -24,7 +24,7 @@ use bytes::{BufMut, Bytes, BytesMut};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
-pub const DEFAULT_PARTITION_ID: u32 = 1;
+pub const DEFAULT_PARTITION_ID: u32 = 0;
pub const DEFAULT_NUMBER_OF_MESSAGES_TO_POLL: u32 = 10;
/// `PollMessages` command is used to poll messages from a topic in a stream.
@@ -76,7 +76,7 @@ impl PollMessages {
let topic_id_bytes = topic_id.to_bytes();
let strategy_bytes = strategy.to_bytes();
let mut bytes = BytesMut::with_capacity(
- 9 + consumer_bytes.len()
+ 10 + consumer_bytes.len()
+ stream_id_bytes.len()
+ topic_id_bytes.len()
+ strategy_bytes.len(),
@@ -84,10 +84,13 @@ impl PollMessages {
bytes.put_slice(&consumer_bytes);
bytes.put_slice(&stream_id_bytes);
bytes.put_slice(&topic_id_bytes);
+ // Encode partition_id with a flag byte: 1 = Some, 0 = None
if let Some(partition_id) = partition_id {
+ bytes.put_u8(1);
bytes.put_u32_le(partition_id);
} else {
- bytes.put_u32_le(0);
+ bytes.put_u8(0);
+ bytes.put_u32_le(0); // Padding to keep structure consistent
}
bytes.put_slice(&strategy_bytes);
bytes.put_u32_le(count);
@@ -149,7 +152,7 @@ impl BytesSerializable for PollMessages {
}
fn from_bytes(bytes: Bytes) -> Result<Self, IggyError> {
- if bytes.len() < 29 {
+ if bytes.len() < 30 {
return Err(IggyError::InvalidCommand);
}
@@ -165,17 +168,20 @@ impl BytesSerializable for PollMessages {
position += stream_id.get_size_bytes().as_bytes_usize();
let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
position += topic_id.get_size_bytes().as_bytes_usize();
- let partition_id = u32::from_le_bytes(
- bytes[position..position + 4]
+ // Decode partition_id with flag byte: 1 = Some, 0 = None
+ let has_partition_id = bytes[position];
+ let partition_id_value = u32::from_le_bytes(
+ bytes[position + 1..position + 5]
.try_into()
.map_err(|_| IggyError::InvalidNumberEncoding)?,
);
- let partition_id = match partition_id {
- 0 => None,
- partition_id => Some(partition_id),
+ let partition_id = if has_partition_id == 1 {
+ Some(partition_id_value)
+ } else {
+ None
};
- let polling_kind = PollingKind::from_code(bytes[position + 4])?;
- position += 5;
+ let polling_kind = PollingKind::from_code(bytes[position + 5])?;
+ position += 6;
let value = u64::from_le_bytes(
bytes[position..position + 8]
.try_into()
@@ -254,9 +260,16 @@ mod tests {
position += stream_id.get_size_bytes().as_bytes_usize();
let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
position += topic_id.get_size_bytes().as_bytes_usize();
- let partition_id = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
- let polling_kind = PollingKind::from_code(bytes[position + 4]).unwrap();
- position += 5;
+ let has_partition_id = bytes[position];
+ let partition_id =
+ u32::from_le_bytes(bytes[position + 1..position + 5].try_into().unwrap());
+ let partition_id = if has_partition_id == 1 {
+ Some(partition_id)
+ } else {
+ None
+ };
+ let polling_kind = PollingKind::from_code(bytes[position + 5]).unwrap();
+ position += 6;
let value = u64::from_le_bytes(bytes[position..position + 8].try_into().unwrap());
let strategy = PollingStrategy {
kind: polling_kind,
@@ -270,7 +283,7 @@ mod tests {
assert_eq!(consumer, command.consumer);
assert_eq!(stream_id, command.stream_id);
assert_eq!(topic_id, command.topic_id);
- assert_eq!(Some(partition_id), command.partition_id);
+ assert_eq!(partition_id, command.partition_id);
assert_eq!(strategy, command.strategy);
assert_eq!(count, command.count);
assert_eq!(auto_commit, command.auto_commit);
@@ -291,7 +304,7 @@ mod tests {
let topic_id_bytes = topic_id.to_bytes();
let strategy_bytes = strategy.to_bytes();
let mut bytes = BytesMut::with_capacity(
- 9 + consumer_bytes.len()
+ 10 + consumer_bytes.len()
+ stream_id_bytes.len()
+ topic_id_bytes.len()
+ strategy_bytes.len(),
@@ -299,6 +312,7 @@ mod tests {
bytes.put_slice(&consumer_bytes);
bytes.put_slice(&stream_id_bytes);
bytes.put_slice(&topic_id_bytes);
+ bytes.put_u8(1); // Flag: partition_id is Some
bytes.put_u32_le(partition_id);
bytes.put_slice(&strategy_bytes);
bytes.put_u32_le(count);
diff --git a/core/common/src/commands/streams/create_stream.rs b/core/common/src/commands/streams/create_stream.rs
index c163e7723..292cfcfc2 100644
--- a/core/common/src/commands/streams/create_stream.rs
+++ b/core/common/src/commands/streams/create_stream.rs
@@ -28,12 +28,9 @@ use std::str::from_utf8;
/// `CreateStream` command is used to create a new stream.
/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric)
/// - `name` - unique stream name (string), max length is 255 characters.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CreateStream {
- /// Unique stream ID (numeric), if None is provided then the server will automatically assign it.
- pub stream_id: Option<u32>,
/// Unique stream name (string), max length is 255 characters.
pub name: String,
}
@@ -47,7 +44,6 @@ impl Command for CreateStream {
impl Default for CreateStream {
fn default() -> Self {
CreateStream {
- stream_id: Some(1),
name: "stream".to_string(),
}
}
@@ -55,12 +51,6 @@ impl Default for CreateStream {
impl Validatable<IggyError> for CreateStream {
fn validate(&self) -> Result<(), IggyError> {
- if let Some(stream_id) = self.stream_id
- && stream_id == 0
- {
- return Err(IggyError::InvalidStreamId);
- }
-
if self.name.is_empty() || self.name.len() > MAX_NAME_LENGTH {
return Err(IggyError::InvalidStreamName);
}
@@ -72,7 +62,6 @@ impl Validatable<IggyError> for CreateStream {
impl BytesSerializable for CreateStream {
fn to_bytes(&self) -> Bytes {
let mut bytes = BytesMut::with_capacity(5 + self.name.len());
- bytes.put_u32_le(self.stream_id.unwrap_or(0));
#[allow(clippy::cast_possible_truncation)]
bytes.put_u8(self.name.len() as u8);
bytes.put_slice(self.name.as_bytes());
@@ -80,36 +69,26 @@ impl BytesSerializable for CreateStream {
}
fn from_bytes(bytes: Bytes) -> Result<CreateStream, IggyError> {
- if bytes.len() < 6 {
+ if bytes.len() < 2 {
return Err(IggyError::InvalidCommand);
}
- let stream_id = u32::from_le_bytes(
- bytes[..4]
- .try_into()
- .map_err(|_| IggyError::InvalidNumberEncoding)?,
- );
- let stream_id = if stream_id == 0 {
- None
- } else {
- Some(stream_id)
- };
- let name_length = bytes[4];
- let name = from_utf8(&bytes[5..5 + name_length as usize])
+ let name_length = bytes[0];
+ let name = from_utf8(&bytes[1..1 + name_length as usize])
.map_err(|_| IggyError::InvalidUtf8)?
.to_string();
if name.len() != name_length as usize {
return Err(IggyError::InvalidCommand);
}
- let command = CreateStream { stream_id, name };
+ let command = CreateStream { name };
Ok(command)
}
}
impl Display for CreateStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}|{}", self.stream_id.unwrap_or(0), self.name)
+ write!(f, "{}", self.name)
}
}
@@ -120,26 +99,21 @@ mod tests {
#[test]
fn should_be_serialized_as_bytes() {
let command = CreateStream {
- stream_id: Some(1),
name: "test".to_string(),
};
let bytes = command.to_bytes();
- let stream_id = u32::from_le_bytes(bytes[..4].try_into().unwrap());
- let name_length = bytes[4];
- let name = from_utf8(&bytes[5..5 + name_length as usize]).unwrap();
+ let name_length = bytes[0];
+ let name = from_utf8(&bytes[1..1 + name_length as usize]).unwrap();
assert!(!bytes.is_empty());
- assert_eq!(stream_id, command.stream_id.unwrap());
assert_eq!(name, command.name);
}
#[test]
fn should_be_deserialized_from_bytes() {
- let stream_id = 1u32;
let name = "test".to_string();
let mut bytes = BytesMut::new();
- bytes.put_u32_le(stream_id);
#[allow(clippy::cast_possible_truncation)]
bytes.put_u8(name.len() as u8);
bytes.put_slice(name.as_bytes());
@@ -147,7 +121,6 @@ mod tests {
assert!(command.is_ok());
let command = command.unwrap();
- assert_eq!(command.stream_id.unwrap(), stream_id);
assert_eq!(command.name, name);
}
}
diff --git a/core/common/src/commands/topics/create_topic.rs b/core/common/src/commands/topics/create_topic.rs
index f8ac2da0b..ba90084e7 100644
--- a/core/common/src/commands/topics/create_topic.rs
+++ b/core/common/src/commands/topics/create_topic.rs
@@ -34,7 +34,6 @@ use std::str::from_utf8;
/// `CreateTopic` command is used to create a new topic in a stream.
/// It has additional payload:
/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric).
/// - `partitions_count` - number of partitions in the topic, max value is 1000.
/// - `message_expiry` - message expiry, if `NeverExpire` then messages will never expire.
/// - `max_topic_size` - maximum size of the topic, if `Unlimited` then topic size is unlimited.
@@ -46,8 +45,6 @@ pub struct CreateTopic {
/// Unique stream ID (numeric or name).
#[serde(skip)]
pub stream_id: Identifier,
- /// Unique topic ID (numeric), if None is provided then the server will automatically assign it.
- pub topic_id: Option<u32>,
/// Number of partitions in the topic, max value is 1000.
pub partitions_count: u32,
/// Compression algorithm for the topic.
@@ -73,7 +70,6 @@ impl Default for CreateTopic {
fn default() -> Self {
CreateTopic {
stream_id: Identifier::default(),
- topic_id: Some(1),
partitions_count: 1,
compression_algorithm: CompressionAlgorithm::None,
message_expiry: IggyExpiry::NeverExpire,
@@ -86,12 +82,6 @@ impl Default for CreateTopic {
impl Validatable<IggyError> for CreateTopic {
fn validate(&self) -> Result<(), IggyError> {
- if let Some(topic_id) = self.topic_id
- && topic_id == 0
- {
- return Err(IggyError::InvalidTopicId);
- }
-
if self.name.is_empty() || self.name.len() > MAX_NAME_LENGTH {
return Err(IggyError::InvalidTopicName);
}
@@ -113,9 +103,8 @@ impl Validatable<IggyError> for CreateTopic {
impl BytesSerializable for CreateTopic {
fn to_bytes(&self) -> Bytes {
let stream_id_bytes = self.stream_id.to_bytes();
- let mut bytes = BytesMut::with_capacity(23 + stream_id_bytes.len() + self.name.len());
+ let mut bytes = BytesMut::with_capacity(19 + stream_id_bytes.len() + self.name.len());
bytes.put_slice(&stream_id_bytes);
- bytes.put_u32_le(self.topic_id.unwrap_or(0));
bytes.put_u32_le(self.partitions_count);
bytes.put_u8(self.compression_algorithm.as_code());
bytes.put_u64_le(self.message_expiry.into());
@@ -131,42 +120,36 @@ impl BytesSerializable for CreateTopic {
}
fn from_bytes(bytes: Bytes) -> std::result::Result<CreateTopic, IggyError> {
- if bytes.len() < 18 {
+ if bytes.len() < 14 {
return Err(IggyError::InvalidCommand);
}
let mut position = 0;
let stream_id = Identifier::from_bytes(bytes.clone())?;
position += stream_id.get_size_bytes().as_bytes_usize();
- let topic_id = u32::from_le_bytes(
- bytes[position..position + 4]
- .try_into()
- .map_err(|_| IggyError::InvalidNumberEncoding)?,
- );
- let topic_id = if topic_id == 0 { None } else { Some(topic_id) };
let partitions_count = u32::from_le_bytes(
- bytes[position + 4..position + 8]
+ bytes[position..position + 4]
.try_into()
.map_err(|_| IggyError::InvalidNumberEncoding)?,
);
- let compression_algorithm = CompressionAlgorithm::from_code(bytes[position + 8])?;
+ let compression_algorithm = CompressionAlgorithm::from_code(bytes[position + 4])?;
let message_expiry = u64::from_le_bytes(
- bytes[position + 9..position + 17]
+ bytes[position + 5..position + 13]
.try_into()
.map_err(|_| IggyError::InvalidNumberEncoding)?,
);
let message_expiry: IggyExpiry = message_expiry.into();
let max_topic_size = u64::from_le_bytes(
- bytes[position + 17..position + 25]
+ bytes[position + 13..position + 21]
.try_into()
.map_err(|_| IggyError::InvalidNumberEncoding)?,
);
let max_topic_size: MaxTopicSize = max_topic_size.into();
- let replication_factor = match bytes[position + 25] {
+ let replication_factor = match bytes[position + 21] {
0 => None,
factor => Some(factor),
};
- let name_length = bytes[position + 26];
- let name = from_utf8(&bytes[position + 27..(position + 27 + name_length as usize)])
+ let name_length = bytes[position + 22];
+ let name = from_utf8(&bytes[position + 23..(position + 23 + name_length as usize)])
.map_err(|_| IggyError::InvalidUtf8)?
.to_string();
if name.len() != name_length as usize {
@@ -174,7 +157,6 @@ impl BytesSerializable for CreateTopic {
}
let command = CreateTopic {
stream_id,
- topic_id,
partitions_count,
compression_algorithm,
message_expiry,
@@ -190,9 +172,8 @@ impl Display for CreateTopic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
- "{}|{}|{}|{}|{}|{}|{}",
+ "{}|{}|{}|{}|{}|{}",
self.stream_id,
- self.topic_id.unwrap_or(0),
self.partitions_count,
self.message_expiry,
self.max_topic_size,
@@ -211,7 +192,6 @@ mod tests {
fn should_be_serialized_as_bytes() {
let command = CreateTopic {
stream_id: Identifier::numeric(1).unwrap(),
- topic_id: Some(2),
partitions_count: 3,
message_expiry: IggyExpiry::NeverExpire,
compression_algorithm: CompressionAlgorithm::None,
@@ -223,25 +203,23 @@ mod tests {
let mut position = 0;
let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
position += stream_id.get_size_bytes().as_bytes_usize();
- let topic_id = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
let partitions_count =
- u32::from_le_bytes(bytes[position + 4..position + 8].try_into().unwrap());
- let compression_algorithm = CompressionAlgorithm::from_code(bytes[position + 8]).unwrap();
+ u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
+ let compression_algorithm = CompressionAlgorithm::from_code(bytes[position + 4]).unwrap();
let message_expiry =
- u64::from_le_bytes(bytes[position + 9..position + 17].try_into().unwrap());
+ u64::from_le_bytes(bytes[position + 5..position + 13].try_into().unwrap());
let message_expiry: IggyExpiry = message_expiry.into();
let max_topic_size =
- u64::from_le_bytes(bytes[position + 17..position + 25].try_into().unwrap());
+ u64::from_le_bytes(bytes[position + 13..position + 21].try_into().unwrap());
let max_topic_size: MaxTopicSize = max_topic_size.into();
- let replication_factor = bytes[position + 25];
- let name_length = bytes[position + 26];
- let name = from_utf8(&bytes[position + 27..(position + 27 + name_length as usize)])
+ let replication_factor = bytes[position + 21];
+ let name_length = bytes[position + 22];
+ let name = from_utf8(&bytes[position + 23..(position + 23 + name_length as usize)])
.unwrap()
.to_string();
assert!(!bytes.is_empty());
assert_eq!(stream_id, command.stream_id);
- assert_eq!(topic_id, command.topic_id.unwrap());
assert_eq!(partitions_count, command.partitions_count);
assert_eq!(compression_algorithm, command.compression_algorithm);
assert_eq!(message_expiry, command.message_expiry);
@@ -254,7 +232,6 @@ mod tests {
#[test]
fn should_be_deserialized_from_bytes() {
let stream_id = Identifier::numeric(1).unwrap();
- let topic_id = 2u32;
let partitions_count = 3u32;
let compression_algorithm = CompressionAlgorithm::None;
let name = "test".to_string();
@@ -262,9 +239,8 @@ mod tests {
let max_topic_size = MaxTopicSize::ServerDefault;
let replication_factor = 1;
let stream_id_bytes = stream_id.to_bytes();
- let mut bytes = BytesMut::with_capacity(14 + stream_id_bytes.len() + name.len());
+ let mut bytes = BytesMut::with_capacity(10 + stream_id_bytes.len() + name.len());
bytes.put_slice(&stream_id_bytes);
- bytes.put_u32_le(topic_id);
bytes.put_u32_le(partitions_count);
bytes.put_u8(compression_algorithm.as_code());
bytes.put_u64_le(message_expiry.into());
@@ -279,7 +255,6 @@ mod tests {
let command = command.unwrap();
assert_eq!(command.stream_id, stream_id);
- assert_eq!(command.topic_id.unwrap(), topic_id);
assert_eq!(command.name, name);
assert_eq!(command.partitions_count, partitions_count);
assert_eq!(command.compression_algorithm, compression_algorithm);
diff --git a/core/common/src/commands/users/defaults.rs b/core/common/src/commands/users/defaults.rs
index f18c8e0da..3d60f18d9 100644
--- a/core/common/src/commands/users/defaults.rs
+++ b/core/common/src/commands/users/defaults.rs
@@ -23,6 +23,6 @@ pub const MIN_PASSWORD_LENGTH: usize = 3;
pub const MAX_PAT_LENGTH: usize = 100;
pub const MAX_PERSONAL_ACCESS_TOKEN_NAME_LENGTH: usize = 30;
pub const MIN_PERSONAL_ACCESS_TOKEN_NAME_LENGTH: usize = 3;
-pub const DEFAULT_ROOT_USER_ID: u32 = 1;
+pub const DEFAULT_ROOT_USER_ID: u32 = 0;
pub const DEFAULT_ROOT_USERNAME: &str = "iggy";
pub const DEFAULT_ROOT_PASSWORD: &str = "iggy";
diff --git a/core/common/src/error/iggy_error.rs b/core/common/src/error/iggy_error.rs
index cc8a284b1..6939c38ba 100644
--- a/core/common/src/error/iggy_error.rs
+++ b/core/common/src/error/iggy_error.rs
@@ -16,6 +16,7 @@
* under the License.
*/
+use crate::Identifier;
use crate::utils::topic_size::MaxTopicSize;
use crate::{IggyMessage, utils::byte_size::IggyByteSize};
use std::sync::Arc;
@@ -68,6 +69,8 @@ pub enum IggyError {
CannotOpenDatabase(String) = 19,
#[error("Resource with key: {0} was not found.")]
ResourceNotFound(String) = 20,
+ #[error("Cannot close WebSocket connection")]
+ CannotCloseWebSocketConnection(String) = 21,
#[error("Stale client")]
StaleClient = 30,
#[error("TCP error")]
@@ -174,6 +177,16 @@ pub enum IggyError {
CannotCreateEndpoint = 305,
#[error("Cannot parse URL")]
CannotParseUrl = 306,
+ #[error("WebSocket error")]
+ WebSocketError = 400,
+ #[error("WebSocket connection error")]
+ WebSocketConnectionError = 401,
+ #[error("WebSocket close error")]
+ WebSocketCloseError = 402,
+ #[error("WebSocket receive error")]
+ WebSocketReceiveError = 403,
+ #[error("WebSocket send error")]
+ WebSocketSendError = 404,
#[error("Cannot create streams directory, Path: {0}")]
CannotCreateStreamsDirectory(String) = 1000,
#[error("Cannot create stream with ID: {0} directory, Path: {1}")]
@@ -193,11 +206,11 @@ pub enum IggyError {
#[error("Failed to delete stream directory with ID: {0}")]
CannotDeleteStreamDirectory(u32) = 1008,
#[error("Stream with ID: {0} was not found.")]
- StreamIdNotFound(u32) = 1009,
+ StreamIdNotFound(Identifier) = 1009,
#[error("Stream with name: {0} was not found.")]
StreamNameNotFound(String) = 1010,
- #[error("Stream with ID: {0} already exists.")]
- StreamIdAlreadyExists(u32) = 1011,
+ #[error("Stream with directory {0} was not found.")]
+ StreamDirectoryNotFound(String) = 1011,
#[error("Stream with name: {0} already exists.")]
StreamNameAlreadyExists(String) = 1012,
#[error("Invalid stream name")]
@@ -211,9 +224,9 @@ pub enum IggyError {
)]
InvalidTopicSize(MaxTopicSize, IggyByteSize) = 1019,
#[error("Cannot create topics directory for stream with ID: {0}, Path: {1}")]
- CannotCreateTopicsDirectory(u32, String) = 2000,
+ CannotCreateTopicsDirectory(Identifier, String) = 2000,
#[error("Failed to create directory for topic with ID: {0} for stream with ID: {1}, Path: {2}")]
- CannotCreateTopicDirectory(u32, u32, String) = 2001,
+ CannotCreateTopicDirectory(usize, usize, String) = 2001,
#[error("Failed to create topic info file for topic with ID: {0} for stream with ID: {1}.")]
CannotCreateTopicInfo(u32, u32) = 2002,
#[error("Failed to update topic info for topic with ID: {0} for stream with ID: {1}.")]
@@ -231,13 +244,11 @@ pub enum IggyError {
#[error("Cannot poll topic")]
CannotPollTopic = 2009,
#[error("Topic with ID: {0} for stream with ID: {1} was not found.")]
- TopicIdNotFound(u32, u32) = 2010,
+ TopicIdNotFound(Identifier, Identifier) = 2010,
#[error("Topic with name: {0} for stream with ID: {1} was not found.")]
TopicNameNotFound(String, String) = 2011,
- #[error("Topic with ID: {0} for stream with ID: {1} already exists.")]
- TopicIdAlreadyExists(u32, u32) = 2012,
#[error("Topic with name: {0} for stream with ID: {1} already exists.")]
- TopicNameAlreadyExists(String, u32) = 2013,
+ TopicNameAlreadyExists(String, Identifier) = 2013,
#[error("Invalid topic name")]
InvalidTopicName = 2014,
#[error("Too many partitions")]
@@ -248,16 +259,20 @@ pub enum IggyError {
CannotReadTopics(u32) = 2017,
#[error("Invalid replication factor")]
InvalidReplicationFactor = 2018,
+ #[error("Invalid partitions count")]
+ InvalidPartitionsCount = 2019,
+ #[error("Topic directory: {0} not found")]
+ TopicDirectoryNotFound(String) = 2020,
#[error("Cannot create partition with ID: {0} for stream with ID: {1} and topic with ID: {2}")]
- CannotCreatePartition(u32, u32, u32) = 3000,
+ CannotCreatePartition(usize, usize, usize) = 3000,
#[error(
"Failed to create directory for partitions for stream with ID: {0} and topic with ID: {1}"
)]
- CannotCreatePartitionsDirectory(u32, u32) = 3001,
+ CannotCreatePartitionsDirectory(usize, usize) = 3001,
#[error(
"Failed to create directory for partition with ID: {0} for stream with ID: {1} and topic with ID: {2}"
)]
- CannotCreatePartitionDirectory(u32, u32, u32) = 3002,
+ CannotCreatePartitionDirectory(usize, usize, usize) = 3002,
#[error("Cannot open partition log file")]
CannotOpenPartitionLogFile = 3003,
#[error("Cannot read partitions directories.")]
@@ -265,17 +280,17 @@ pub enum IggyError {
#[error(
"Failed to delete partition with ID: {0} for stream with ID: {1} and topic with ID: {2}"
)]
- CannotDeletePartition(u32, u32, u32) = 3005,
+ CannotDeletePartition(usize, Identifier, Identifier) = 3005,
#[error(
"Failed to delete partition directory with ID: {0} for stream with ID: {1} and topic with ID: {2}"
)]
- CannotDeletePartitionDirectory(u32, u32, u32) = 3006,
+ CannotDeletePartitionDirectory(usize, usize, usize) = 3006,
#[error("Partition with ID: {0} for topic with ID: {1} for stream with ID: {2} was not found.")]
- PartitionNotFound(u32, u32, u32) = 3007,
+ PartitionNotFound(usize, Identifier, Identifier) = 3007,
#[error("Topic with ID: {0} for stream with ID: {1} has no partitions.")]
- NoPartitions(u32, u32) = 3008,
+ NoPartitions(Identifier, Identifier) = 3008,
#[error("Cannot read partitions for topic with ID: {0} for stream with ID: {1}")]
- TopicFull(u32, u32) = 3009,
+ TopicFull(Identifier, Identifier) = 3009,
#[error("Failed to delete consumer offsets directory for path: {0}")]
CannotDeleteConsumerOffsetsDirectory(String) = 3010,
#[error("Failed to delete consumer offset file for path: {0}")]
@@ -285,11 +300,15 @@ pub enum IggyError {
#[error("Failed to read consumers offsets from path: {0}")]
CannotReadConsumerOffsets(String) = 3020,
#[error("Consumer offset for consumer with ID: {0} was not found.")]
- ConsumerOffsetNotFound(u32) = 3021,
+ ConsumerOffsetNotFound(usize) = 3021,
+ #[error("Failed to resolve consumer with ID: {0}")]
+ NotResolvedConsumer(Identifier) = 3022,
+ #[error("Cannot open consumer offsets file for path: {0}")]
+ CannotOpenConsumerOffsetsFile(String) = 3023,
#[error("Segment not found")]
SegmentNotFound = 4000,
#[error("Segment with start offset: {0} and partition with ID: {1} is closed")]
- SegmentClosed(u64, u32) = 4001,
+ SegmentClosed(u64, usize) = 4001,
#[error("Segment size is invalid")]
InvalidSegmentSize(u64) = 4002,
#[error("Failed to create segment log file for Path: {0}.")]
@@ -386,29 +405,27 @@ pub enum IggyError {
#[error("Invalid offset: {0}")]
InvalidOffset(u64) = 4100,
#[error("Consumer group with ID: {0} for topic with ID: {1} was not found.")]
- ConsumerGroupIdNotFound(u32, u32) = 5000,
- #[error("Consumer group with ID: {0} for topic with ID: {1} already exists.")]
- ConsumerGroupIdAlreadyExists(u32, u32) = 5001,
+ ConsumerGroupIdNotFound(Identifier, Identifier) = 5000,
#[error("Invalid consumer group ID")]
InvalidConsumerGroupId = 5002,
#[error("Consumer group with name: {0} for topic with ID: {1} was not found.")]
- ConsumerGroupNameNotFound(String, String) = 5003,
+ ConsumerGroupNameNotFound(String, Identifier) = 5003,
#[error("Consumer group with name: {0} for topic with ID: {1} already exists.")]
- ConsumerGroupNameAlreadyExists(String, u32) = 5004,
+ ConsumerGroupNameAlreadyExists(String, Identifier) = 5004,
#[error("Invalid consumer group name")]
InvalidConsumerGroupName = 5005,
#[error(
"Consumer group member with ID: {0} for group with ID: {1} for topic with ID: {2} was not found."
)]
- ConsumerGroupMemberNotFound(u32, u32, u32) = 5006,
+ ConsumerGroupMemberNotFound(usize, Identifier, Identifier) = 5006,
#[error(
"Failed to create consumer group info file for ID: {0} for topic with ID: {1} for stream with ID: {2}."
)]
- CannotCreateConsumerGroupInfo(u32, u32, u32) = 5007,
+ CannotCreateConsumerGroupInfo(usize, Identifier, Identifier) = 5007,
#[error(
"Failed to delete consumer group info file for ID: {0} for topic with ID: {1} for stream with ID: {2}."
)]
- CannotDeleteConsumerGroupInfo(u32, u32, u32) = 5008,
+ CannotDeleteConsumerGroupInfo(usize, Identifier, Identifier) = 5008,
#[error("Base offset is missing")]
MissingBaseOffsetRetainedMessageBatch = 6000,
#[error("Last offset delta is missing")]
@@ -459,6 +476,19 @@ pub enum IggyError {
CannotReadIndexPosition = 10011,
#[error("Cannot read index timestamp")]
CannotReadIndexTimestamp = 10012,
+
+ #[error("Timestamp out of range: {0}")]
+ TimestampOutOfRange(u64) = 10013,
+
+ #[error("Shard not found for stream ID: {0}, topic ID: {1}, partition ID: {2}")]
+ ShardNotFound(usize, usize, usize) = 11000,
+ #[error("Shard communication error")]
+ ShardCommunicationError = 11001,
+
+ #[error("Cannot bind to socket with addr: {0}")]
+ CannotBindToSocket(String) = 12000,
+ #[error("Task execution timeout")]
+ TaskTimeout = 12001,
}
impl IggyError {
diff --git a/core/common/src/lib.rs b/core/common/src/lib.rs
index dbf26cefe..ababdfbb1 100644
--- a/core/common/src/lib.rs
+++ b/core/common/src/lib.rs
@@ -66,7 +66,10 @@ pub use types::configuration::tcp_config::tcp_client_config_builder::*;
pub use types::configuration::tcp_config::tcp_client_reconnection_config::*;
pub use types::configuration::tcp_config::tcp_connection_string_options::*;
pub use types::configuration::transport::TransportProtocol;
-pub use types::confirmation::*;
+pub use types::configuration::websocket_config::websocket_client_config::*;
+pub use types::configuration::websocket_config::websocket_client_config_builder::*;
+pub use types::configuration::websocket_config::websocket_client_reconnection_config::*;
+pub use types::configuration::websocket_config::websocket_connection_string_options::*;
pub use types::consumer::consumer_group::*;
pub use types::consumer::consumer_kind::*;
pub use types::consumer::consumer_offset_info::*;
diff --git a/core/common/src/locking/fast_async_lock.rs b/core/common/src/locking/fast_async_lock.rs
index 4e2fb00a2..3d81c8c15 100644
--- a/core/common/src/locking/fast_async_lock.rs
+++ b/core/common/src/locking/fast_async_lock.rs
@@ -16,14 +16,14 @@
* under the License.
*/
-use crate::locking::IggySharedMutFn;
+use crate::locking::IggyRwLockFn;
use fast_async_mutex::rwlock::{RwLock as FastAsyncRwLock, RwLockReadGuard, RwLockWriteGuard};
use std::sync::Arc;
#[derive(Debug)]
pub struct IggyFastAsyncRwLock<T>(Arc<FastAsyncRwLock<T>>);
-impl<T> IggySharedMutFn<T> for IggyFastAsyncRwLock<T>
+impl<T> IggyRwLockFn<T> for IggyFastAsyncRwLock<T>
where
T: Send + Sync,
{
diff --git a/core/common/src/locking/mod.rs b/core/common/src/locking/mod.rs
index 42d72ebb3..630de5adc 100644
--- a/core/common/src/locking/mod.rs
+++ b/core/common/src/locking/mod.rs
@@ -29,19 +29,19 @@ mod fast_async_lock;
#[cfg(feature = "tokio_lock")]
#[cfg(not(any(feature = "fast_async_lock")))]
-pub type IggySharedMut<T> = tokio_lock::IggyTokioRwLock<T>;
+pub type IggyRwLock<T> = tokio_lock::IggyTokioRwLock<T>;
//this can be used in the future to provide different locking mechanisms
#[cfg(feature = "fast_async_lock")]
-pub type IggySharedMut<T> = fast_async_lock::IggyFastAsyncRwLock<T>;
+pub type IggyRwLock<T> = fast_async_lock::IggyFastAsyncRwLock<T>;
#[allow(async_fn_in_trait)]
-pub trait IggySharedMutFn<T>: Send + Sync {
- type ReadGuard<'a>: Deref<Target = T> + Send
+pub trait IggyRwLockFn<T> {
+ type ReadGuard<'a>: Deref<Target = T>
where
T: 'a,
Self: 'a;
- type WriteGuard<'a>: DerefMut<Target = T> + Send
+ type WriteGuard<'a>: DerefMut<Target = T>
where
T: 'a,
Self: 'a;
diff --git a/core/common/src/locking/tokio_lock.rs b/core/common/src/locking/tokio_lock.rs
index 2e26b367e..d2e46ef1d 100644
--- a/core/common/src/locking/tokio_lock.rs
+++ b/core/common/src/locking/tokio_lock.rs
@@ -16,7 +16,7 @@
* under the License.
*/
-use crate::locking::IggySharedMutFn;
+use crate::locking::IggyRwLockFn;
use std::sync::Arc;
use tokio::sync::{RwLock as TokioRwLock, RwLockReadGuard, RwLockWriteGuard};
@@ -24,10 +24,7 @@ use tokio::sync::{RwLock as TokioRwLock, RwLockReadGuard, RwLockWriteGuard};
#[derive(Debug)]
pub struct IggyTokioRwLock<T>(Arc<TokioRwLock<T>>);
-impl<T> IggySharedMutFn<T> for IggyTokioRwLock<T>
-where
- T: Send + Sync,
-{
+impl<T> IggyRwLockFn<T> for IggyTokioRwLock<T> {
type ReadGuard<'a>
= RwLockReadGuard<'a, T>
where
diff --git a/core/common/src/types/args/mod.rs b/core/common/src/types/args/mod.rs
index 3010faa50..cc22f9439 100644
--- a/core/common/src/types/args/mod.rs
+++ b/core/common/src/types/args/mod.rs
@@ -28,7 +28,7 @@ use serde::{Deserialize, Serialize};
#[derive(Parser, Debug, Clone, Deserialize, Serialize, Default)]
#[command(author, version, about, long_about = None)]
pub struct ArgsOptional {
- /// The transport to use. Valid values are `quic`, `http` and `tcp`
+ /// The transport to use. Valid values are `quic`, `http`, `tcp` and `ws`
///
/// [default: tcp]
#[arg(long)]
@@ -198,12 +198,33 @@ pub struct ArgsOptional {
#[arg(long, default_missing_value(Some("true")), num_args(0..1))]
#[serde(skip_serializing_if = "Option::is_none")]
pub quic_validate_certificate: Option<bool>,
+
+ /// The optional server address for the WebSocket transport
+ ///
+ /// [default: 127.0.0.1:8092]
+ #[arg(long)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub websocket_server_address: Option<String>,
+
+ /// The optional number of max reconnect retries for the WebSocket transport
+ ///
+ /// [default: 3]
+ #[arg(long)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub websocket_reconnection_max_retries: Option<u32>,
+
+ /// The optional reconnect interval for the WebSocket transport
+ ///
+ /// [default: "1s"]
+ #[arg(long)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub websocket_reconnection_interval: Option<String>,
}
/// The arguments used by the `ClientProviderConfig` to create a client.
#[derive(Debug, Clone)]
pub struct Args {
- /// The transport to use. Valid values are `quic`, `http` and `tcp`
+ /// The transport to use. Valid values are `quic`, `http`, `tcp` and `ws`
pub transport: String,
/// Optional encryption key for the message payload used by the client
@@ -301,11 +322,42 @@ pub struct Args {
/// The optional heartbeat interval for the QUIC transport
pub quic_heartbeat_interval: String,
+
+ /// The optional server address for the WebSocket transport
+ pub websocket_server_address: String,
+
+ /// The optional number of maximum reconnect retries for the WebSocket transport
+ pub websocket_reconnection_enabled: bool,
+
+ /// The optional number of maximum reconnect retries for the WebSocket transport
+ pub websocket_reconnection_max_retries: Option<u32>,
+
+ /// The optional reconnect interval for the WebSocket transport
+ pub websocket_reconnection_interval: String,
+
+ /// The optional re-establish after last connection interval for WebSocket
+ pub websocket_reconnection_reestablish_after: String,
+
+ /// The optional heartbeat interval for the WebSocket transport
+ pub websocket_heartbeat_interval: String,
+
+ /// The optional TLS enabled for the WebSocket transport
+ pub websocket_tls_enabled: bool,
+
+ /// The optional TLS domain for the WebSocket transport
+ pub websocket_tls_domain: String,
+
+ /// The optional TLS CA file for the WebSocket transport
+ pub websocket_tls_ca_file: Option<String>,
+
+ /// The optional TLS validate certificate for the WebSocket transport
+ pub websocket_tls_validate_certificate: bool,
}
const QUIC_TRANSPORT: &str = "quic";
const HTTP_TRANSPORT: &str = "http";
const TCP_TRANSPORT: &str = "tcp";
+const WEBSOCKET_TRANSPORT: &str = "websocket";
impl Args {
pub fn get_server_address(&self) -> Option<String> {
@@ -318,6 +370,10 @@ impl Args {
.replace("localhost", "127.0.0.1"),
),
TCP_TRANSPORT => Some(self.tcp_server_address.replace("localhost", "127.0.0.1")),
+ WEBSOCKET_TRANSPORT => Some(
+ self.websocket_server_address
+ .replace("localhost", "127.0.0.1"),
+ ),
_ => None,
}
}
@@ -352,13 +408,23 @@ impl Default for Args {
quic_max_concurrent_bidi_streams: 10000,
quic_datagram_send_buffer_size: 100000,
quic_initial_mtu: 1200,
- quic_send_window: 100000,
- quic_receive_window: 100000,
+ quic_send_window: 1000000,
+ quic_receive_window: 1000000,
quic_response_buffer_size: 1048576,
quic_keep_alive_interval: 5000,
- quic_max_idle_timeout: 10000,
+ quic_max_idle_timeout: 100000,
quic_validate_certificate: false,
quic_heartbeat_interval: "5s".to_string(),
+ websocket_server_address: "127.0.0.1:8092".to_string(),
+ websocket_reconnection_enabled: true,
+ websocket_reconnection_max_retries: None,
+ websocket_reconnection_interval: "1s".to_string(),
+ websocket_reconnection_reestablish_after: "5s".to_string(),
+ websocket_heartbeat_interval: "5s".to_string(),
+ websocket_tls_enabled: false,
+ websocket_tls_domain: "localhost".to_string(),
+ websocket_tls_ca_file: None,
+ websocket_tls_validate_certificate: false,
}
}
}
@@ -447,6 +513,19 @@ impl From<Vec<ArgsOptional>> for Args {
if let Some(quic_validate_certificate) = optional_args.quic_validate_certificate {
args.quic_validate_certificate = quic_validate_certificate;
}
+ if let Some(websocket_server_address) = optional_args.websocket_server_address {
+ args.websocket_server_address = websocket_server_address;
+ }
+ if let Some(websocket_reconnection_retries) =
+ optional_args.websocket_reconnection_max_retries
+ {
+ args.websocket_reconnection_max_retries = Some(websocket_reconnection_retries);
+ }
+ if let Some(websocket_reconnection_interval) =
+ optional_args.websocket_reconnection_interval
+ {
+ args.websocket_reconnection_interval = websocket_reconnection_interval;
+ }
}
args
diff --git a/core/common/src/types/configuration/auth_config/connection_string.rs b/core/common/src/types/configuration/auth_config/connection_string.rs
index cb44c4684..391633d53 100644
--- a/core/common/src/types/configuration/auth_config/connection_string.rs
+++ b/core/common/src/types/configuration/auth_config/connection_string.rs
@@ -43,7 +43,11 @@ impl<T: ConnectionStringOptions + Default> ConnectionString<T> {
}
pub fn new(connection_string: &str) -> Result<Self, IggyError> {
- let connection_string = connection_string.split("://").collect::<Vec<&str>>()[1];
+ let protocol_parts = connection_string.split("://").collect::<Vec<&str>>();
+ if protocol_parts.len() != 2 {
+ return Err(IggyError::InvalidConnectionString);
+ }
+ let connection_string = protocol_parts[1];
let parts = connection_string.split('@').collect::<Vec<&str>>();
let mut username = "";
let mut password = "";
diff --git a/core/common/src/types/configuration/mod.rs b/core/common/src/types/configuration/mod.rs
index 75f03ed22..667e58884 100644
--- a/core/common/src/types/configuration/mod.rs
+++ b/core/common/src/types/configuration/mod.rs
@@ -20,3 +20,4 @@ pub(crate) mod http_config;
pub(crate) mod quic_config;
pub(crate) mod tcp_config;
pub(crate) mod transport;
+pub(crate) mod websocket_config;
diff --git a/core/common/src/types/configuration/transport.rs b/core/common/src/types/configuration/transport.rs
index 356295f13..45c78eb63 100644
--- a/core/common/src/types/configuration/transport.rs
+++ b/core/common/src/types/configuration/transport.rs
@@ -32,6 +32,8 @@ pub enum TransportProtocol {
Quic = 2,
#[strum(to_string = "http")]
Http = 3,
+ #[strum(to_string = "ws")]
+ WebSocket = 4,
}
impl TransportProtocol {
@@ -48,6 +50,7 @@ impl TryFrom<u8> for TransportProtocol {
v if v == TransportProtocol::Tcp as u8 => Ok(TransportProtocol::Tcp),
v if v == TransportProtocol::Quic as u8 => Ok(TransportProtocol::Quic),
v if v == TransportProtocol::Http as u8 => Ok(TransportProtocol::Http),
+ v if v == TransportProtocol::WebSocket as u8 => Ok(TransportProtocol::WebSocket),
_ => Err(IggyError::InvalidCommand),
}
}
@@ -98,6 +101,9 @@ impl<'de> Deserialize<'de> for TransportProtocol {
v if v == TransportProtocol::Tcp as u8 => Ok(TransportProtocol::Tcp),
v if v == TransportProtocol::Quic as u8 => Ok(TransportProtocol::Quic),
v if v == TransportProtocol::Http as u8 => Ok(TransportProtocol::Http),
+ v if v == TransportProtocol::WebSocket as u8 => {
+ Ok(TransportProtocol::WebSocket)
+ }
_ => Err(serde::de::Error::custom(format!(
"invalid transport protocol number: {}",
value
diff --git a/examples/csharp/src/GettingStarted/Iggy_SDK.Examples.GettingStarted.Consumer/Program.cs b/core/common/src/types/configuration/websocket_config/mod.rs
similarity index 53%
rename from examples/csharp/src/GettingStarted/Iggy_SDK.Examples.GettingStarted.Consumer/Program.cs
rename to core/common/src/types/configuration/websocket_config/mod.rs
index deb1ae1df..46de921e5 100644
--- a/examples/csharp/src/GettingStarted/Iggy_SDK.Examples.GettingStarted.Consumer/Program.cs
+++ b/core/common/src/types/configuration/websocket_config/mod.rs
@@ -1,4 +1,4 @@
-// Licensed to the Apache Software Foundation (ASF) under one
+// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
@@ -15,22 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-using Apache.Iggy.Configuration;
-using Apache.Iggy.Enums;
-using Apache.Iggy.Factory;
-using Iggy_SDK.Examples.GettingStarted.Consumer;
-using Microsoft.Extensions.Logging;
-
-var loggerFactory = LoggerFactory.Create(b => { b.AddConsole(); });
-var logger = loggerFactory.CreateLogger<Program>();
-
-var client = IggyClientFactory.CreateClient(new IggyClientConfigurator()
-{
- BaseAddress = Utils.GetTcpServerAddr(args, logger),
- Protocol = Protocol.Tcp,
- LoggerFactory = loggerFactory
-});
-
-await client.LoginUser("iggy", "iggy");
-
-await Utils.ConsumeMessages(client, logger);
+pub mod websocket_client_config;
+pub mod websocket_client_config_builder;
+pub mod websocket_client_reconnection_config;
+pub mod websocket_connection_string_options;
diff --git a/core/common/src/types/configuration/websocket_config/websocket_client_config.rs b/core/common/src/types/configuration/websocket_config/websocket_client_config.rs
new file mode 100644
index 000000000..58bc5d9e3
--- /dev/null
+++ b/core/common/src/types/configuration/websocket_config/websocket_client_config.rs
@@ -0,0 +1,192 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use crate::types::configuration::auth_config::connection_string::ConnectionString;
+use crate::types::configuration::websocket_config::websocket_connection_string_options::WebSocketConnectionStringOptions;
+use crate::{AutoLogin, IggyDuration, WebSocketClientReconnectionConfig};
+use compio_ws::WebSocketConfig as TungsteniteConfig;
+use std::fmt::{Display, Formatter};
+use std::str::FromStr;
+
+/// Configuration for the WebSocket client.
+#[derive(Debug, Clone)]
+pub struct WebSocketClientConfig {
+ /// The address of the Iggy server.
+ pub server_address: String,
+ /// Whether to automatically login user after establishing connection.
+ pub auto_login: AutoLogin,
+ /// Whether to automatically reconnect when disconnected.
+ pub reconnection: WebSocketClientReconnectionConfig,
+ /// Interval of heartbeats sent by the client
+ pub heartbeat_interval: IggyDuration,
+ /// WebSocket-specific configuration.
+ pub ws_config: WebSocketConfig,
+ /// Whether tls is enabled
+ pub tls_enabled: bool,
+ /// The domain to use for TLS
+ pub tls_domain: String,
+ /// The path to the CA file for TLS
+ pub tls_ca_file: Option<String>,
+ /// Whether to validate the TLS certificate
+ pub tls_validate_certificate: bool,
+}
+
+/// WebSocket-specific configuration that maps to tungstenite options.
+/// Uses same structure as server-side WebSocketConfig but for client.
+#[derive(Debug, Clone)]
+pub struct WebSocketConfig {
+ /// Read buffer size.
+ pub read_buffer_size: Option<usize>,
+ /// Write buffer size.
+ pub write_buffer_size: Option<usize>,
+ /// Maximum write buffer size.
+ pub max_write_buffer_size: Option<usize>,
+ /// Maximum message size.
+ pub max_message_size: Option<usize>,
+ /// Maximum frame size.
+ pub max_frame_size: Option<usize>,
+ /// Accept unmasked frames (client should typically keep as false).
+ pub accept_unmasked_frames: bool,
+}
+
+impl Default for WebSocketClientConfig {
+ fn default() -> Self {
+ WebSocketClientConfig {
+ server_address: "127.0.0.1:8092".to_string(),
+ auto_login: AutoLogin::Disabled,
+ reconnection: WebSocketClientReconnectionConfig::default(),
+ heartbeat_interval: IggyDuration::from_str("5s").unwrap(),
+ ws_config: WebSocketConfig::default(),
+ tls_enabled: false,
+ tls_domain: "localhost".to_string(),
+ tls_ca_file: None,
+ tls_validate_certificate: false,
+ }
+ }
+}
+
+impl Default for WebSocketConfig {
+ fn default() -> Self {
+ // Use tungstenite defaults
+ let tungstenite_config = TungsteniteConfig::default();
+ WebSocketConfig {
+ read_buffer_size: Some(tungstenite_config.read_buffer_size),
+ write_buffer_size: Some(tungstenite_config.write_buffer_size),
+ max_write_buffer_size: Some(tungstenite_config.max_write_buffer_size),
+ max_message_size: tungstenite_config.max_message_size,
+ max_frame_size: tungstenite_config.max_frame_size,
+ accept_unmasked_frames: false,
+ }
+ }
+}
+
+impl WebSocketConfig {
+ /// Convert to tungstenite WebSocketConfig so we can use tungstenite defaults
+ pub fn to_tungstenite_config(&self) -> TungsteniteConfig {
+ let mut config = TungsteniteConfig::default();
+
+ if let Some(read_buf_size) = self.read_buffer_size {
+ config = config.read_buffer_size(read_buf_size);
+ }
+
+ if let Some(write_buf_size) = self.write_buffer_size {
+ config = config.write_buffer_size(write_buf_size);
+ }
+
+ if let Some(max_write_buf_size) = self.max_write_buffer_size {
+ config = config.max_write_buffer_size(max_write_buf_size);
+ }
+
+ if let Some(max_msg_size) = self.max_message_size {
+ config = config.max_message_size(Some(max_msg_size));
+ }
+
+ if let Some(max_frame_size) = self.max_frame_size {
+ config = config.max_frame_size(Some(max_frame_size));
+ }
+
+ config = config.accept_unmasked_frames(self.accept_unmasked_frames);
+
+ config
+ }
+}
+
+impl From<ConnectionString<WebSocketConnectionStringOptions>> for WebSocketClientConfig {
+ fn from(connection_string: ConnectionString<WebSocketConnectionStringOptions>) -> Self {
+ let options = connection_string.options();
+
+ let mut ws_config = WebSocketConfig::default();
+
+ // Apply connection string options to WebSocket config
+ if let Some(read_buf_size) = options.read_buffer_size() {
+ ws_config.read_buffer_size = Some(read_buf_size);
+ }
+ if let Some(write_buf_size) = options.write_buffer_size() {
+ ws_config.write_buffer_size = Some(write_buf_size);
+ }
+ if let Some(max_write_buf_size) = options.max_write_buffer_size() {
+ ws_config.max_write_buffer_size = Some(max_write_buf_size);
+ }
+ if let Some(max_msg_size) = options.max_message_size() {
+ ws_config.max_message_size = Some(max_msg_size);
+ }
+ if let Some(max_frame_size) = options.max_frame_size() {
+ ws_config.max_frame_size = Some(max_frame_size);
+ }
+ if let Some(accept_unmasked) = options.accept_unmasked_frames() {
+ ws_config.accept_unmasked_frames = accept_unmasked;
+ }
+
+ WebSocketClientConfig {
+ server_address: connection_string.server_address().into(),
+ auto_login: connection_string.auto_login().to_owned(),
+ reconnection: connection_string.options().reconnection().to_owned(),
+ heartbeat_interval: connection_string.options().heartbeat_interval(),
+ ws_config,
+ tls_enabled: options.tls_enabled(),
+ tls_domain: options.tls_domain().into(),
+ tls_ca_file: options.tls_ca_file().map(|s| s.to_string()),
+ tls_validate_certificate: options.tls_validate_certificate(),
+ }
+ }
+}
+
+impl Display for WebSocketClientConfig {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{{ server_address: {}, reconnection: {}, heartbeat_interval: {}, ws_config: {} }}",
+ self.server_address, self.reconnection, self.heartbeat_interval, self.ws_config
+ )
+ }
+}
+
+impl Display for WebSocketConfig {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{{ read_buffer_size: {:?}, write_buffer_size: {:?}, max_write_buffer_size: {:?}, max_message_size: {:?}, max_frame_size: {:?}, accept_unmasked_frames: {} }}",
+ self.read_buffer_size,
+ self.write_buffer_size,
+ self.max_write_buffer_size,
+ self.max_message_size,
+ self.max_frame_size,
+ self.accept_unmasked_frames
+ )
+ }
+}
diff --git a/core/common/src/types/configuration/websocket_config/websocket_client_config_builder.rs b/core/common/src/types/configuration/websocket_config/websocket_client_config_builder.rs
new file mode 100644
index 000000000..27e296f6c
--- /dev/null
+++ b/core/common/src/types/configuration/websocket_config/websocket_client_config_builder.rs
@@ -0,0 +1,158 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use crate::{AutoLogin, IggyDuration, IggyError, WebSocketClientConfig};
+use std::net::SocketAddr;
+
+/// Builder for the WebSocket client configuration.
+/// Allows configuring the WebSocket client with custom settings or using defaults:
+/// - `server_address`: Default is "127.0.0.1:8092"
+/// - `auto_login`: Default is AutoLogin::Disabled.
+/// - `reconnection`: Default is enabled unlimited retries and 1 second interval.
+/// - `heartbeat_interval`: Default is 5 seconds.
+/// - WebSocket-specific options with sensible defaults from tungstenite.
+#[derive(Debug, Default)]
+pub struct WebSocketClientConfigBuilder {
+ config: WebSocketClientConfig,
+}
+
+impl WebSocketClientConfigBuilder {
+ pub fn new() -> Self {
+ WebSocketClientConfigBuilder::default()
+ }
+
+ /// Sets the server address for the WebSocket client.
+ pub fn with_server_address(mut self, server_address: String) -> Self {
+ self.config.server_address = server_address;
+ self
+ }
+
+ /// Sets the auto sign in during connection.
+ pub fn with_auto_sign_in(mut self, auto_sign_in: AutoLogin) -> Self {
+ self.config.auto_login = auto_sign_in;
+ self
+ }
+
+ pub fn with_enabled_reconnection(mut self) -> Self {
+ self.config.reconnection.enabled = true;
+ self
+ }
+
+ /// Sets the number of retries when connecting to the server.
+ pub fn with_reconnection_max_retries(mut self, max_retries: Option<u32>) -> Self {
+ self.config.reconnection.max_retries = max_retries;
+ self
+ }
+
+ /// Sets the interval between retries when connecting to the server.
+ pub fn with_reconnection_interval(mut self, interval: IggyDuration) -> Self {
+ self.config.reconnection.interval = interval;
+ self
+ }
+
+ /// Sets the time to wait before attempting to reestablish connection.
+ pub fn with_reestablish_after(mut self, reestablish_after: IggyDuration) -> Self {
+ self.config.reconnection.reestablish_after = reestablish_after;
+ self
+ }
+
+ /// Sets the heartbeat interval.
+ pub fn with_heartbeat_interval(mut self, heartbeat_interval: IggyDuration) -> Self {
+ self.config.heartbeat_interval = heartbeat_interval;
+ self
+ }
+
+ /// Sets the read buffer size.
+ pub fn with_read_buffer_size(mut self, size: usize) -> Self {
+ self.config.ws_config.read_buffer_size = Some(size);
+ self
+ }
+
+ /// Sets the write buffer size.
+ pub fn with_write_buffer_size(mut self, size: usize) -> Self {
+ self.config.ws_config.write_buffer_size = Some(size);
+ self
+ }
+
+ /// Sets the maximum write buffer size.
+ pub fn with_max_write_buffer_size(mut self, size: usize) -> Self {
+ self.config.ws_config.max_write_buffer_size = Some(size);
+ self
+ }
+
+ /// Sets the maximum message size.
+ pub fn with_max_message_size(mut self, size: usize) -> Self {
+ self.config.ws_config.max_message_size = Some(size);
+ self
+ }
+
+ /// Sets the maximum frame size.
+ pub fn with_max_frame_size(mut self, size: usize) -> Self {
+ self.config.ws_config.max_frame_size = Some(size);
+ self
+ }
+
+ /// Sets whether to accept unmasked frames.
+ /// Note: Clients should typically keep this as false for RFC compliance.
+ pub fn with_accept_unmasked_frames(mut self, accept: bool) -> Self {
+ self.config.ws_config.accept_unmasked_frames = accept;
+ self
+ }
+
+ /// Sets whether to use TLS when connecting to the server.
+ pub fn with_tls_enabled(mut self, tls_enabled: bool) -> Self {
+ self.config.tls_enabled = tls_enabled;
+ self
+ }
+
+ /// Sets the domain to use for TLS when connecting to the server.
+ pub fn with_tls_domain(mut self, tls_domain: String) -> Self {
+ self.config.tls_domain = tls_domain;
+ self
+ }
+
+ /// Sets the path to the CA file for TLS.
+ pub fn with_tls_ca_file(mut self, tls_ca_file: String) -> Self {
+ self.config.tls_ca_file = Some(tls_ca_file);
+ self
+ }
+
+ /// Sets whether to validate the TLS certificate.
+ pub fn with_tls_validate_certificate(mut self, tls_validate_certificate: bool) -> Self {
+ self.config.tls_validate_certificate = tls_validate_certificate;
+ self
+ }
+
+ /// Builds the WebSocket client configuration.
+ pub fn build(self) -> Result<WebSocketClientConfig, IggyError> {
+ let addr = self.config.server_address.trim();
+
+ // Check if it's a valid socket address or host:port format
+ if addr.parse::<SocketAddr>().is_err() {
+ let (host, port) = addr.rsplit_once(':').unwrap_or((addr, ""));
+ if port.is_empty() || port.parse::<u16>().is_err() {
+ return Err(IggyError::InvalidIpAddress(
+ host.to_owned(),
+ port.to_owned(),
+ ));
+ }
+ }
+
+ Ok(self.config)
+ }
+}
diff --git a/core/common/src/types/configuration/websocket_config/websocket_client_reconnection_config.rs b/core/common/src/types/configuration/websocket_config/websocket_client_reconnection_config.rs
new file mode 100644
index 000000000..c7a86aa90
--- /dev/null
+++ b/core/common/src/types/configuration/websocket_config/websocket_client_reconnection_config.rs
@@ -0,0 +1,61 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use crate::IggyDuration;
+use serde::{Deserialize, Serialize};
+use std::fmt::{Display, Formatter};
+use std::str::FromStr;
+
+/// WebSocket client reconnection configuration.
+/// It consists of the following fields:
+/// - `enabled`: whether to enable reconnection.
+/// - `max_retries`: the maximum number of retries. If None, will retry infinitely.
+/// - `interval`: the interval between retries.
+/// - `reestablish_after`: the time to wait before attempting to reestablish connection.
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct WebSocketClientReconnectionConfig {
+ /// Whether to enable reconnection.
+ pub enabled: bool,
+ /// The maximum number of retries. If None, will retry infinitely.
+ pub max_retries: Option<u32>,
+ /// The interval between retries.
+ pub interval: IggyDuration,
+ /// The time to wait before attempting to reestablish connection.
+ pub reestablish_after: IggyDuration,
+}
+
+impl Default for WebSocketClientReconnectionConfig {
+ fn default() -> Self {
+ WebSocketClientReconnectionConfig {
+ enabled: true,
+ max_retries: None,
+ interval: IggyDuration::from_str("1s").unwrap(),
+ reestablish_after: IggyDuration::from_str("5s").unwrap(),
+ }
+ }
+}
+
+impl Display for WebSocketClientReconnectionConfig {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{{ enabled: {}, max_retries: {:?}, interval: {}, reestablish_after: {} }}",
+ self.enabled, self.max_retries, self.interval, self.reestablish_after
+ )
+ }
+}
diff --git a/core/common/src/types/configuration/websocket_config/websocket_connection_string_options.rs b/core/common/src/types/configuration/websocket_config/websocket_connection_string_options.rs
new file mode 100644
index 000000000..85c5fb205
--- /dev/null
+++ b/core/common/src/types/configuration/websocket_config/websocket_connection_string_options.rs
@@ -0,0 +1,209 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::{ConnectionStringOptions, IggyDuration, IggyError, WebSocketClientReconnectionConfig};
+use std::str::FromStr;
+
+#[derive(Debug, Clone)]
+pub struct WebSocketConnectionStringOptions {
+ heartbeat_interval: IggyDuration,
+ reconnection: WebSocketClientReconnectionConfig,
+
+ read_buffer_size: Option<usize>,
+ write_buffer_size: Option<usize>,
+ max_write_buffer_size: Option<usize>,
+ max_message_size: Option<usize>,
+ max_frame_size: Option<usize>,
+
+ accept_unmasked_frames: Option<bool>,
+
+ tls_enabled: bool,
+ tls_domain: String,
+ tls_ca_file: Option<String>,
+ tls_validate_certificate: bool,
+}
+
+impl WebSocketConnectionStringOptions {
+ pub fn heartbeat_interval(&self) -> IggyDuration {
+ self.heartbeat_interval
+ }
+
+ pub fn reconnection(&self) -> &WebSocketClientReconnectionConfig {
+ &self.reconnection
+ }
+
+ pub fn read_buffer_size(&self) -> Option<usize> {
+ self.read_buffer_size
+ }
+
+ pub fn write_buffer_size(&self) -> Option<usize> {
+ self.write_buffer_size
+ }
+
+ pub fn max_write_buffer_size(&self) -> Option<usize> {
+ self.max_write_buffer_size
+ }
+
+ pub fn max_message_size(&self) -> Option<usize> {
+ self.max_message_size
+ }
+
+ pub fn max_frame_size(&self) -> Option<usize> {
+ self.max_frame_size
+ }
+
+ pub fn accept_unmasked_frames(&self) -> Option<bool> {
+ self.accept_unmasked_frames
+ }
+
+ pub fn tls_enabled(&self) -> bool {
+ self.tls_enabled
+ }
+
+ pub fn tls_domain(&self) -> &str {
+ &self.tls_domain
+ }
+
+ pub fn tls_ca_file(&self) -> Option<&str> {
+ self.tls_ca_file.as_deref()
+ }
+
+ pub fn tls_validate_certificate(&self) -> bool {
+ self.tls_validate_certificate
+ }
+}
+
+impl ConnectionStringOptions for WebSocketConnectionStringOptions {
+ fn retries(&self) -> Option<u32> {
+ self.reconnection.max_retries
+ }
+
+ fn heartbeat_interval(&self) -> IggyDuration {
+ self.heartbeat_interval
+ }
+
+ fn parse_options(options_str: &str) -> Result<Self, IggyError> {
+ let mut parsed_options = WebSocketConnectionStringOptions::default();
+
+ if options_str.is_empty() {
+ return Ok(parsed_options);
+ }
+
+ for option in options_str.split('&') {
+ let parts: Vec<&str> = option.split('=').collect();
+ if parts.len() != 2 {
+ return Err(IggyError::InvalidConnectionString);
+ }
+
+ match parts[0] {
+ "heartbeat_interval" => {
+ parsed_options.heartbeat_interval = IggyDuration::from_str(parts[1])
+ .map_err(|_| IggyError::InvalidConnectionString)?;
+ }
+ "reconnection_retries" => {
+ let retries = match parts[1] {
+ "unlimited" => None,
+ val => Some(
+ val.parse::<u32>()
+ .map_err(|_| IggyError::InvalidConnectionString)?,
+ ),
+ };
+ parsed_options.reconnection.max_retries = retries;
+ }
+ "reconnection_interval" => {
+ parsed_options.reconnection.interval = IggyDuration::from_str(parts[1])
+ .map_err(|_| IggyError::InvalidConnectionString)?;
+ }
+ "reestablish_after" => {
+ parsed_options.reconnection.reestablish_after =
+ IggyDuration::from_str(parts[1])
+ .map_err(|_| IggyError::InvalidConnectionString)?;
+ }
+
+ "read_buffer_size" => {
+ let size = parts[1]
+ .parse::<usize>()
+ .map_err(|_| IggyError::InvalidConnectionString)?;
+ parsed_options.read_buffer_size = Some(size);
+ }
+ "write_buffer_size" => {
+ let size = parts[1]
+ .parse::<usize>()
+ .map_err(|_| IggyError::InvalidConnectionString)?;
+ parsed_options.write_buffer_size = Some(size);
+ }
+ "max_write_buffer_size" => {
+ let size = parts[1]
+ .parse::<usize>()
+ .map_err(|_| IggyError::InvalidConnectionString)?;
+ parsed_options.max_write_buffer_size = Some(size);
+ }
+ "max_message_size" => {
+ let size = parts[1]
+ .parse::<usize>()
+ .map_err(|_| IggyError::InvalidConnectionString)?;
+ parsed_options.max_message_size = Some(size);
+ }
+ "max_frame_size" => {
+ let size = parts[1]
+ .parse::<usize>()
+ .map_err(|_| IggyError::InvalidConnectionString)?;
+ parsed_options.max_frame_size = Some(size);
+ }
+
+ "accept_unmasked_frames" => {
+ parsed_options.accept_unmasked_frames = Some(parts[1] == "true");
+ }
+
+ "tls" => {
+ parsed_options.tls_enabled = parts[1] == "true";
+ }
+ "tls_domain" => {
+ parsed_options.tls_domain = parts[1].to_string();
+ }
+ "tls_ca_file" => {
+ parsed_options.tls_ca_file = Some(parts[1].to_string());
+ }
+ "tls_validate_certificate" => {
+ parsed_options.tls_validate_certificate = parts[1] == "true";
+ }
+
+ _ => return Err(IggyError::InvalidConnectionString),
+ }
+ }
+ Ok(parsed_options)
+ }
+}
+
+impl Default for WebSocketConnectionStringOptions {
+ fn default() -> Self {
+ WebSocketConnectionStringOptions {
+ heartbeat_interval: IggyDuration::from_str("5s").unwrap(),
+ reconnection: WebSocketClientReconnectionConfig::default(),
+ read_buffer_size: None,
+ write_buffer_size: None,
+ max_write_buffer_size: None,
+ max_message_size: None,
+ max_frame_size: None,
+ accept_unmasked_frames: None,
+ tls_enabled: false,
+ tls_domain: "".to_string(),
+ tls_ca_file: None,
+ tls_validate_certificate: false,
+ }
+ }
+}
diff --git a/core/common/src/types/confirmation/mod.rs b/core/common/src/types/confirmation/mod.rs
deleted file mode 100644
index 5289a6274..000000000
--- a/core/common/src/types/confirmation/mod.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use serde::{Deserialize, Serialize};
-use strum::{Display, EnumString};
-
-#[derive(Clone, Copy, Debug, Default, Display, Serialize, Deserialize, EnumString, PartialEq)]
-#[strum(serialize_all = "snake_case")]
-pub enum Confirmation {
- #[default]
- Wait,
- NoWait,
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use std::str::FromStr;
-
- #[test]
- fn test_to_string() {
- assert_eq!(Confirmation::Wait.to_string(), "wait");
- assert_eq!(Confirmation::NoWait.to_string(), "no_wait");
- }
-
- #[test]
- fn test_from_str() {
- assert_eq!(Confirmation::from_str("wait").unwrap(), Confirmation::Wait);
- assert_eq!(
- Confirmation::from_str("no_wait").unwrap(),
- Confirmation::NoWait
- );
- }
-
- #[test]
- fn test_default() {
- assert_eq!(Confirmation::default(), Confirmation::Wait);
- }
-}
diff --git a/core/common/src/types/identifier/mod.rs b/core/common/src/types/identifier/mod.rs
index a12ab4d8a..bd4a5ccc0 100644
--- a/core/common/src/types/identifier/mod.rs
+++ b/core/common/src/types/identifier/mod.rs
@@ -64,7 +64,7 @@ impl Default for Identifier {
Self {
kind: IdKind::default(),
length: 4,
- value: 1u32.to_le_bytes().to_vec(),
+ value: 0u32.to_le_bytes().to_vec(),
}
}
}
@@ -157,9 +157,11 @@ impl Identifier {
/// Creates a new identifier from the given numeric value.
pub fn numeric(value: u32) -> Result<Self, IggyError> {
+ /*
if value == 0 {
return Err(IggyError::InvalidIdentifier);
}
+ */
Ok(Self {
kind: IdKind::Numeric,
@@ -365,11 +367,6 @@ mod tests {
assert!(Identifier::numeric(1).is_ok());
}
- #[test]
- fn identifier_with_a_value_of_zero_should_be_invalid() {
- assert!(Identifier::numeric(0).is_err());
- }
-
#[test]
fn identifier_with_a_value_of_non_empty_string_should_be_valid() {
assert!(Identifier::named("test").is_ok());
diff --git a/core/common/src/types/mod.rs b/core/common/src/types/mod.rs
index dd8a2dea9..96b41233d 100644
--- a/core/common/src/types/mod.rs
+++ b/core/common/src/types/mod.rs
@@ -22,7 +22,6 @@ pub(crate) mod cluster;
pub(crate) mod command;
pub(crate) mod compression;
pub(crate) mod configuration;
-pub(crate) mod confirmation;
pub(crate) mod consumer;
pub(crate) mod diagnostic;
pub(crate) mod identifier;
diff --git a/core/common/src/types/permissions/permissions_global.rs b/core/common/src/types/permissions/permissions_global.rs
index e2038009e..8291d48f9 100644
--- a/core/common/src/types/permissions/permissions_global.rs
+++ b/core/common/src/types/permissions/permissions_global.rs
@@ -35,7 +35,7 @@ pub struct Permissions {
pub global: GlobalPermissions,
/// Stream permissions are applied to a specific stream.
- pub streams: Option<AHashMap<u32, StreamPermissions>>,
+ pub streams: Option<AHashMap<usize, StreamPermissions>>,
}
/// `GlobalPermissions` are applied to all streams without a need to specify them one by one in the `streams` field.
@@ -142,7 +142,7 @@ pub struct StreamPermissions {
pub send_messages: bool,
/// The `topics` field allows to define the granular permissions for each topic of a stream.
- pub topics: Option<AHashMap<u32, TopicPermissions>>,
+ pub topics: Option<AHashMap<usize, TopicPermissions>>,
}
/// `TopicPermissions` are applied to a specific topic of a stream. This is the lowest level of permissions.
@@ -237,7 +237,7 @@ impl BytesSerializable for Permissions {
let streams_count = streams.len();
let mut current_stream = 1;
for (stream_id, stream) in streams {
- bytes.put_u32_le(*stream_id);
+ bytes.put_u32_le(*stream_id as u32);
bytes.put_u8(if stream.manage_stream { 1 } else { 0 });
bytes.put_u8(if stream.read_stream { 1 } else { 0 });
bytes.put_u8(if stream.manage_topics { 1 } else { 0 });
@@ -249,7 +249,7 @@ impl BytesSerializable for Permissions {
let topics_count = topics.len();
let mut current_topic = 1;
for (topic_id, topic) in topics {
- bytes.put_u32_le(*topic_id);
+ bytes.put_u32_le(*topic_id as u32);
bytes.put_u8(if topic.manage_topic { 1 } else { 0 });
bytes.put_u8(if topic.read_topic { 1 } else { 0 });
bytes.put_u8(if topic.poll_messages { 1 } else { 0 });
@@ -296,7 +296,7 @@ impl BytesSerializable for Permissions {
if bytes.get_u8() == 1 {
let mut streams_map = AHashMap::new();
loop {
- let stream_id = bytes.get_u32_le();
+ let stream_id = bytes.get_u32_le() as usize;
let manage_stream = bytes.get_u8() == 1;
let read_stream = bytes.get_u8() == 1;
let manage_topics = bytes.get_u8() == 1;
@@ -307,7 +307,7 @@ impl BytesSerializable for Permissions {
if bytes.get_u8() == 1 {
let mut topics_map = AHashMap::new();
loop {
- let topic_id = bytes.get_u32_le();
+ let topic_id = bytes.get_u32_le() as usize;
let manage_topic = bytes.get_u8() == 1;
let read_topic = bytes.get_u8() == 1;
let poll_messages = bytes.get_u8() == 1;
diff --git a/core/common/src/utils/crypto.rs b/core/common/src/utils/crypto.rs
index 34cd72c7b..fca62dde1 100644
--- a/core/common/src/utils/crypto.rs
+++ b/core/common/src/utils/crypto.rs
@@ -18,12 +18,11 @@
use crate::IggyError;
use crate::text;
-use aes_gcm::aead::generic_array::GenericArray;
use aes_gcm::aead::{Aead, OsRng};
use aes_gcm::{AeadCore, Aes256Gcm, KeyInit};
use std::fmt::Debug;
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum EncryptorKind {
Aes256Gcm(Aes256GcmEncryptor),
}
@@ -46,6 +45,7 @@ pub trait Encryptor {
fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, IggyError>;
}
+#[derive(Clone)]
pub struct Aes256GcmEncryptor {
cipher: Aes256Gcm,
}
@@ -62,7 +62,7 @@ impl Aes256GcmEncryptor {
return Err(IggyError::InvalidEncryptionKey);
}
Ok(Self {
- cipher: Aes256Gcm::new(GenericArray::from_slice(key)),
+ cipher: Aes256Gcm::new(key.into()),
})
}
@@ -83,7 +83,7 @@ impl Encryptor for Aes256GcmEncryptor {
}
fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, IggyError> {
- let nonce = GenericArray::from_slice(&data[0..12]);
+ let nonce = (&data[0..12]).into();
let payload = self.cipher.decrypt(nonce, &data[12..]);
if payload.is_err() {
return Err(IggyError::CannotDecryptData);
diff --git a/core/configs/server.toml b/core/configs/server.toml
index 5b9d0ad4e..ea1be6c66 100644
--- a/core/configs/server.toml
+++ b/core/configs/server.toml
@@ -53,7 +53,7 @@ archiver_enabled = false
cleaner_enabled = false
# Interval for running the message archiver and cleaner.
-interval = "1 m"
+interval = "35s"
[data_maintenance.state]
# Enables or disables the archiver process for state log.
@@ -271,6 +271,20 @@ cert_file = "core/certs/iggy_cert.pem"
# Path to the QUIC TLS key file.
key_file = "core/certs/iggy_key.pem"
+# Configuration for the QUIC socket
+[quic.socket]
+# Whether to override the OS-default socket parameters
+override_defaults = false
+
+# SO_RCVBUF: maximum size of the receive buffer, can be clamped by the OS
+recv_buffer_size = "64 KB"
+
+# SO_SNDBUF: maximum size of the send buffer, can be clamped by the OS
+send_buffer_size = "64 KB"
+
+# SO_KEEPALIVE: whether to regularly send a keepalive packet maintaining the connection
+keepalive = false
+
# Message cleaner configuration.
[message_cleaner]
# Enables or disables the background process for deleting expired messages.
@@ -577,3 +591,23 @@ address = "127.0.0.1:8090"
id = 2
name = "iggy-node-2"
address = "127.0.0.1:8091"
+
+# Sharding configuration
+[system.sharding]
+# CPU allocation - controls the number of shards and their CPU affinity.
+# Possible values:
+# - "all": Use all available CPU cores (default)
+# - numeric value (e.g. 4): Use 4 shards (4 threads pinned to cores 0, 1, 2, 3)
+# - range (e.g. "5..8"): Use 3 shards with affinity to cores 5, 6, 7
+cpu_allocation = "all"
+
+[websocket]
+
+enabled = true
+address = "0.0.0.0:8092"
+
+[websocket.tls]
+enabled = false
+self_signed = true
+cert_file = "core/certs/iggy_cert.pem"
+key_file = "core/certs/iggy_key.pem"
diff --git a/core/integration/Cargo.toml b/core/integration/Cargo.toml
index f3e681c84..bb3d00e7f 100644
--- a/core/integration/Cargo.toml
+++ b/core/integration/Cargo.toml
@@ -32,6 +32,7 @@ assert_cmd = "2.0.17"
async-trait = { workspace = true }
bytes = { workspace = true }
chrono = { workspace = true }
+compio = { workspace = true }
ctor = "0.6.0"
derive_more = { workspace = true }
env_logger = { workspace = true }
diff --git a/core/integration/src/lib.rs b/core/integration/src/lib.rs
index 996961e6d..269698b2b 100644
--- a/core/integration/src/lib.rs
+++ b/core/integration/src/lib.rs
@@ -24,6 +24,9 @@ pub mod http_client;
pub mod quic_client;
#[allow(deprecated)]
pub mod tcp_client;
+#[allow(deprecated)]
+pub mod websocket_client;
+
#[allow(deprecated)]
pub mod test_connectors_runtime;
#[allow(deprecated)]
diff --git a/core/integration/src/quic_client.rs b/core/integration/src/quic_client.rs
index 667a8965c..c9fa64990 100644
--- a/core/integration/src/quic_client.rs
+++ b/core/integration/src/quic_client.rs
@@ -33,6 +33,8 @@ impl ClientFactory for QuicClientFactory {
async fn create_client(&self) -> ClientWrapper {
let config = QuicClientConfig {
server_address: self.server_addr.clone(),
+ // TODO: Need to increase this in order to not timeout during tests
+ max_idle_timeout: 2_000_000,
..QuicClientConfig::default()
};
let client = QuicClient::create(Arc::new(config)).unwrap();
diff --git a/core/integration/src/test_mcp_server.rs b/core/integration/src/test_mcp_server.rs
index 5fcddbf8f..9d729eaa8 100644
--- a/core/integration/src/test_mcp_server.rs
+++ b/core/integration/src/test_mcp_server.rs
@@ -24,17 +24,18 @@ use rmcp::{
service::RunningService,
transport::StreamableHttpClientTransport,
};
-use std::fs::OpenOptions;
-use std::io::Write;
+use std::fs::{self, File};
use std::net::{Ipv4Addr, SocketAddr};
use std::path::PathBuf;
-use std::process::{Child, Command};
+use std::process::{Child, Command, Stdio};
+use std::thread::panicking;
use std::time::Duration;
use std::{collections::HashMap, net::TcpListener};
use tokio::time::sleep;
pub const CONSUMER_NAME: &str = "mcp";
const MCP_PATH: &str = "/mcp";
+const TEST_VERBOSITY_ENV_VAR: &str = "IGGY_TEST_VERBOSE";
pub type McpClient = RunningService<RoleClient, InitializeRequestParam>;
@@ -101,7 +102,23 @@ impl TestMcpServer {
Command::cargo_bin("iggy-mcp").unwrap()
};
command.envs(self.envs.clone());
- let child = command.spawn().expect("Failed to start MCP server process");
+
+ // By default, MCP server logs are redirected to files,
+ // and dumped to stderr when test fails. With IGGY_TEST_VERBOSE=1
+ // logs are dumped to stdout during test execution.
+ if std::env::var(TEST_VERBOSITY_ENV_VAR).is_ok()
+ || self.envs.contains_key(TEST_VERBOSITY_ENV_VAR)
+ {
+ command.stdout(Stdio::inherit());
+ command.stderr(Stdio::inherit());
+ } else {
+ command.stdout(self.get_stdout_file());
+ self.stdout_file_path = Some(fs::canonicalize(self.get_stdout_file_path()).unwrap());
+ command.stderr(self.get_stderr_file());
+ self.stderr_file_path = Some(fs::canonicalize(self.get_stderr_file_path()).unwrap());
+ }
+
+ let child = command.spawn().unwrap();
self.child_handle = Some(child);
}
@@ -118,29 +135,7 @@ impl TestMcpServer {
#[cfg(not(unix))]
child_handle.kill().unwrap();
- if let Ok(output) = child_handle.wait_with_output() {
- let stderr = String::from_utf8_lossy(&output.stderr);
- let stdout = String::from_utf8_lossy(&output.stdout);
- if let Some(stderr_file_path) = &self.stderr_file_path {
- OpenOptions::new()
- .append(true)
- .create(true)
- .open(stderr_file_path)
- .unwrap()
- .write_all(stderr.as_bytes())
- .unwrap();
- }
-
- if let Some(stdout_file_path) = &self.stdout_file_path {
- OpenOptions::new()
- .append(true)
- .create(true)
- .open(stdout_file_path)
- .unwrap()
- .write_all(stdout.as_bytes())
- .unwrap();
- }
- }
+ let _ = child_handle.wait();
}
}
@@ -224,10 +219,55 @@ impl TestMcpServer {
panic!("Failed to find a free port after {max_retries} retries");
}
+
+ fn get_stdout_file_path(&self) -> String {
+ format!("/tmp/iggy-mcp-{}.stdout", self.server_address.port())
+ }
+
+ fn get_stderr_file_path(&self) -> String {
+ format!("/tmp/iggy-mcp-{}.stderr", self.server_address.port())
+ }
+
+ fn get_stdout_file(&self) -> Stdio {
+ Stdio::from(File::create(self.get_stdout_file_path()).unwrap())
+ }
+
+ fn get_stderr_file(&self) -> Stdio {
+ Stdio::from(File::create(self.get_stderr_file_path()).unwrap())
+ }
+
+ fn read_file_to_string(path: &str) -> String {
+ fs::read_to_string(path).unwrap_or_else(|_| format!("Failed to read file: {}", path))
+ }
}
impl Drop for TestMcpServer {
fn drop(&mut self) {
self.stop();
+
+ if panicking() {
+ if let Some(stdout_file_path) = &self.stdout_file_path {
+ eprintln!(
+ "Iggy MCP server stdout:\n{}",
+ Self::read_file_to_string(stdout_file_path.to_str().unwrap())
+ );
+ }
+
+ if let Some(stderr_file_path) = &self.stderr_file_path {
+ eprintln!(
+ "Iggy MCP server stderr:\n{}",
+ Self::read_file_to_string(stderr_file_path.to_str().unwrap())
+ );
+ }
+ }
+
+ // Clean up log files
+ if let Some(stdout_file_path) = &self.stdout_file_path {
+ fs::remove_file(stdout_file_path).unwrap();
+ }
+
+ if let Some(stderr_file_path) = &self.stderr_file_path {
+ fs::remove_file(stderr_file_path).unwrap();
+ }
}
}
diff --git a/core/integration/src/test_server.rs b/core/integration/src/test_server.rs
index 372a44139..160094d26 100644
--- a/core/integration/src/test_server.rs
+++ b/core/integration/src/test_server.rs
@@ -16,6 +16,15 @@
* under the License.
*/
+use assert_cmd::prelude::CommandCargoExt;
+use async_trait::async_trait;
+use derive_more::Display;
+use futures::executor::block_on;
+use iggy::prelude::UserStatus::Active;
+use iggy::prelude::*;
+use iggy_common::TransportProtocol;
+use rand::Rng;
+use server::configs::config_provider::{ConfigProvider, FileConfigProvider};
use std::collections::HashMap;
use std::fs;
use std::fs::{File, OpenOptions};
@@ -23,17 +32,8 @@ use std::io::Write;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
-use std::thread::{panicking, sleep};
+use std::thread::{available_parallelism, panicking, sleep};
use std::time::Duration;
-
-use assert_cmd::prelude::CommandCargoExt;
-use async_trait::async_trait;
-use derive_more::Display;
-use futures::executor::block_on;
-use iggy::prelude::UserStatus::Active;
-use iggy::prelude::*;
-use iggy_common::{ConfigProvider, TransportProtocol};
-use server::configs::server::ServerConfig;
use uuid::Uuid;
pub const SYSTEM_PATH_ENV_VAR: &str = "IGGY_SYSTEM_PATH";
@@ -59,7 +59,6 @@ pub trait ClientFactory: Sync + Send {
fn transport(&self) -> TransportProtocol;
fn server_addr(&self) -> String;
}
-
#[derive(Display, Debug)]
enum ServerProtocolAddr {
#[display("RAW_TCP:{_0}")]
@@ -70,6 +69,9 @@ enum ServerProtocolAddr {
#[display("QUIC_UDP:{_0}")]
QuicUdp(SocketAddr),
+
+ #[display("WEBSOCKET:{_0}")]
+ WebSocket(SocketAddr),
}
#[derive(Debug)]
@@ -98,6 +100,28 @@ impl TestServer {
}
}
+ // Randomly select 4 CPU cores to reduce interference between parallel tests
+ let cpu_allocation = match available_parallelism() {
+ Ok(parallelism) => {
+ let available_cpus = parallelism.get();
+ if available_cpus >= 4 {
+ let mut rng = rand::thread_rng();
+ let max_start = available_cpus - 4;
+ let start = rng.gen_range(0..=max_start);
+ let end = start + 4;
+ format!("{}..{}", start, end)
+ } else {
+ "all".to_string()
+ }
+ }
+ Err(_) => "0..4".to_string(),
+ };
+
+ envs.insert(
+ "IGGY_SYSTEM_SHARDING_CPU_ALLOCATION".to_string(),
+ cpu_allocation,
+ );
+
if ip_kind == IpAddrKind::V6 {
envs.insert(IPV6_ENV_VAR.to_string(), "true".to_string());
}
@@ -277,6 +301,7 @@ impl TestServer {
ServerProtocolAddr::QuicUdp(addr),
ServerProtocolAddr::RawTcp(addr),
ServerProtocolAddr::HttpTcp(addr),
+ ServerProtocolAddr::WebSocket(addr),
]
}
@@ -286,6 +311,7 @@ impl TestServer {
ServerProtocolAddr::QuicUdp(addr),
ServerProtocolAddr::RawTcp(addr),
ServerProtocolAddr::HttpTcp(addr),
+ ServerProtocolAddr::WebSocket(addr),
]
}
@@ -301,6 +327,9 @@ impl TestServer {
ServerProtocolAddr::QuicUdp(addr) => {
("IGGY_QUIC_ADDRESS".to_string(), addr.to_string())
}
+ ServerProtocolAddr::WebSocket(addr) => {
+ ("IGGY_WEBSOCKET_ADDRESS".to_string(), addr.to_string())
+ }
};
self.envs.entry(key.0).or_insert(key.1);
@@ -309,11 +338,13 @@ impl TestServer {
fn wait_until_server_has_bound(&mut self) {
let config_path = format!("{}/runtime/current_config.toml", self.local_data_path);
+ let file_config_provider = FileConfigProvider::new(config_path.clone());
+
let max_attempts = (MAX_PORT_WAIT_DURATION_S * 1000) / SLEEP_INTERVAL_MS;
self.server_addrs.clear();
let config = block_on(async {
- let mut loaded_config: Option<ServerConfig> = None;
+ let mut loaded_config = None;
for _ in 0..max_attempts {
if !Path::new(&config_path).exists() {
@@ -325,11 +356,48 @@ impl TestServer {
sleep(Duration::from_millis(SLEEP_INTERVAL_MS));
continue;
}
- match ServerConfig::file_config_provider(config_path.clone())
- .load_config()
- .await
- {
+ match file_config_provider.load_config().await {
Ok(config) => {
+ // Verify config contains fresh addresses, not stale defaults
+ // Default ports: TCP=8090, HTTP=3000, QUIC=8080, WebSocket=8092
+ let tcp_port: u16 = config
+ .tcp
+ .address
+ .split(':')
+ .nth(1)
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(0);
+ let http_port: u16 = config
+ .http
+ .address
+ .split(':')
+ .nth(1)
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(0);
+ let quic_port: u16 = config
+ .quic
+ .address
+ .split(':')
+ .nth(1)
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(0);
+ let websocket_port: u16 = config
+ .websocket
+ .address
+ .split(':')
+ .nth(1)
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(0);
+
+ if tcp_port == 8090
+ || http_port == 3000
+ || quic_port == 8080
+ || websocket_port == 8092
+ {
+ sleep(Duration::from_millis(SLEEP_INTERVAL_MS));
+ continue;
+ }
+
loaded_config = Some(config);
break;
}
@@ -340,17 +408,41 @@ impl TestServer {
});
if let Some(config) = config {
- self.server_addrs.push(ServerProtocolAddr::QuicUdp(
- config.quic.address.parse().unwrap(),
- ));
+ // Only validate and add enabled protocols
+ if config.quic.enabled {
+ let quic_addr: SocketAddr = config.quic.address.parse().unwrap();
+ if quic_addr.port() == 0 {
+ panic!("Quic address port is 0!");
+ }
+ self.server_addrs
+ .push(ServerProtocolAddr::QuicUdp(quic_addr));
+ }
- self.server_addrs.push(ServerProtocolAddr::RawTcp(
- config.tcp.address.parse().unwrap(),
- ));
+ if config.tcp.enabled {
+ let tcp_addr: SocketAddr = config.tcp.address.parse().unwrap();
+ if tcp_addr.port() == 0 {
+ panic!("Tcp address port is 0!");
+ }
+ self.server_addrs.push(ServerProtocolAddr::RawTcp(tcp_addr));
+ }
+
+ if config.http.enabled {
+ let http_addr: SocketAddr = config.http.address.parse().unwrap();
+ if http_addr.port() == 0 {
+ panic!("Http address port is 0!");
+ }
+ self.server_addrs
+ .push(ServerProtocolAddr::HttpTcp(http_addr));
+ }
- self.server_addrs.push(ServerProtocolAddr::HttpTcp(
- config.http.address.parse().unwrap(),
- ));
+ if config.websocket.enabled {
+ let websocket_addr: SocketAddr = config.websocket.address.parse().unwrap();
+ if websocket_addr.port() == 0 {
+ panic!("WebSocket address port is 0!");
+ }
+ self.server_addrs
+ .push(ServerProtocolAddr::WebSocket(websocket_addr));
+ }
} else {
panic!(
"Failed to load config from file {config_path} in {MAX_PORT_WAIT_DURATION_S} s!"
@@ -429,6 +521,15 @@ impl TestServer {
None
}
}
+
+ pub fn get_websocket_addr(&self) -> Option<String> {
+ for server_protocol_addr in &self.server_addrs {
+ if let ServerProtocolAddr::WebSocket(a) = server_protocol_addr {
+ return Some(a.to_string());
+ }
+ }
+ None
+ }
}
impl Drop for TestServer {
diff --git a/core/integration/src/quic_client.rs b/core/integration/src/websocket_client.rs
similarity index 70%
copy from core/integration/src/quic_client.rs
copy to core/integration/src/websocket_client.rs
index 667a8965c..cc0cc0826 100644
--- a/core/integration/src/quic_client.rs
+++ b/core/integration/src/websocket_client.rs
@@ -18,30 +18,30 @@
use crate::test_server::ClientFactory;
use async_trait::async_trait;
-use iggy::prelude::{Client, ClientWrapper, QuicClientConfig};
-use iggy::quic::quic_client::QuicClient;
+use iggy::prelude::{Client, ClientWrapper, WebSocketClientConfig};
+use iggy::websocket::websocket_client::WebSocketClient;
use iggy_common::TransportProtocol;
use std::sync::Arc;
#[derive(Debug, Clone)]
-pub struct QuicClientFactory {
+pub struct WebSocketClientFactory {
pub server_addr: String,
}
#[async_trait]
-impl ClientFactory for QuicClientFactory {
+impl ClientFactory for WebSocketClientFactory {
async fn create_client(&self) -> ClientWrapper {
- let config = QuicClientConfig {
+ let config = WebSocketClientConfig {
server_address: self.server_addr.clone(),
- ..QuicClientConfig::default()
+ ..WebSocketClientConfig::default()
};
- let client = QuicClient::create(Arc::new(config)).unwrap();
+ let client = WebSocketClient::create(Arc::new(config)).unwrap();
Client::connect(&client).await.unwrap();
- ClientWrapper::Quic(client)
+ ClientWrapper::WebSocket(client)
}
fn transport(&self) -> TransportProtocol {
- TransportProtocol::Quic
+ TransportProtocol::WebSocket
}
fn server_addr(&self) -> String {
@@ -49,5 +49,5 @@ impl ClientFactory for QuicClientFactory {
}
}
-unsafe impl Send for QuicClientFactory {}
-unsafe impl Sync for QuicClientFactory {}
+unsafe impl Send for WebSocketClientFactory {}
+unsafe impl Sync for WebSocketClientFactory {}
diff --git a/core/integration/tests/archiver/disk.rs b/core/integration/tests/archiver/disk.rs
deleted file mode 100644
index f676d23f7..000000000
--- a/core/integration/tests/archiver/disk.rs
+++ /dev/null
@@ -1,129 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use crate::archiver::DiskArchiverSetup;
-use server::streaming::utils::file;
-use server::{archiver::Archiver, server_error::ArchiverError};
-use std::path::Path;
-use tokio::io::{AsyncReadExt, AsyncWriteExt};
-
-#[tokio::test]
-async fn should_init_base_archiver_directory() {
- let setup = DiskArchiverSetup::init().await;
- let archiver = setup.archiver();
- let result = archiver.init().await;
- assert!(result.is_ok());
- let path = Path::new(&setup.archive_path);
- assert!(path.exists());
-}
-
-#[tokio::test]
-async fn should_archive_file_on_disk_by_making_a_copy_of_original_file() {
- let setup = DiskArchiverSetup::init().await;
- let archiver = setup.archiver();
- let content = "hello world";
- let file_to_archive_path = format!("{}/file_to_archive", setup.base_path);
- create_file(&file_to_archive_path, content).await;
- let files_to_archive = vec![file_to_archive_path.as_ref()];
-
- let result = archiver.archive(&files_to_archive, None).await;
- assert!(result.is_ok());
- let archived_file_path = format!("{}/{}", setup.archive_path, file_to_archive_path);
- assert_archived_file(&file_to_archive_path, &archived_file_path, content).await;
-}
-
-#[tokio::test]
-async fn should_archive_file_on_disk_within_additional_base_directory() {
- let setup = DiskArchiverSetup::init().await;
- let archiver = setup.archiver();
- let base_directory = "base";
- let content = "hello world";
- let file_to_archive_path = format!("{}/file_to_archive", setup.base_path);
- create_file(&file_to_archive_path, content).await;
- let files_to_archive = vec![file_to_archive_path.as_ref()];
-
- let result = archiver
- .archive(&files_to_archive, Some(base_directory.to_string()))
- .await;
- assert!(result.is_ok());
- let archived_file_path = format!(
- "{}/{base_directory}/{}",
- setup.archive_path, file_to_archive_path
- );
- assert_archived_file(&file_to_archive_path, &archived_file_path, content).await;
-}
-
-#[tokio::test]
-async fn should_return_true_when_file_is_archived() {
- let setup = DiskArchiverSetup::init().await;
- let archiver = setup.archiver();
- let content = "hello world";
- let file_to_archive_path = format!("{}/file_to_archive", setup.base_path);
- create_file(&file_to_archive_path, content).await;
- let files_to_archive = vec![file_to_archive_path.as_ref()];
- archiver.archive(&files_to_archive, None).await.unwrap();
-
- let is_archived = archiver.is_archived(&file_to_archive_path, None).await;
- assert!(is_archived.is_ok());
- assert!(is_archived.unwrap());
-}
-
-#[tokio::test]
-async fn should_return_false_when_file_is_not_archived() {
- let setup = DiskArchiverSetup::init().await;
- let archiver = setup.archiver();
- let content = "hello world";
- let file_to_archive_path = format!("{}/file_to_archive", setup.base_path);
- create_file(&file_to_archive_path, content).await;
-
- let is_archived = archiver.is_archived(&file_to_archive_path, None).await;
- assert!(is_archived.is_ok());
- assert!(!is_archived.unwrap());
-}
-
-#[tokio::test]
-async fn should_fail_when_file_to_archive_does_not_exist() {
- let setup = DiskArchiverSetup::init().await;
- let archiver = setup.archiver();
- let file_to_archive_path = "invalid_file_to_archive";
- let files_to_archive = vec![file_to_archive_path];
- let result = archiver.archive(&files_to_archive, None).await;
-
- assert!(result.is_err());
- let error = result.err().unwrap();
- assert!(matches!(error, ArchiverError::FileToArchiveNotFound { .. }));
-}
-
-async fn create_file(path: &str, content: &str) {
- let mut file = file::overwrite(path).await.unwrap();
- file.write_all(content.as_bytes()).await.unwrap();
-}
-
-async fn assert_archived_file(file_to_archive_path: &str, archived_file_path: &str, content: &str) {
- assert!(Path::new(&file_to_archive_path).exists());
- assert!(Path::new(&archived_file_path).exists());
- let archived_file = file::open(archived_file_path).await;
- assert!(archived_file.is_ok());
- let mut archived_file = archived_file.unwrap();
- let mut archived_file_content = String::new();
- archived_file
- .read_to_string(&mut archived_file_content)
- .await
- .unwrap();
- assert_eq!(content, archived_file_content);
-}
diff --git a/core/integration/tests/archiver/mod.rs b/core/integration/tests/archiver/mod.rs
deleted file mode 100644
index 2e3ed94cc..000000000
--- a/core/integration/tests/archiver/mod.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use server::archiver::disk::DiskArchiver;
-use server::configs::server::DiskArchiverConfig;
-use tokio::fs::create_dir;
-use uuid::Uuid;
-
-mod disk;
-mod s3;
-
-pub struct DiskArchiverSetup {
- base_path: String,
- archive_path: String,
- archiver: DiskArchiver,
-}
-
-impl DiskArchiverSetup {
- pub async fn init() -> DiskArchiverSetup {
- let base_path = format!("test_local_data_{}", Uuid::now_v7().to_u128_le());
- let archive_path = format!("{base_path}/archive");
- let config = DiskArchiverConfig {
- path: archive_path.clone(),
- };
- let archiver = DiskArchiver::new(config);
- create_dir(&base_path).await.unwrap();
-
- Self {
- base_path,
- archive_path,
- archiver,
- }
- }
-
- pub fn archiver(&self) -> &DiskArchiver {
- &self.archiver
- }
-}
-
-impl Drop for DiskArchiverSetup {
- fn drop(&mut self) {
- std::fs::remove_dir_all(&self.base_path).unwrap();
- }
-}
diff --git a/core/integration/tests/archiver/s3.rs b/core/integration/tests/archiver/s3.rs
deleted file mode 100644
index df2970f1f..000000000
--- a/core/integration/tests/archiver/s3.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use server::archiver::Archiver;
-use server::archiver::s3::S3Archiver;
-use server::configs::server::S3ArchiverConfig;
-
-#[tokio::test]
-async fn should_not_be_initialized_given_invalid_configuration() {
- let config = S3ArchiverConfig {
- key_id: "test".to_owned(),
- key_secret: "secret".to_owned(),
- bucket: "iggy".to_owned(),
- endpoint: Some("https://iggy.s3.com".to_owned()),
- region: None,
- tmp_upload_dir: "tmp".to_owned(),
- };
- let archiver = S3Archiver::new(config);
- assert!(archiver.is_ok());
- let archiver = archiver.unwrap();
- let init = archiver.init().await;
- assert!(init.is_err());
-}
diff --git a/core/integration/tests/cli/client/test_client_get_command.rs b/core/integration/tests/cli/client/test_client_get_command.rs
index d76c09e47..734341758 100644
--- a/core/integration/tests/cli/client/test_client_get_command.rs
+++ b/core/integration/tests/cli/client/test_client_get_command.rs
@@ -67,7 +67,7 @@ impl IggyCmdTestCase for TestClientGetCmd {
"Client ID | {}",
self.get_client_id()
)))
- .stdout(contains("User ID | 1"));
+ .stdout(contains("User ID | 0"));
}
async fn verify_server_state(&self, _client: &dyn Client) {}
diff --git a/core/integration/tests/cli/common/mod.rs b/core/integration/tests/cli/common/mod.rs
index 402ba5803..bebf46f21 100644
--- a/core/integration/tests/cli/common/mod.rs
+++ b/core/integration/tests/cli/common/mod.rs
@@ -128,7 +128,7 @@ impl IggyCmdTest {
.await
.unwrap();
- assert_eq!(identity_info.user_id, 1);
+ assert_eq!(identity_info.user_id, 0);
}
pub(crate) async fn execute_test(&mut self, mut test_case: impl IggyCmdTestCase) {
@@ -145,6 +145,8 @@ impl IggyCmdTest {
let command_args = test_case.get_command();
// Set environment variables for the command
command.envs(command_args.get_env());
+ // Set a fixed terminal width for consistent help output formatting
+ command.env("COLUMNS", "500");
// Set server address for the command - it's randomized for each test
command.args(test_case.protocol(&self.server));
// Set COLUMNS environment variable to 200 to avoid truncation of output
diff --git a/core/integration/tests/cli/consumer_group/test_consumer_group_create_command.rs b/core/integration/tests/cli/consumer_group/test_consumer_group_create_command.rs
index 213831f08..4e02d6143 100644
--- a/core/integration/tests/cli/consumer_group/test_consumer_group_create_command.rs
+++ b/core/integration/tests/cli/consumer_group/test_consumer_group_create_command.rs
@@ -86,10 +86,10 @@ impl TestConsumerGroupCreateCmd {
#[async_trait]
impl IggyCmdTestCase for TestConsumerGroupCreateCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client
- .create_stream(&self.stream_name, self.stream_id.into())
- .await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
+ let stream_details = stream.unwrap();
+ self.stream_id = stream_details.id;
let topic = client
.create_topic(
@@ -98,12 +98,13 @@ impl IggyCmdTestCase for TestConsumerGroupCreateCmd {
1,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await;
assert!(topic.is_ok());
+ let topic_details = topic.unwrap();
+ self.topic_id = topic_details.id;
}
fn get_command(&self) -> IggyCmdCommand {
@@ -189,7 +190,7 @@ pub async fn should_be_successful() {
String::from("main"),
1,
String::from("sync"),
- Some(1),
+ None,
String::from("group1"),
TestStreamId::Numeric,
TestTopicId::Numeric,
@@ -201,7 +202,7 @@ pub async fn should_be_successful() {
String::from("stream"),
3,
String::from("topic"),
- Some(3),
+ None,
String::from("group3"),
TestStreamId::Named,
TestTopicId::Numeric,
@@ -213,7 +214,7 @@ pub async fn should_be_successful() {
String::from("development"),
1,
String::from("probe"),
- Some(7),
+ None,
String::from("group7"),
TestStreamId::Numeric,
TestTopicId::Named,
@@ -225,7 +226,7 @@ pub async fn should_be_successful() {
String::from("production"),
5,
String::from("test"),
- Some(4),
+ None,
String::from("group4"),
TestStreamId::Named,
TestTopicId::Named,
diff --git a/core/integration/tests/cli/consumer_group/test_consumer_group_delete_command.rs b/core/integration/tests/cli/consumer_group/test_consumer_group_delete_command.rs
index a6f950440..9c58038e0 100644
--- a/core/integration/tests/cli/consumer_group/test_consumer_group_delete_command.rs
+++ b/core/integration/tests/cli/consumer_group/test_consumer_group_delete_command.rs
@@ -89,10 +89,10 @@ impl TestConsumerGroupDeleteCmd {
#[async_trait]
impl IggyCmdTestCase for TestConsumerGroupDeleteCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client
- .create_stream(&self.stream_name, Some(self.stream_id))
- .await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
+ let stream_details = stream.unwrap();
+ self.stream_id = stream_details.id;
let topic = client
.create_topic(
@@ -101,22 +101,24 @@ impl IggyCmdTestCase for TestConsumerGroupDeleteCmd {
1,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await;
assert!(topic.is_ok());
+ let topic_details = topic.unwrap();
+ self.topic_id = topic_details.id;
let consumer_group = client
.create_consumer_group(
&self.stream_id.try_into().unwrap(),
&self.topic_id.try_into().unwrap(),
&self.group_name,
- Some(self.group_id),
)
.await;
assert!(consumer_group.is_ok());
+ let consumer_group_details = consumer_group.unwrap();
+ self.group_id = consumer_group_details.id;
}
fn get_command(&self) -> IggyCmdCommand {
diff --git a/core/integration/tests/cli/consumer_group/test_consumer_group_get_command.rs b/core/integration/tests/cli/consumer_group/test_consumer_group_get_command.rs
index 85a58c12f..8db78e700 100644
--- a/core/integration/tests/cli/consumer_group/test_consumer_group_get_command.rs
+++ b/core/integration/tests/cli/consumer_group/test_consumer_group_get_command.rs
@@ -89,34 +89,36 @@ impl TestConsumerGroupGetCmd {
#[async_trait]
impl IggyCmdTestCase for TestConsumerGroupGetCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client
- .create_stream(&self.stream_name, Some(self.stream_id))
- .await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
+ let stream_details = stream.unwrap();
+ self.stream_id = stream_details.id;
let topic = client
.create_topic(
&self.stream_id.try_into().unwrap(),
&self.topic_name,
- 0,
+ 1,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await;
assert!(topic.is_ok());
+ let topic_details = topic.unwrap();
+ self.topic_id = topic_details.id;
let consumer_group = client
.create_consumer_group(
&self.stream_id.try_into().unwrap(),
&self.topic_id.try_into().unwrap(),
&self.group_name,
- self.group_id.into(),
)
.await;
assert!(consumer_group.is_ok());
+ let consumer_group_details = consumer_group.unwrap();
+ self.group_id = consumer_group_details.id;
}
fn get_command(&self) -> IggyCmdCommand {
diff --git a/core/integration/tests/cli/consumer_group/test_consumer_group_list_command.rs b/core/integration/tests/cli/consumer_group/test_consumer_group_list_command.rs
index 88712d147..0267b3789 100644
--- a/core/integration/tests/cli/consumer_group/test_consumer_group_list_command.rs
+++ b/core/integration/tests/cli/consumer_group/test_consumer_group_list_command.rs
@@ -18,7 +18,7 @@
use crate::cli::common::{
CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, OutputFormat, TestHelpCmd,
- TestStreamId, TestTopicId, USAGE_PREFIX,
+ USAGE_PREFIX,
};
use assert_cmd::assert::Assert;
use async_trait::async_trait;
@@ -29,53 +29,50 @@ use predicates::str::{contains, starts_with};
use serial_test::parallel;
struct TestConsumerGroupListCmd {
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
- consumer_group_id: u32,
consumer_group_name: String,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
output: OutputFormat,
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
+ actual_consumer_group_id: Option<u32>,
}
impl TestConsumerGroupListCmd {
- #[allow(clippy::too_many_arguments)]
fn new(
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
- consumer_group_id: u32,
consumer_group_name: String,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
output: OutputFormat,
) -> Self {
Self {
- stream_id,
stream_name,
- topic_id,
topic_name,
- consumer_group_id,
consumer_group_name,
- using_stream_id,
- using_topic_id,
output,
+ actual_stream_id: None,
+ actual_topic_id: None,
+ actual_consumer_group_id: None,
}
}
fn to_args(&self) -> Vec<String> {
- let mut command = match self.using_stream_id {
- TestStreamId::Numeric => vec![format!("{}", self.stream_id)],
- TestStreamId::Named => vec![self.stream_name.clone()],
- };
+ let mut command = vec![];
- command.push(match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
- });
+ // Use actual stream ID if available, otherwise use stream name as fallback
+ if let Some(stream_id) = self.actual_stream_id {
+ command.push(format!("{}", stream_id));
+ } else {
+ command.push(self.stream_name.clone());
+ }
+
+ // Use actual topic ID if available, otherwise use topic name as fallback
+ if let Some(topic_id) = self.actual_topic_id {
+ command.push(format!("{}", topic_id));
+ } else {
+ command.push(self.topic_name.clone());
+ }
command.extend(self.output.to_args().into_iter().map(String::from));
@@ -86,34 +83,38 @@ impl TestConsumerGroupListCmd {
#[async_trait]
impl IggyCmdTestCase for TestConsumerGroupListCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
+ // Create stream and capture its actual ID
let stream = client
- .create_stream(&self.stream_name, self.stream_id.into())
- .await;
- assert!(stream.is_ok());
+ .create_stream(&self.stream_name)
+ .await
+ .expect("Failed to create stream");
+ self.actual_stream_id = Some(stream.id);
+ // Create topic and capture its actual ID
let topic = client
.create_topic(
- &self.stream_id.try_into().unwrap(),
+ &stream.id.try_into().unwrap(),
&self.topic_name,
1,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
- .await;
- assert!(topic.is_ok());
+ .await
+ .expect("Failed to create topic");
+ self.actual_topic_id = Some(topic.id);
+ // Create consumer group and capture its actual ID
let consumer_group = client
.create_consumer_group(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &stream.id.try_into().unwrap(),
+ &topic.id.try_into().unwrap(),
&self.consumer_group_name,
- self.consumer_group_id.into(),
)
- .await;
- assert!(consumer_group.is_ok());
+ .await
+ .expect("Failed to create consumer group");
+ self.actual_consumer_group_id = Some(consumer_group.id);
}
fn get_command(&self) -> IggyCmdCommand {
@@ -125,14 +126,16 @@ impl IggyCmdTestCase for TestConsumerGroupListCmd {
}
fn verify_command(&self, command_state: Assert) {
- let stream_id = match self.using_stream_id {
- TestStreamId::Numeric => format!("{}", self.stream_id),
- TestStreamId::Named => self.stream_name.clone(),
+ let stream_id = if let Some(stream_id) = self.actual_stream_id {
+ format!("{}", stream_id)
+ } else {
+ self.stream_name.clone()
};
- let topic_id = match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
+ let topic_id = if let Some(topic_id) = self.actual_topic_id {
+ format!("{}", topic_id)
+ } else {
+ self.topic_name.clone()
};
let start_message = format!(
@@ -147,27 +150,32 @@ impl IggyCmdTestCase for TestConsumerGroupListCmd {
}
async fn verify_server_state(&self, client: &dyn Client) {
- let consumer_group = client
- .delete_consumer_group(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
- &self.consumer_group_id.try_into().unwrap(),
- )
- .await;
- assert!(consumer_group.is_ok());
+ // Use the actual IDs that were generated during resource creation
+ if let Some(consumer_group_id) = self.actual_consumer_group_id {
+ let consumer_group = client
+ .delete_consumer_group(
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
+ &consumer_group_id.try_into().unwrap(),
+ )
+ .await;
+ assert!(consumer_group.is_ok());
+ }
- let topic = client
- .delete_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
- )
- .await;
- assert!(topic.is_ok());
+ if let Some(topic_id) = self.actual_topic_id {
+ let topic = client
+ .delete_topic(
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &topic_id.try_into().unwrap(),
+ )
+ .await;
+ assert!(topic.is_ok());
+ }
- let stream = client
- .delete_stream(&self.stream_id.try_into().unwrap())
- .await;
- assert!(stream.is_ok());
+ if let Some(stream_id) = self.actual_stream_id {
+ let stream = client.delete_stream(&stream_id.try_into().unwrap()).await;
+ assert!(stream.is_ok());
+ }
}
}
@@ -177,72 +185,18 @@ pub async fn should_be_successful() {
let mut iggy_cmd_test = IggyCmdTest::default();
let test_parameters = vec![
- (
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- OutputFormat::Default,
- ),
- (
- TestStreamId::Named,
- TestTopicId::Numeric,
- OutputFormat::Default,
- ),
- (
- TestStreamId::Numeric,
- TestTopicId::Named,
- OutputFormat::Default,
- ),
- (
- TestStreamId::Named,
- TestTopicId::Named,
- OutputFormat::Default,
- ),
- (
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- OutputFormat::List,
- ),
- (
- TestStreamId::Named,
- TestTopicId::Numeric,
- OutputFormat::List,
- ),
- (
- TestStreamId::Numeric,
- TestTopicId::Named,
- OutputFormat::List,
- ),
- (TestStreamId::Named, TestTopicId::Named, OutputFormat::List),
- (
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- OutputFormat::Table,
- ),
- (
- TestStreamId::Named,
- TestTopicId::Numeric,
- OutputFormat::Table,
- ),
- (
- TestStreamId::Numeric,
- TestTopicId::Named,
- OutputFormat::Table,
- ),
- (TestStreamId::Named, TestTopicId::Named, OutputFormat::Table),
+ OutputFormat::Default,
+ OutputFormat::List,
+ OutputFormat::Table,
];
iggy_cmd_test.setup().await;
- for (using_stream_id, using_topic_id, output_format) in test_parameters {
+ for output_format in test_parameters {
iggy_cmd_test
.execute_test(TestConsumerGroupListCmd::new(
- 1,
String::from("stream"),
- 2,
String::from("topic"),
- 3,
String::from("consumer-group"),
- using_stream_id,
- using_topic_id,
output_format,
))
.await;
diff --git a/core/integration/tests/cli/consumer_offset/test_consumer_offset_get_command.rs b/core/integration/tests/cli/consumer_offset/test_consumer_offset_get_command.rs
index af0861cf4..537787a69 100644
--- a/core/integration/tests/cli/consumer_offset/test_consumer_offset_get_command.rs
+++ b/core/integration/tests/cli/consumer_offset/test_consumer_offset_get_command.rs
@@ -18,7 +18,7 @@
use crate::cli::common::{
CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestConsumerId, TestHelpCmd,
- TestStreamId, TestTopicId, USAGE_PREFIX,
+ USAGE_PREFIX,
};
use assert_cmd::assert::Assert;
use async_trait::async_trait;
@@ -30,63 +30,59 @@ use std::str::FromStr;
struct TestConsumerOffsetGetCmd {
consumer_id: u32,
consumer_name: String,
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partition_id: u32,
using_consumer_id: TestConsumerId,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
messages_count: u32,
stored_offset: u64,
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
}
impl TestConsumerOffsetGetCmd {
- #[allow(clippy::too_many_arguments)]
fn new(
consumer_id: u32,
consumer_name: String,
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partition_id: u32,
using_consumer_id: TestConsumerId,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
) -> Self {
Self {
consumer_id,
consumer_name,
- stream_id,
stream_name,
- topic_id,
topic_name,
partition_id,
using_consumer_id,
- using_stream_id,
- using_topic_id,
messages_count: 100,
stored_offset: 66,
+ actual_stream_id: None,
+ actual_topic_id: None,
}
}
fn to_args(&self) -> Vec<String> {
let mut command = match self.using_consumer_id {
- TestStreamId::Numeric => vec![format!("{}", self.consumer_id)],
- TestStreamId::Named => vec![self.consumer_name.clone()],
+ TestConsumerId::Numeric => vec![format!("{}", self.consumer_id)],
+ TestConsumerId::Named => vec![self.consumer_name.clone()],
};
- command.push(match self.using_stream_id {
- TestTopicId::Numeric => format!("{}", self.stream_id),
- TestTopicId::Named => self.stream_name.clone(),
- });
+ // Use actual stream ID if available, otherwise use stream name as fallback
+ if let Some(stream_id) = self.actual_stream_id {
+ command.push(format!("{}", stream_id));
+ } else {
+ command.push(self.stream_name.clone());
+ }
- command.push(match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
- });
+ // Use actual topic ID if available, otherwise use topic name as fallback
+ if let Some(topic_id) = self.actual_topic_id {
+ command.push(format!("{}", topic_id));
+ } else {
+ command.push(self.topic_name.clone());
+ }
command.push(format!("{}", self.partition_id));
@@ -97,24 +93,27 @@ impl TestConsumerOffsetGetCmd {
#[async_trait]
impl IggyCmdTestCase for TestConsumerOffsetGetCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
+ // Create stream and capture its actual ID
let stream = client
- .create_stream(&self.stream_name, self.stream_id.into())
- .await;
- assert!(stream.is_ok());
+ .create_stream(&self.stream_name)
+ .await
+ .expect("Failed to create stream");
+ self.actual_stream_id = Some(stream.id);
+ // Create topic and capture its actual ID
let topic = client
.create_topic(
- &self.stream_id.try_into().unwrap(),
+ &stream.id.try_into().unwrap(),
&self.topic_name,
1,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
- .await;
- assert!(topic.is_ok());
+ .await
+ .expect("Failed to create topic");
+ self.actual_topic_id = Some(topic.id);
let mut messages = (1..=self.messages_count)
.filter_map(|id| IggyMessage::from_str(format!("Test message {id}").as_str()).ok())
@@ -122,8 +121,8 @@ impl IggyCmdTestCase for TestConsumerOffsetGetCmd {
let send_status = client
.send_messages(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &stream.id.try_into().unwrap(),
+ &topic.id.try_into().unwrap(),
&Partitioning::partition_id(self.partition_id),
&mut messages,
)
@@ -140,8 +139,8 @@ impl IggyCmdTestCase for TestConsumerOffsetGetCmd {
}
.unwrap(),
},
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &stream.id.try_into().unwrap(),
+ &topic.id.try_into().unwrap(),
Some(self.partition_id),
self.stored_offset,
)
@@ -163,14 +162,16 @@ impl IggyCmdTestCase for TestConsumerOffsetGetCmd {
TestConsumerId::Named => self.consumer_name.clone(),
};
- let stream_id = match self.using_stream_id {
- TestStreamId::Numeric => format!("{}", self.stream_id),
- TestStreamId::Named => self.stream_name.clone(),
+ let stream_id = if let Some(stream_id) = self.actual_stream_id {
+ format!("{}", stream_id)
+ } else {
+ self.stream_name.clone()
};
- let topic_id = match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
+ let topic_id = if let Some(topic_id) = self.actual_topic_id {
+ format!("{}", topic_id)
+ } else {
+ self.topic_name.clone()
};
let message = format!(
@@ -189,18 +190,20 @@ impl IggyCmdTestCase for TestConsumerOffsetGetCmd {
}
async fn verify_server_state(&self, client: &dyn Client) {
- let topic = client
- .delete_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
- )
- .await;
- assert!(topic.is_ok());
+ if let Some(topic_id) = self.actual_topic_id {
+ let topic = client
+ .delete_topic(
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &topic_id.try_into().unwrap(),
+ )
+ .await;
+ assert!(topic.is_ok());
+ }
- let stream = client
- .delete_stream(&self.stream_id.try_into().unwrap())
- .await;
- assert!(stream.is_ok());
+ if let Some(stream_id) = self.actual_stream_id {
+ let stream = client.delete_stream(&stream_id.try_into().unwrap()).await;
+ assert!(stream.is_ok());
+ }
}
}
@@ -209,68 +212,18 @@ impl IggyCmdTestCase for TestConsumerOffsetGetCmd {
pub async fn should_be_successful() {
let mut iggy_cmd_test = IggyCmdTest::default();
- let test_parameters = vec![
- (
- TestConsumerId::Numeric,
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- ),
- (
- TestConsumerId::Named,
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- ),
- (
- TestConsumerId::Numeric,
- TestStreamId::Named,
- TestTopicId::Numeric,
- ),
- (
- TestConsumerId::Numeric,
- TestStreamId::Numeric,
- TestTopicId::Named,
- ),
- (
- TestConsumerId::Named,
- TestStreamId::Named,
- TestTopicId::Numeric,
- ),
- (
- TestConsumerId::Named,
- TestStreamId::Numeric,
- TestTopicId::Named,
- ),
- (
- TestConsumerId::Numeric,
- TestStreamId::Named,
- TestTopicId::Named,
- ),
- (
- TestConsumerId::Named,
- TestStreamId::Named,
- TestTopicId::Named,
- ),
- (
- TestConsumerId::Numeric,
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- ),
- ];
+ let test_parameters = vec![TestConsumerId::Numeric, TestConsumerId::Named];
iggy_cmd_test.setup().await;
- for (using_stream_id, using_topic_id, using_consumer_id) in test_parameters {
+ for using_consumer_id in test_parameters {
iggy_cmd_test
.execute_test(TestConsumerOffsetGetCmd::new(
1,
String::from("consumer"),
- 2,
String::from("stream"),
- 3,
String::from("topic"),
- 1,
+ 0,
using_consumer_id,
- using_stream_id,
- using_topic_id,
))
.await;
}
diff --git a/core/integration/tests/cli/consumer_offset/test_consumer_offset_set_command.rs b/core/integration/tests/cli/consumer_offset/test_consumer_offset_set_command.rs
index f1a107a26..4f28bae43 100644
--- a/core/integration/tests/cli/consumer_offset/test_consumer_offset_set_command.rs
+++ b/core/integration/tests/cli/consumer_offset/test_consumer_offset_set_command.rs
@@ -97,10 +97,10 @@ impl TestConsumerOffsetSetCmd {
#[async_trait]
impl IggyCmdTestCase for TestConsumerOffsetSetCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client
- .create_stream(&self.stream_name, Some(self.stream_id))
- .await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
+ let stream_details = stream.unwrap();
+ self.stream_id = stream_details.id;
let topic = client
.create_topic(
@@ -109,12 +109,13 @@ impl IggyCmdTestCase for TestConsumerOffsetSetCmd {
1,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await;
assert!(topic.is_ok());
+ let topic_details = topic.unwrap();
+ self.topic_id = topic_details.id;
let mut messages = (1..=self.stored_offset + 1)
.filter_map(|id| IggyMessage::from_str(format!("Test message {id}").as_str()).ok())
@@ -274,7 +275,7 @@ pub async fn should_be_successful() {
String::from("stream"),
3,
String::from("topic"),
- 1,
+ 0,
using_consumer_id,
using_stream_id,
using_topic_id,
diff --git a/core/integration/tests/cli/context/test_context_applied.rs b/core/integration/tests/cli/context/test_context_applied.rs
index 4e96a69c5..0bbbc1dd5 100644
--- a/core/integration/tests/cli/context/test_context_applied.rs
+++ b/core/integration/tests/cli/context/test_context_applied.rs
@@ -16,8 +16,8 @@
* under the License.
*/
-use std::collections::HashMap;
-
+use super::common::TestIggyContext;
+use crate::cli::common::{IggyCmdCommand, IggyCmdTest, IggyCmdTestCase};
use assert_cmd::assert::Assert;
use async_trait::async_trait;
use iggy::prelude::ArgsOptional;
@@ -26,10 +26,7 @@ use iggy_binary_protocol::cli::binary_context::common::ContextConfig;
use integration::test_server::TestServer;
use predicates::str::{contains, starts_with};
use serial_test::parallel;
-
-use crate::cli::common::{IggyCmdCommand, IggyCmdTest, IggyCmdTestCase};
-
-use super::common::TestIggyContext;
+use std::collections::HashMap;
struct TestContextApplied {
set_transport_context: Option<String>,
diff --git a/core/integration/tests/cli/general/test_help_command.rs b/core/integration/tests/cli/general/test_help_command.rs
index 8481bc456..9ae5fcb95 100644
--- a/core/integration/tests/cli/general/test_help_command.rs
+++ b/core/integration/tests/cli/general/test_help_command.rs
@@ -54,7 +54,7 @@ Commands:
Options:
--transport <TRANSPORT>
- The transport to use. Valid values are `quic`, `http` and `tcp`
+ The transport to use. Valid values are `quic`, `http`, `tcp` and `ws`
{CLAP_INDENT}
[default: tcp]
@@ -174,6 +174,21 @@ Options:
--quic-validate-certificate
Flag to enable certificate validation for QUIC
+ --websocket-server-address <WEBSOCKET_SERVER_ADDRESS>
+ The optional server address for the WebSocket transport
+{CLAP_INDENT}
+ [default: 127.0.0.1:8092]
+
+ --websocket-reconnection-max-retries <WEBSOCKET_RECONNECTION_MAX_RETRIES>
+ The optional number of max reconnect retries for the WebSocket transport
+{CLAP_INDENT}
+ [default: 3]
+
+ --websocket-reconnection-interval <WEBSOCKET_RECONNECTION_INTERVAL>
+ The optional reconnect interval for the WebSocket transport
+{CLAP_INDENT}
+ [default: "1s"]
+
-q, --quiet
Quiet mode (disabled stdout printing)
diff --git a/core/integration/tests/cli/message/test_message_flush_command.rs b/core/integration/tests/cli/message/test_message_flush_command.rs
index ce0c888cf..83541b8cd 100644
--- a/core/integration/tests/cli/message/test_message_flush_command.rs
+++ b/core/integration/tests/cli/message/test_message_flush_command.rs
@@ -17,8 +17,7 @@
*/
use crate::cli::common::{
- CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, TestStreamId,
- TestTopicId, USAGE_PREFIX,
+ CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, USAGE_PREFIX,
};
use assert_cmd::assert::Assert;
use async_trait::async_trait;
@@ -31,55 +30,51 @@ use serial_test::parallel;
use std::str::FromStr;
struct TestMessageFetchCmd {
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
partition_id: u32,
fsync: bool,
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
}
impl TestMessageFetchCmd {
- #[allow(clippy::too_many_arguments)]
fn new(
- stream_id: u32,
stream_name: &str,
- topic_id: u32,
topic_name: &str,
partitions_count: u32,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
partition_id: u32,
fsync: bool,
) -> Self {
Self {
- stream_id,
stream_name: stream_name.to_string(),
- topic_id,
topic_name: topic_name.to_string(),
partitions_count,
- using_stream_id,
- using_topic_id,
partition_id,
fsync,
+ actual_stream_id: None,
+ actual_topic_id: None,
}
}
fn to_args(&self) -> Vec<String> {
let mut command = Vec::new();
- command.extend(match self.using_stream_id {
- TestStreamId::Numeric => vec![format!("{}", self.stream_id)],
- TestStreamId::Named => vec![self.stream_name.clone()],
- });
+ // Use actual stream ID if available, otherwise use stream name as fallback
+ if let Some(stream_id) = self.actual_stream_id {
+ command.push(format!("{}", stream_id));
+ } else {
+ command.push(self.stream_name.clone());
+ }
- command.push(match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
- });
+ // Use actual topic ID if available, otherwise use topic name as fallback
+ if let Some(topic_id) = self.actual_topic_id {
+ command.push(format!("{}", topic_id));
+ } else {
+ command.push(self.topic_name.clone());
+ }
command.push(format!("{}", self.partition_id));
@@ -94,26 +89,27 @@ impl TestMessageFetchCmd {
#[async_trait]
impl IggyCmdTestCase for TestMessageFetchCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client.create_stream(&self.stream_name, None).await;
- assert!(stream.is_ok());
-
- let stream_id = Identifier::from_str(&self.stream_name);
- assert!(stream_id.is_ok());
- let stream_id = stream_id.unwrap();
-
+ // Create stream and capture its actual ID
+ let stream = client
+ .create_stream(&self.stream_name)
+ .await
+ .expect("Failed to create stream");
+ self.actual_stream_id = Some(stream.id);
+
+ // Create topic and capture its actual ID
let topic = client
.create_topic(
- &stream_id,
+ &stream.id.try_into().unwrap(),
&self.topic_name,
self.partitions_count,
Default::default(),
None,
- None,
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
- .await;
- assert!(topic.is_ok());
+ .await
+ .expect("Failed to create topic");
+ self.actual_topic_id = Some(topic.id);
}
fn get_command(&self) -> IggyCmdCommand {
@@ -125,14 +121,16 @@ impl IggyCmdTestCase for TestMessageFetchCmd {
}
fn verify_command(&self, command_state: Assert) {
- let stream_id = match self.using_stream_id {
- TestStreamId::Numeric => format!("{}", self.stream_id),
- TestStreamId::Named => self.stream_name.clone(),
+ let stream_id = if let Some(stream_id) = self.actual_stream_id {
+ format!("{}", stream_id)
+ } else {
+ self.stream_name.clone()
};
- let topic_id = match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
+ let topic_id = if let Some(topic_id) = self.actual_topic_id {
+ format!("{}", topic_id)
+ } else {
+ self.topic_name.clone()
};
let identification_part = format!(
@@ -153,18 +151,19 @@ impl IggyCmdTestCase for TestMessageFetchCmd {
}
async fn verify_server_state(&self, client: &dyn Client) {
- let topic_id = Identifier::from_str(&self.topic_name);
- assert!(topic_id.is_ok());
- let topic_id = topic_id.unwrap();
- let stream_id = Identifier::from_str(&self.stream_name);
- assert!(stream_id.is_ok());
- let stream_id = stream_id.unwrap();
-
- let topic = client.delete_topic(&stream_id, &topic_id).await;
- assert!(topic.is_ok());
-
- let stream = client.delete_stream(&stream_id).await;
- assert!(stream.is_ok());
+ if let Some(topic_id) = self.actual_topic_id {
+ let stream_id = Identifier::from_str(&self.stream_name);
+ assert!(stream_id.is_ok());
+ let stream_id = stream_id.unwrap();
+
+ let topic = client
+ .delete_topic(&stream_id, &topic_id.try_into().unwrap())
+ .await;
+ assert!(topic.is_ok());
+
+ let stream = client.delete_stream(&stream_id).await;
+ assert!(stream.is_ok());
+ }
}
}
@@ -173,31 +172,12 @@ impl IggyCmdTestCase for TestMessageFetchCmd {
pub async fn should_be_successful() {
let mut iggy_cmd_test = IggyCmdTest::default();
- let test_parameters: Vec<(TestStreamId, TestTopicId, bool)> = vec![
- (TestStreamId::Numeric, TestTopicId::Numeric, false),
- (TestStreamId::Numeric, TestTopicId::Named, false),
- (TestStreamId::Named, TestTopicId::Numeric, false),
- (TestStreamId::Named, TestTopicId::Named, false),
- (TestStreamId::Numeric, TestTopicId::Numeric, true),
- (TestStreamId::Numeric, TestTopicId::Named, true),
- (TestStreamId::Named, TestTopicId::Numeric, true),
- (TestStreamId::Named, TestTopicId::Named, true),
- ];
+ let test_parameters = vec![false, true]; // Test with and without fsync
iggy_cmd_test.setup().await;
- for (using_stream_id, using_topic_id, fsync) in test_parameters {
+ for fsync in test_parameters {
iggy_cmd_test
- .execute_test(TestMessageFetchCmd::new(
- 1,
- "stream",
- 1,
- "topic",
- 1,
- using_stream_id,
- using_topic_id,
- 1,
- fsync,
- ))
+ .execute_test(TestMessageFetchCmd::new("stream", "topic", 1, 0, fsync))
.await;
}
}
diff --git a/core/integration/tests/cli/message/test_message_poll_command.rs b/core/integration/tests/cli/message/test_message_poll_command.rs
index aea549642..2a4c2b4f3 100644
--- a/core/integration/tests/cli/message/test_message_poll_command.rs
+++ b/core/integration/tests/cli/message/test_message_poll_command.rs
@@ -17,8 +17,7 @@
*/
use crate::cli::common::{
- CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, TestStreamId,
- TestTopicId, USAGE_PREFIX,
+ CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, USAGE_PREFIX,
};
use assert_cmd::assert::Assert;
use async_trait::async_trait;
@@ -30,55 +29,47 @@ use std::collections::HashMap;
use std::str::FromStr;
struct TestMessagePollCmd {
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
messages: Vec<String>,
partition_id: u32,
message_count: usize,
strategy: PollingStrategy,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
show_headers: bool,
headers: (HeaderKey, HeaderValue),
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
}
impl TestMessagePollCmd {
#[allow(clippy::too_many_arguments)]
fn new(
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
messages: &[String],
partition_id: u32,
message_count: usize,
strategy: PollingStrategy,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
show_headers: bool,
headers: (HeaderKey, HeaderValue),
) -> Self {
- assert!(partition_id <= partitions_count);
- assert!(partition_id > 0);
+ assert!(partition_id < partitions_count);
assert!(message_count < messages.len());
Self {
- stream_id,
stream_name,
- topic_id,
topic_name,
partitions_count,
messages: messages.to_owned(),
partition_id,
message_count,
strategy,
- using_stream_id,
- using_topic_id,
show_headers,
headers,
+ actual_stream_id: None,
+ actual_topic_id: None,
}
}
@@ -102,15 +93,19 @@ impl TestMessagePollCmd {
command.extend(vec!["--show-headers".into()]);
}
- command.extend(match self.using_stream_id {
- TestStreamId::Numeric => vec![format!("{}", self.stream_id)],
- TestStreamId::Named => vec![self.stream_name.clone()],
- });
+ // Use actual stream ID if available, otherwise use stream name as fallback
+ if let Some(stream_id) = self.actual_stream_id {
+ command.push(format!("{}", stream_id));
+ } else {
+ command.push(self.stream_name.clone());
+ }
- command.push(match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
- });
+ // Use actual topic ID if available, otherwise use topic name as fallback
+ if let Some(topic_id) = self.actual_topic_id {
+ command.push(format!("{}", topic_id));
+ } else {
+ command.push(self.topic_name.clone());
+ }
command.push(format!("{}", self.partition_id));
@@ -121,24 +116,25 @@ impl TestMessagePollCmd {
#[async_trait]
impl IggyCmdTestCase for TestMessagePollCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client
- .create_stream(&self.stream_name, self.stream_id.into())
- .await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
+ let stream = stream.unwrap();
+ self.actual_stream_id = Some(stream.id);
let topic = client
.create_topic(
- &self.stream_id.try_into().unwrap(),
+ &stream.id.try_into().unwrap(),
&self.topic_name,
self.partitions_count,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await;
assert!(topic.is_ok());
+ let topic = topic.unwrap();
+ self.actual_topic_id = Some(topic.id);
let mut messages = self
.messages
@@ -155,8 +151,8 @@ impl IggyCmdTestCase for TestMessagePollCmd {
let send_status = client
.send_messages(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &stream.id.try_into().unwrap(),
+ &topic.id.try_into().unwrap(),
&Partitioning::partition_id(self.partition_id),
&mut messages,
)
@@ -173,14 +169,16 @@ impl IggyCmdTestCase for TestMessagePollCmd {
}
fn verify_command(&self, command_state: Assert) {
- let stream_id = match self.using_stream_id {
- TestStreamId::Numeric => format!("{}", self.stream_id),
- TestStreamId::Named => self.stream_name.clone(),
+ let stream_id = if let Some(stream_id) = self.actual_stream_id {
+ format!("{}", stream_id)
+ } else {
+ self.stream_name.clone()
};
- let topic_id = match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
+ let topic_id = if let Some(topic_id) = self.actual_topic_id {
+ format!("{}", topic_id)
+ } else {
+ self.topic_name.clone()
};
let polled_status = match self.message_count {
@@ -235,18 +233,18 @@ impl IggyCmdTestCase for TestMessagePollCmd {
}
async fn verify_server_state(&self, client: &dyn Client) {
- let topic = client
- .delete_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
- )
- .await;
- assert!(topic.is_ok());
-
- let stream = client
- .delete_stream(&self.stream_id.try_into().unwrap())
- .await;
- assert!(stream.is_ok());
+ if let (Some(stream_id), Some(topic_id)) = (self.actual_stream_id, self.actual_topic_id) {
+ let topic = client
+ .delete_topic(
+ &stream_id.try_into().unwrap(),
+ &topic_id.try_into().unwrap(),
+ )
+ .await;
+ assert!(topic.is_ok());
+
+ let stream = client.delete_stream(&stream_id.try_into().unwrap()).await;
+ assert!(stream.is_ok());
+ }
}
}
@@ -273,74 +271,26 @@ pub async fn should_be_successful() {
HeaderValue::from_str("value1").unwrap(),
);
- let test_parameters: Vec<(u32, usize, PollingStrategy, TestStreamId, TestTopicId, bool)> = vec![
- (
- 1,
- 1,
- PollingStrategy::offset(0),
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- true,
- ),
- (
- 2,
- 5,
- PollingStrategy::offset(0),
- TestStreamId::Numeric,
- TestTopicId::Named,
- true,
- ),
- (
- 3,
- 3,
- PollingStrategy::offset(3),
- TestStreamId::Named,
- TestTopicId::Numeric,
- true,
- ),
- (
- 4,
- 5,
- PollingStrategy::first(),
- TestStreamId::Named,
- TestTopicId::Named,
- true,
- ),
- (
- 1,
- 4,
- PollingStrategy::last(),
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- true,
- ),
- (
- 2,
- 3,
- PollingStrategy::next(),
- TestStreamId::Numeric,
- TestTopicId::Named,
- false,
- ),
+ let test_parameters: Vec<(u32, usize, PollingStrategy, bool)> = vec![
+ (0, 1, PollingStrategy::offset(0), true),
+ (1, 5, PollingStrategy::offset(0), true),
+ (2, 3, PollingStrategy::offset(3), true),
+ (3, 5, PollingStrategy::first(), true),
+ (0, 4, PollingStrategy::last(), true),
+ (1, 3, PollingStrategy::next(), false),
];
iggy_cmd_test.setup().await;
- for (partition_id, message_count, strategy, using_stream_id, using_topic_id, show_headers) in
- test_parameters
- {
+ for (partition_id, message_count, strategy, show_headers) in test_parameters {
iggy_cmd_test
.execute_test(TestMessagePollCmd::new(
- 1,
String::from("stream"),
- 2,
String::from("topic"),
4,
&test_messages,
partition_id,
message_count,
strategy,
- using_stream_id,
- using_topic_id,
show_headers,
test_headers.clone(),
))
@@ -418,7 +368,7 @@ Options:
{CLAP_INDENT}
Consumer ID can be specified as a consumer name or ID
{CLAP_INDENT}
- [default: 1]
+ [default: 0]
-s, --show-headers
Include the message headers in the output
@@ -469,7 +419,7 @@ Options:
-f, --first Polling strategy - start polling from the first message in the partition
-l, --last Polling strategy - start polling from the last message in the partition
-n, --next Polling strategy - start polling from the next message
- -c, --consumer <CONSUMER> Regular consumer which will poll messages [default: 1]
+ -c, --consumer <CONSUMER> Regular consumer which will poll messages [default: 0]
-s, --show-headers Include the message headers in the output
--output-file <OUTPUT_FILE> Store polled message into file in binary format
-h, --help Print help (see more with '--help')
diff --git a/core/integration/tests/cli/message/test_message_poll_to_file_command.rs b/core/integration/tests/cli/message/test_message_poll_to_file_command.rs
index 733871c4d..3f9d029e5 100644
--- a/core/integration/tests/cli/message/test_message_poll_to_file_command.rs
+++ b/core/integration/tests/cli/message/test_message_poll_to_file_command.rs
@@ -36,6 +36,9 @@ pub(super) struct TestMessagePollToFileCmd<'a> {
headers: HashMap<HeaderKey, HeaderValue>,
output_file: String,
cleanup: bool,
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
}
impl<'a> TestMessagePollToFileCmd<'a> {
@@ -60,6 +63,8 @@ impl<'a> TestMessagePollToFileCmd<'a> {
headers,
output_file: output_file.into(),
cleanup,
+ actual_stream_id: None,
+ actual_topic_id: None,
}
}
@@ -81,11 +86,21 @@ impl<'a> TestMessagePollToFileCmd<'a> {
command.extend(vec!["--output-file".into(), self.output_file.clone()]);
- command.extend(vec![
- self.stream_name.clone(),
- self.topic_name.clone(),
- "1".into(),
- ]);
+ // Use actual stream ID if available, otherwise use stream name as fallback
+ if let Some(stream_id) = self.actual_stream_id {
+ command.push(format!("{}", stream_id));
+ } else {
+ command.push(self.stream_name.clone());
+ }
+
+ // Use actual topic ID if available, otherwise use topic name as fallback
+ if let Some(topic_id) = self.actual_topic_id {
+ command.push(format!("{}", topic_id));
+ } else {
+ command.push(self.topic_name.clone());
+ }
+
+ command.push("0".into());
command
}
@@ -94,30 +109,25 @@ impl<'a> TestMessagePollToFileCmd<'a> {
#[async_trait]
impl IggyCmdTestCase for TestMessagePollToFileCmd<'_> {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client.create_stream(&self.stream_name, None).await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
-
- let stream_id = Identifier::from_str(self.stream_name.as_str());
- assert!(stream_id.is_ok());
- let stream_id = stream_id.unwrap();
+ let stream = stream.unwrap();
+ self.actual_stream_id = Some(stream.id);
let topic = client
.create_topic(
- &stream_id,
+ &stream.id.try_into().unwrap(),
&self.topic_name,
1,
Default::default(),
None,
- None,
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await;
assert!(topic.is_ok());
-
- let topic_id = Identifier::from_str(self.topic_name.as_str());
- assert!(topic_id.is_ok());
- let topic_id = topic_id.unwrap();
+ let topic = topic.unwrap();
+ self.actual_topic_id = Some(topic.id);
let mut messages = self
.messages
@@ -134,9 +144,9 @@ impl IggyCmdTestCase for TestMessagePollToFileCmd<'_> {
let send_status = client
.send_messages(
- &stream_id,
- &topic_id,
- &Partitioning::partition_id(1),
+ &stream.id.try_into().unwrap(),
+ &topic.id.try_into().unwrap(),
+ &Partitioning::partition_id(0),
&mut messages,
)
.await;
@@ -157,9 +167,21 @@ impl IggyCmdTestCase for TestMessagePollToFileCmd<'_> {
_ => format!("Polled {} messages", self.message_count),
};
+ let stream_id = if let Some(stream_id) = self.actual_stream_id {
+ format!("{}", stream_id)
+ } else {
+ self.stream_name.clone()
+ };
+
+ let topic_id = if let Some(topic_id) = self.actual_topic_id {
+ format!("{}", topic_id)
+ } else {
+ self.topic_name.clone()
+ };
+
let message_prefix = format!(
- "Executing poll messages from topic ID: {} and stream with ID: {}\nPolled messages from topic with ID: {} and stream with ID: {} (from partition with ID: 1)\n{polled_status}",
- self.topic_name, self.stream_name, self.topic_name, self.stream_name
+ "Executing poll messages from topic ID: {} and stream with ID: {}\nPolled messages from topic with ID: {} and stream with ID: {} (from partition with ID: 0)\n{polled_status}",
+ topic_id, stream_id, topic_id, stream_id
);
let message_file = format!("Storing messages to {} binary file", self.output_file);
let message_count = format!(
@@ -179,19 +201,18 @@ impl IggyCmdTestCase for TestMessagePollToFileCmd<'_> {
}
async fn verify_server_state(&self, client: &dyn Client) {
- let stream_id = Identifier::from_str(self.stream_name.as_str());
- assert!(stream_id.is_ok());
- let stream_id = stream_id.unwrap();
-
- let topic_id = Identifier::from_str(self.topic_name.as_str());
- assert!(topic_id.is_ok());
- let topic_id = topic_id.unwrap();
+ if let (Some(stream_id), Some(topic_id)) = (self.actual_stream_id, self.actual_topic_id) {
+ let topic = client
+ .delete_topic(
+ &stream_id.try_into().unwrap(),
+ &topic_id.try_into().unwrap(),
+ )
+ .await;
+ assert!(topic.is_ok());
- let topic = client.delete_topic(&stream_id, &topic_id).await;
- assert!(topic.is_ok());
-
- let stream = client.delete_stream(&stream_id).await;
- assert!(stream.is_ok());
+ let stream = client.delete_stream(&stream_id.try_into().unwrap()).await;
+ assert!(stream.is_ok());
+ }
assert!(Path::new(&self.output_file).is_file());
if self.cleanup {
diff --git a/core/integration/tests/cli/message/test_message_send_command.rs b/core/integration/tests/cli/message/test_message_send_command.rs
index 2a9cf5a5a..27227cd94 100644
--- a/core/integration/tests/cli/message/test_message_send_command.rs
+++ b/core/integration/tests/cli/message/test_message_send_command.rs
@@ -17,8 +17,7 @@
*/
use crate::cli::common::{
- CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, TestStreamId,
- TestTopicId, USAGE_PREFIX,
+ CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, USAGE_PREFIX,
};
use assert_cmd::assert::Assert;
use async_trait::async_trait;
@@ -53,61 +52,57 @@ enum ProvideMessages {
}
struct TestMessageSendCmd {
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
messages: Vec<String>,
message_input: ProvideMessages,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
partitioning: PartitionSelection,
header: Option<HashMap<HeaderKey, HeaderValue>>,
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
}
impl TestMessageSendCmd {
- #[allow(clippy::too_many_arguments)]
fn new(
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
messages: Vec<String>,
message_input: ProvideMessages,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
partitioning: PartitionSelection,
header: Option<HashMap<HeaderKey, HeaderValue>>,
) -> Self {
Self {
- stream_id,
stream_name,
- topic_id,
topic_name,
partitions_count,
messages,
message_input,
- using_stream_id,
- using_topic_id,
partitioning,
header,
+ actual_stream_id: None,
+ actual_topic_id: None,
}
}
fn to_args(&self) -> Vec<String> {
let mut command = self.partitioning.to_args();
- command.extend(match self.using_stream_id {
- TestStreamId::Numeric => vec![format!("{}", self.stream_id)],
- TestStreamId::Named => vec![self.stream_name.clone()],
- });
+ // Use actual stream ID if available, otherwise use stream name as fallback
+ if let Some(stream_id) = self.actual_stream_id {
+ command.push(format!("{}", stream_id));
+ } else {
+ command.push(self.stream_name.clone());
+ }
- command.push(match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
- });
+ // Use actual topic ID if available, otherwise use topic name as fallback
+ if let Some(topic_id) = self.actual_topic_id {
+ command.push(format!("{}", topic_id));
+ } else {
+ command.push(self.topic_name.clone());
+ }
if let Some(header) = &self.header {
command.push("--headers".to_string());
@@ -132,12 +127,7 @@ impl TestMessageSendCmd {
fn calculate_partition_id_from_messages_key(&self, messages_key: &[u8]) -> u32 {
let messages_key_hash = XxHash32::oneshot(0, messages_key);
- let mut partition_id = messages_key_hash % self.partitions_count;
- if partition_id == 0 {
- partition_id = self.partitions_count;
- }
-
- partition_id
+ messages_key_hash % self.partitions_count
}
fn get_partition_id(&self) -> u32 {
@@ -154,24 +144,27 @@ impl TestMessageSendCmd {
#[async_trait]
impl IggyCmdTestCase for TestMessageSendCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
+ // Create stream and capture its actual ID
let stream = client
- .create_stream(&self.stream_name, self.stream_id.into())
- .await;
- assert!(stream.is_ok());
+ .create_stream(&self.stream_name)
+ .await
+ .expect("Failed to create stream");
+ self.actual_stream_id = Some(stream.id);
+ // Create topic and capture its actual ID
let topic = client
.create_topic(
- &self.stream_id.try_into().unwrap(),
+ &stream.id.try_into().unwrap(),
&self.topic_name,
self.partitions_count,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
- .await;
- assert!(topic.is_ok());
+ .await
+ .expect("Failed to create topic");
+ self.actual_topic_id = Some(topic.id);
}
fn get_command(&self) -> IggyCmdCommand {
@@ -190,14 +183,16 @@ impl IggyCmdTestCase for TestMessageSendCmd {
}
fn verify_command(&self, command_state: Assert) {
- let stream_id = match self.using_stream_id {
- TestStreamId::Numeric => format!("{}", self.stream_id),
- TestStreamId::Named => self.stream_name.clone(),
+ let stream_id = if let Some(stream_id) = self.actual_stream_id {
+ format!("{}", stream_id)
+ } else {
+ self.stream_name.clone()
};
- let topic_id = match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
+ let topic_id = if let Some(topic_id) = self.actual_topic_id {
+ format!("{}", topic_id)
+ } else {
+ self.topic_name.clone()
};
let message = format!(
@@ -210,40 +205,85 @@ impl IggyCmdTestCase for TestMessageSendCmd {
async fn verify_server_state(&self, client: &dyn Client) {
let topic = client
.get_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
)
.await;
assert!(topic.is_ok());
let topic_details = topic.unwrap().expect("Failed to get topic");
assert_eq!(topic_details.messages_count, self.messages.len() as u64);
- let polled_messages = client
- .poll_messages(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
- Some(self.get_partition_id()),
- &Consumer::default(),
- &PollingStrategy::offset(0),
- self.messages.len() as u32,
- false,
- )
- .await;
+ // For Balanced partitioning, messages are distributed across all partitions
+ // so we need to poll from all partitions and collect messages
+ let all_messages = match &self.partitioning {
+ PartitionSelection::Balanced => {
+ let mut collected_messages = Vec::new();
+ for partition_id in 0..self.partitions_count {
+ let polled = client
+ .poll_messages(
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
+ Some(partition_id),
+ &Consumer::default(),
+ &PollingStrategy::offset(0),
+ self.messages.len() as u32,
+ false,
+ )
+ .await;
+ if let Ok(polled) = polled {
+ collected_messages.extend(polled.messages);
+ }
+ }
+ collected_messages
+ }
+ _ => {
+ // For specific partition or key-based partitioning
+ let polled_messages = client
+ .poll_messages(
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
+ Some(self.get_partition_id()),
+ &Consumer::default(),
+ &PollingStrategy::offset(0),
+ self.messages.len() as u32,
+ false,
+ )
+ .await;
+
+ assert!(polled_messages.is_ok());
+ polled_messages.unwrap().messages
+ }
+ };
- assert!(polled_messages.is_ok());
- let polled_messages = polled_messages.unwrap();
- assert_eq!(polled_messages.messages.len(), self.messages.len());
- assert_eq!(
- polled_messages
- .messages
- .iter()
- .map(|m| from_utf8(&m.payload.clone()).unwrap().to_string())
- .collect::<Vec<_>>(),
- self.messages
- );
+ assert_eq!(all_messages.len(), self.messages.len());
+
+ // For Balanced partitioning, messages may arrive in different order
+ // so we just check that all expected messages are present
+ let expected_messages: Vec<String> = self.messages.clone();
+ let received_messages: Vec<String> = all_messages
+ .iter()
+ .map(|m| from_utf8(&m.payload.clone()).unwrap().to_string())
+ .collect();
+
+ match &self.partitioning {
+ PartitionSelection::Balanced => {
+ // For balanced, just check all messages are present (order may vary)
+ for expected in &expected_messages {
+ assert!(
+ received_messages.contains(expected),
+ "Expected message '{}' not found in received messages",
+ expected
+ );
+ }
+ }
+ _ => {
+ // For specific partition, order should be preserved
+ assert_eq!(received_messages, expected_messages);
+ }
+ }
if let Some(expected_header) = &self.header {
- polled_messages.messages.iter().for_each(|m| {
+ all_messages.iter().for_each(|m| {
assert!(m.user_headers.is_some());
assert_eq!(expected_header, &m.user_headers_map().unwrap().unwrap());
})
@@ -251,14 +291,14 @@ impl IggyCmdTestCase for TestMessageSendCmd {
let topic = client
.delete_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
)
.await;
assert!(topic.is_ok());
let stream = client
- .delete_stream(&self.stream_id.try_into().unwrap())
+ .delete_stream(&self.actual_stream_id.unwrap().try_into().unwrap())
.await;
assert!(stream.is_ok());
}
@@ -270,87 +310,25 @@ pub async fn should_be_successful() {
let mut iggy_cmd_test = IggyCmdTest::default();
let test_parameters = vec![
+ (ProvideMessages::AsArgs, PartitionSelection::Balanced),
+ (ProvideMessages::ViaStdin, PartitionSelection::Balanced),
+ (ProvideMessages::ViaStdin, PartitionSelection::Id(0)),
+ (ProvideMessages::AsArgs, PartitionSelection::Id(1)),
(
ProvideMessages::AsArgs,
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- PartitionSelection::Balanced,
- ),
- (
- ProvideMessages::ViaStdin,
- TestStreamId::Numeric,
- TestTopicId::Named,
- PartitionSelection::Balanced,
- ),
- (
- ProvideMessages::AsArgs,
- TestStreamId::Named,
- TestTopicId::Numeric,
- PartitionSelection::Balanced,
- ),
- (
- ProvideMessages::ViaStdin,
- TestStreamId::Named,
- TestTopicId::Named,
- PartitionSelection::Balanced,
- ),
- (
- ProvideMessages::ViaStdin,
- TestStreamId::Numeric,
- TestTopicId::Numeric,
- PartitionSelection::Id(1),
- ),
- (
- ProvideMessages::AsArgs,
- TestStreamId::Named,
- TestTopicId::Numeric,
- PartitionSelection::Id(2),
- ),
- (
- ProvideMessages::AsArgs,
- TestStreamId::Numeric,
- TestTopicId::Named,
- PartitionSelection::Id(3),
- ),
- (
- ProvideMessages::ViaStdin,
- TestStreamId::Named,
- TestTopicId::Named,
- PartitionSelection::Id(4),
- ),
- (
- ProvideMessages::AsArgs,
- TestStreamId::Numeric,
- TestTopicId::Numeric,
PartitionSelection::Key(String::from("some-complex-key")),
),
(
ProvideMessages::ViaStdin,
- TestStreamId::Named,
- TestTopicId::Numeric,
- PartitionSelection::Key(String::from("another random value")),
- ),
- (
- ProvideMessages::ViaStdin,
- TestStreamId::Numeric,
- TestTopicId::Named,
- PartitionSelection::Key(String::from("some-random-key")),
- ),
- (
- ProvideMessages::AsArgs,
- TestStreamId::Named,
- TestTopicId::Named,
- PartitionSelection::Key(String::from("just a key")),
+ PartitionSelection::Key(String::from("another-key")),
),
];
iggy_cmd_test.setup().await;
- for (message_input, using_stream_id, using_topic_id, using_partitioning) in test_parameters {
+ for (message_input, using_partitioning) in test_parameters {
iggy_cmd_test
.execute_test(TestMessageSendCmd::new(
- 1,
String::from("stream"),
- 2,
String::from("topic"),
4,
vec![
@@ -359,8 +337,6 @@ pub async fn should_be_successful() {
String::from("test message3"),
],
message_input,
- using_stream_id,
- using_topic_id,
using_partitioning,
Some(HashMap::from([
(
diff --git a/core/integration/tests/cli/message/test_message_send_from_file_command.rs b/core/integration/tests/cli/message/test_message_send_from_file_command.rs
index 19cf4c556..1089bd7ff 100644
--- a/core/integration/tests/cli/message/test_message_send_from_file_command.rs
+++ b/core/integration/tests/cli/message/test_message_send_from_file_command.rs
@@ -35,6 +35,9 @@ pub(super) struct TestMessageSendFromFileCmd<'a> {
messages: Vec<&'a str>,
message_count: usize,
headers: HashMap<HeaderKey, HeaderValue>,
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
}
impl<'a> TestMessageSendFromFileCmd<'a> {
@@ -57,19 +60,33 @@ impl<'a> TestMessageSendFromFileCmd<'a> {
messages: messages.to_owned(),
message_count,
headers,
+ actual_stream_id: None,
+ actual_topic_id: None,
}
}
fn to_args(&self) -> Vec<String> {
- let command = vec![
+ let mut command = vec![
"--input-file".into(),
self.input_file.clone(),
"--partition-id".into(),
- "1".into(),
- self.stream_name.clone(),
- self.topic_name.clone(),
+ "0".into(),
];
+ // Use actual stream ID if available, otherwise use stream name as fallback
+ if let Some(stream_id) = self.actual_stream_id {
+ command.push(format!("{}", stream_id));
+ } else {
+ command.push(self.stream_name.clone());
+ }
+
+ // Use actual topic ID if available, otherwise use topic name as fallback
+ if let Some(topic_id) = self.actual_topic_id {
+ command.push(format!("{}", topic_id));
+ } else {
+ command.push(self.topic_name.clone());
+ }
+
command
}
}
@@ -77,29 +94,27 @@ impl<'a> TestMessageSendFromFileCmd<'a> {
#[async_trait]
impl IggyCmdTestCase for TestMessageSendFromFileCmd<'_> {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client.create_stream(&self.stream_name, None).await;
- assert!(stream.is_ok());
-
- let stream_id = Identifier::from_str(self.stream_name.as_str());
- assert!(stream_id.is_ok());
- let stream_id = stream_id.unwrap();
+ // Create stream and capture its actual ID
+ let stream = client
+ .create_stream(&self.stream_name)
+ .await
+ .expect("Failed to create stream");
+ self.actual_stream_id = Some(stream.id);
+ // Create topic and capture its actual ID
let topic = client
.create_topic(
- &stream_id,
+ &stream.id.try_into().unwrap(),
&self.topic_name,
1,
Default::default(),
None,
- None,
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
- .await;
- assert!(topic.is_ok());
-
- let topic_id = Identifier::from_str(self.topic_name.as_str());
- assert!(topic_id.is_ok());
+ .await
+ .expect("Failed to create topic");
+ self.actual_topic_id = Some(topic.id);
if self.initialize {
let file = tokio::fs::OpenOptions::new()
@@ -154,9 +169,21 @@ impl IggyCmdTestCase for TestMessageSendFromFileCmd<'_> {
}
fn verify_command(&self, command_state: Assert) {
+ let stream_id = if let Some(stream_id) = self.actual_stream_id {
+ format!("{}", stream_id)
+ } else {
+ self.stream_name.clone()
+ };
+
+ let topic_id = if let Some(topic_id) = self.actual_topic_id {
+ format!("{}", topic_id)
+ } else {
+ self.topic_name.clone()
+ };
+
let message_prefix = format!(
"Executing send messages to topic with ID: {} and stream with ID: {}\n",
- self.topic_name, self.stream_name
+ topic_id, stream_id
);
let message_read = format!("Read [0-9]+ bytes from {} file", self.input_file);
let message_created = format!(
@@ -168,7 +195,7 @@ impl IggyCmdTestCase for TestMessageSendFromFileCmd<'_> {
);
let message_sent = format!(
"Sent messages to topic with ID: {} and stream with ID: {}\n",
- self.topic_name, self.stream_name
+ topic_id, stream_id
);
command_state
@@ -180,50 +207,49 @@ impl IggyCmdTestCase for TestMessageSendFromFileCmd<'_> {
}
async fn verify_server_state(&self, client: &dyn Client) {
- let stream_id = Identifier::from_str(self.stream_name.as_str());
- assert!(stream_id.is_ok());
- let stream_id = stream_id.unwrap();
+ if let (Some(stream_id), Some(topic_id)) = (self.actual_stream_id, self.actual_topic_id) {
+ let messages = client
+ .poll_messages(
+ &stream_id.try_into().unwrap(),
+ &topic_id.try_into().unwrap(),
+ Some(0),
+ &Consumer::new(Identifier::default()),
+ &PollingStrategy::offset(0),
+ self.message_count as u32 * 2,
+ true,
+ )
+ .await;
+ assert!(messages.is_ok());
+ let messages = messages.unwrap();
- let topic_id = Identifier::from_str(self.topic_name.as_str());
- assert!(topic_id.is_ok());
- let topic_id = topic_id.unwrap();
+ // Check if there are only the expected number of messages
+ assert_eq!(messages.messages.len(), self.message_count);
- let messages = client
- .poll_messages(
- &stream_id,
- &topic_id,
- Some(1),
- &Consumer::new(Identifier::default()),
- &PollingStrategy::offset(0),
- self.message_count as u32 * 2,
- true,
- )
- .await;
- assert!(messages.is_ok());
- let messages = messages.unwrap();
+ // Check message order and content (payload and headers)
+ for (i, message) in messages.messages.iter().enumerate() {
+ assert_eq!(
+ message.payload,
+ Bytes::from(self.messages[i].as_bytes().to_vec())
+ );
+ assert_eq!(
+ message.user_headers_map().unwrap().is_some(),
+ !self.headers.is_empty()
+ );
+ assert_eq!(&message.user_headers_map().unwrap().unwrap(), &self.headers);
+ }
- // Check if there are only the expected number of messages
- assert_eq!(messages.messages.len(), self.message_count);
+ let topic_delete = client
+ .delete_topic(
+ &stream_id.try_into().unwrap(),
+ &topic_id.try_into().unwrap(),
+ )
+ .await;
+ assert!(topic_delete.is_ok());
- // Check message order and content (payload and headers)
- for (i, message) in messages.messages.iter().enumerate() {
- assert_eq!(
- message.payload,
- Bytes::from(self.messages[i].as_bytes().to_vec())
- );
- assert_eq!(
- message.user_headers_map().unwrap().is_some(),
- !self.headers.is_empty()
- );
- assert_eq!(&message.user_headers_map().unwrap().unwrap(), &self.headers);
+ let stream_delete = client.delete_stream(&stream_id.try_into().unwrap()).await;
+ assert!(stream_delete.is_ok());
}
- let topic_delete = client.delete_topic(&stream_id, &topic_id).await;
- assert!(topic_delete.is_ok());
-
- let stream_delete = client.delete_stream(&stream_id).await;
- assert!(stream_delete.is_ok());
-
let file_removal = std::fs::remove_file(&self.input_file);
assert!(file_removal.is_ok());
}
diff --git a/core/integration/tests/cli/partition/test_partition_create_command.rs b/core/integration/tests/cli/partition/test_partition_create_command.rs
index e1f0b191a..d1ced0b78 100644
--- a/core/integration/tests/cli/partition/test_partition_create_command.rs
+++ b/core/integration/tests/cli/partition/test_partition_create_command.rs
@@ -17,8 +17,7 @@
*/
use crate::cli::common::{
- CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, TestStreamId,
- TestTopicId, USAGE_PREFIX,
+ CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, USAGE_PREFIX,
};
use assert_cmd::assert::Assert;
use async_trait::async_trait;
@@ -30,53 +29,51 @@ use predicates::str::diff;
use serial_test::parallel;
struct TestPartitionCreateCmd {
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
compression_algorithm: CompressionAlgorithm,
new_partitions: u32,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
}
impl TestPartitionCreateCmd {
- #[allow(clippy::too_many_arguments)]
fn new(
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
compression_algorithm: CompressionAlgorithm,
new_partitions: u32,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
) -> Self {
Self {
- stream_id,
stream_name,
- topic_id,
topic_name,
partitions_count,
compression_algorithm,
new_partitions,
- using_stream_id,
- using_topic_id,
+ actual_stream_id: None,
+ actual_topic_id: None,
}
}
fn to_args(&self) -> Vec<String> {
- let mut command = match self.using_stream_id {
- TestStreamId::Numeric => vec![format!("{}", self.stream_id)],
- TestStreamId::Named => vec![self.stream_name.clone()],
- };
+ let mut command = vec![];
+
+ // Use actual stream ID if available, otherwise use stream name as fallback
+ if let Some(stream_id) = self.actual_stream_id {
+ command.push(format!("{}", stream_id));
+ } else {
+ command.push(self.stream_name.clone());
+ }
- command.push(match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
- });
+ // Use actual topic ID if available, otherwise use topic name as fallback
+ if let Some(topic_id) = self.actual_topic_id {
+ command.push(format!("{}", topic_id));
+ } else {
+ command.push(self.topic_name.clone());
+ }
command.push(format!("{}", self.new_partitions));
@@ -87,24 +84,23 @@ impl TestPartitionCreateCmd {
#[async_trait]
impl IggyCmdTestCase for TestPartitionCreateCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client
- .create_stream(&self.stream_name, self.stream_id.into())
- .await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
+ self.actual_stream_id = Some(stream.unwrap().id);
let topic = client
.create_topic(
- &self.stream_id.try_into().unwrap(),
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
&self.topic_name,
self.partitions_count,
self.compression_algorithm,
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await;
assert!(topic.is_ok());
+ self.actual_topic_id = Some(topic.unwrap().id);
}
fn get_command(&self) -> IggyCmdCommand {
@@ -116,14 +112,16 @@ impl IggyCmdTestCase for TestPartitionCreateCmd {
}
fn verify_command(&self, command_state: Assert) {
- let stream_id = match self.using_stream_id {
- TestStreamId::Numeric => format!("{}", self.stream_id),
- TestStreamId::Named => self.stream_name.clone(),
+ let stream_id = if let Some(stream_id) = self.actual_stream_id {
+ format!("{}", stream_id)
+ } else {
+ self.stream_name.clone()
};
- let topic_id = match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
+ let topic_id = if let Some(topic_id) = self.actual_topic_id {
+ format!("{}", topic_id)
+ } else {
+ self.topic_name.clone()
};
let mut partitions = String::from("partition");
@@ -142,14 +140,14 @@ impl IggyCmdTestCase for TestPartitionCreateCmd {
async fn verify_server_state(&self, client: &dyn Client) {
let topic = client
.get_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
)
.await;
assert!(topic.is_ok());
let topic_details = topic.unwrap().expect("Failed to get topic");
assert_eq!(topic_details.name, self.topic_name);
- assert_eq!(topic_details.id, self.topic_id);
+ assert_eq!(topic_details.id, self.actual_topic_id.unwrap());
assert_eq!(
topic_details.partitions_count,
self.partitions_count + self.new_partitions
@@ -158,14 +156,14 @@ impl IggyCmdTestCase for TestPartitionCreateCmd {
let topic = client
.delete_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
)
.await;
assert!(topic.is_ok());
let stream = client
- .delete_stream(&self.stream_id.try_into().unwrap())
+ .delete_stream(&self.actual_stream_id.unwrap().try_into().unwrap())
.await;
assert!(stream.is_ok());
}
@@ -179,54 +177,38 @@ pub async fn should_be_successful() {
iggy_cmd_test.setup().await;
iggy_cmd_test
.execute_test(TestPartitionCreateCmd::new(
- 1,
String::from("main"),
- 1,
String::from("sync"),
1,
Default::default(),
1,
- TestStreamId::Numeric,
- TestTopicId::Numeric,
))
.await;
iggy_cmd_test
.execute_test(TestPartitionCreateCmd::new(
- 2,
String::from("stream"),
- 3,
String::from("topic"),
3,
Default::default(),
2,
- TestStreamId::Named,
- TestTopicId::Numeric,
))
.await;
iggy_cmd_test
.execute_test(TestPartitionCreateCmd::new(
- 4,
String::from("development"),
- 1,
String::from("probe"),
0,
Default::default(),
4,
- TestStreamId::Numeric,
- TestTopicId::Named,
))
.await;
iggy_cmd_test
.execute_test(TestPartitionCreateCmd::new(
- 2,
String::from("production"),
- 5,
String::from("test"),
4,
Default::default(),
1,
- TestStreamId::Named,
- TestTopicId::Named,
))
.await;
}
diff --git a/core/integration/tests/cli/partition/test_partition_delete_command.rs b/core/integration/tests/cli/partition/test_partition_delete_command.rs
index 0b789aee0..8e726cd84 100644
--- a/core/integration/tests/cli/partition/test_partition_delete_command.rs
+++ b/core/integration/tests/cli/partition/test_partition_delete_command.rs
@@ -17,8 +17,7 @@
*/
use crate::cli::common::{
- CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, TestStreamId,
- TestTopicId, USAGE_PREFIX,
+ CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd, USAGE_PREFIX,
};
use assert_cmd::assert::Assert;
use async_trait::async_trait;
@@ -29,50 +28,40 @@ use predicates::str::diff;
use serial_test::parallel;
struct TestPartitionDeleteCmd {
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
new_partitions: u32,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
+ // These will be populated after creating the resources
+ actual_stream_id: Option<u32>,
+ actual_topic_id: Option<u32>,
}
impl TestPartitionDeleteCmd {
- #[allow(clippy::too_many_arguments)]
fn new(
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
partitions_count: u32,
new_partitions: u32,
- using_stream_id: TestStreamId,
- using_topic_id: TestTopicId,
) -> Self {
Self {
- stream_id,
stream_name,
- topic_id,
topic_name,
partitions_count,
new_partitions,
- using_stream_id,
- using_topic_id,
+ actual_stream_id: None,
+ actual_topic_id: None,
}
}
fn to_args(&self) -> Vec<String> {
- let mut command = match self.using_stream_id {
- TestStreamId::Numeric => vec![format!("{}", self.stream_id)],
- TestStreamId::Named => vec![self.stream_name.clone()],
- };
+ let mut command = vec![];
+
+ // Always use stream name for consistency with other tests
+ command.push(self.stream_name.clone());
- command.push(match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
- });
+ // Always use topic name for consistency with other tests
+ command.push(self.topic_name.clone());
command.push(format!("{}", self.new_partitions));
@@ -83,24 +72,23 @@ impl TestPartitionDeleteCmd {
#[async_trait]
impl IggyCmdTestCase for TestPartitionDeleteCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client
- .create_stream(&self.stream_name, self.stream_id.into())
- .await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
+ self.actual_stream_id = Some(stream.unwrap().id);
let topic = client
.create_topic(
- &self.stream_id.try_into().unwrap(),
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
&self.topic_name,
self.partitions_count,
Default::default(),
None,
- Some(self.topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await;
assert!(topic.is_ok());
+ self.actual_topic_id = Some(topic.unwrap().id);
}
fn get_command(&self) -> IggyCmdCommand {
@@ -112,16 +100,6 @@ impl IggyCmdTestCase for TestPartitionDeleteCmd {
}
fn verify_command(&self, command_state: Assert) {
- let stream_id = match self.using_stream_id {
- TestStreamId::Numeric => format!("{}", self.stream_id),
- TestStreamId::Named => self.stream_name.clone(),
- };
-
- let topic_id = match self.using_topic_id {
- TestTopicId::Numeric => format!("{}", self.topic_id),
- TestTopicId::Named => self.topic_name.clone(),
- };
-
let mut partitions = String::from("partition");
if self.new_partitions > 1 {
partitions.push('s');
@@ -129,7 +107,12 @@ impl IggyCmdTestCase for TestPartitionDeleteCmd {
let message = format!(
"Executing delete {} {partitions} for topic with ID: {} and stream with ID: {}\nDeleted {} {partitions} for topic with ID: {} and stream with ID: {}\n",
- self.new_partitions, topic_id, stream_id, self.new_partitions, topic_id, stream_id
+ self.new_partitions,
+ self.topic_name,
+ self.stream_name,
+ self.new_partitions,
+ self.topic_name,
+ self.stream_name
);
command_state.success().stdout(diff(message));
@@ -138,34 +121,30 @@ impl IggyCmdTestCase for TestPartitionDeleteCmd {
async fn verify_server_state(&self, client: &dyn Client) {
let topic = client
.get_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
)
.await;
assert!(topic.is_ok());
let topic_details = topic.unwrap().expect("Failed to get topic");
assert_eq!(topic_details.name, self.topic_name);
- assert_eq!(topic_details.id, self.topic_id);
- if self.new_partitions > self.partitions_count {
- assert_eq!(topic_details.partitions_count, 0);
- } else {
- assert_eq!(
- topic_details.partitions_count,
- self.partitions_count - self.new_partitions
- );
- }
+ assert_eq!(topic_details.id, self.actual_topic_id.unwrap());
+ assert_eq!(
+ topic_details.partitions_count,
+ self.partitions_count - self.new_partitions
+ );
assert_eq!(topic_details.messages_count, 0);
let topic = client
.delete_topic(
- &self.stream_id.try_into().unwrap(),
- &self.topic_id.try_into().unwrap(),
+ &self.actual_stream_id.unwrap().try_into().unwrap(),
+ &self.actual_topic_id.unwrap().try_into().unwrap(),
)
.await;
assert!(topic.is_ok());
let stream = client
- .delete_stream(&self.stream_id.try_into().unwrap())
+ .delete_stream(&self.actual_stream_id.unwrap().try_into().unwrap())
.await;
assert!(stream.is_ok());
}
@@ -179,50 +158,34 @@ pub async fn should_be_successful() {
iggy_cmd_test.setup().await;
iggy_cmd_test
.execute_test(TestPartitionDeleteCmd::new(
- 1,
String::from("main"),
- 1,
String::from("sync"),
3,
1,
- TestStreamId::Numeric,
- TestTopicId::Numeric,
))
.await;
iggy_cmd_test
.execute_test(TestPartitionDeleteCmd::new(
- 2,
String::from("stream"),
- 3,
String::from("topic"),
3,
2,
- TestStreamId::Named,
- TestTopicId::Numeric,
))
.await;
iggy_cmd_test
.execute_test(TestPartitionDeleteCmd::new(
- 4,
String::from("development"),
- 1,
String::from("probe"),
3,
3,
- TestStreamId::Numeric,
- TestTopicId::Named,
))
.await;
iggy_cmd_test
.execute_test(TestPartitionDeleteCmd::new(
- 2,
String::from("production"),
- 5,
String::from("test"),
+ 5,
3,
- 7,
- TestStreamId::Named,
- TestTopicId::Named,
))
.await;
}
diff --git a/core/integration/tests/cli/stream/test_stream_create_command.rs b/core/integration/tests/cli/stream/test_stream_create_command.rs
index 70e18f02f..b657863b6 100644
--- a/core/integration/tests/cli/stream/test_stream_create_command.rs
+++ b/core/integration/tests/cli/stream/test_stream_create_command.rs
@@ -25,12 +25,15 @@ use serial_test::parallel;
struct TestStreamCreateCmd {
stream_id: Option<u32>,
- name: String,
+ stream_name: String,
}
impl TestStreamCreateCmd {
fn new(stream_id: Option<u32>, name: String) -> Self {
- Self { stream_id, name }
+ Self {
+ stream_id,
+ stream_name: name,
+ }
}
fn to_args(&self) -> Vec<String> {
@@ -41,7 +44,7 @@ impl TestStreamCreateCmd {
args.push(format!("{stream_id}"));
}
- args.push(self.name.clone());
+ args.push(self.stream_name.clone());
args
}
@@ -60,14 +63,11 @@ impl IggyCmdTestCase for TestStreamCreateCmd {
}
fn verify_command(&self, command_state: Assert) {
- let stream_id = match self.stream_id {
- Some(stream_id) => format!("ID: {stream_id}"),
- None => "ID auto incremented".to_string(),
- };
+ let stream_id = "ID auto incremented";
let message = format!(
"Executing create stream with name: {} and {}\nStream with name: {} and {} created\n",
- self.name, stream_id, self.name, stream_id
+ self.stream_name, stream_id, self.stream_name, stream_id
);
command_state.success().stdout(diff(message));
@@ -75,17 +75,14 @@ impl IggyCmdTestCase for TestStreamCreateCmd {
async fn verify_server_state(&self, client: &dyn Client) {
let stream = client
- .get_stream(&self.name.clone().try_into().unwrap())
+ .get_stream(&self.stream_name.clone().try_into().unwrap())
.await;
assert!(stream.is_ok());
let stream = stream.unwrap().expect("Stream not found");
- assert_eq!(stream.name, self.name);
- if let Some(stream_id) = self.stream_id {
- assert_eq!(stream.id, stream_id);
- }
+ assert_eq!(stream.name, self.stream_name);
let delete = client
- .delete_stream(&self.name.clone().try_into().unwrap())
+ .delete_stream(&self.stream_name.clone().try_into().unwrap())
.await;
assert!(delete.is_ok());
}
@@ -98,7 +95,7 @@ pub async fn should_be_successful() {
iggy_cmd_test.setup().await;
iggy_cmd_test
- .execute_test(TestStreamCreateCmd::new(Some(123), String::from("main")))
+ .execute_test(TestStreamCreateCmd::new(None, String::from("main")))
.await;
iggy_cmd_test.setup().await;
diff --git a/core/integration/tests/cli/stream/test_stream_delete_command.rs b/core/integration/tests/cli/stream/test_stream_delete_command.rs
index fa0fe6a6f..aa548aeb1 100644
--- a/core/integration/tests/cli/stream/test_stream_delete_command.rs
+++ b/core/integration/tests/cli/stream/test_stream_delete_command.rs
@@ -28,7 +28,7 @@ use serial_test::parallel;
struct TestStreamDeleteCmd {
stream_id: u32,
- name: String,
+ stream_name: String,
using_identifier: TestStreamId,
}
@@ -36,14 +36,14 @@ impl TestStreamDeleteCmd {
fn new(stream_id: u32, name: String, using_identifier: TestStreamId) -> Self {
Self {
stream_id,
- name,
+ stream_name: name,
using_identifier,
}
}
fn to_arg(&self) -> String {
match self.using_identifier {
- TestStreamId::Named => self.name.clone(),
+ TestStreamId::Named => self.stream_name.clone(),
TestStreamId::Numeric => format!("{}", self.stream_id),
}
}
@@ -52,7 +52,7 @@ impl TestStreamDeleteCmd {
#[async_trait]
impl IggyCmdTestCase for TestStreamDeleteCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client.create_stream(&self.name, Some(self.stream_id)).await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
}
@@ -68,7 +68,7 @@ impl IggyCmdTestCase for TestStreamDeleteCmd {
let message = match self.using_identifier {
TestStreamId::Named => format!(
"Executing delete stream with ID: {}\nStream with ID: {} deleted\n",
- self.name, self.name
+ self.stream_name, self.stream_name
),
TestStreamId::Numeric => format!(
"Executing delete stream with ID: {}\nStream with ID: {} deleted\n",
@@ -95,14 +95,14 @@ pub async fn should_be_successful() {
iggy_cmd_test.setup().await;
iggy_cmd_test
.execute_test(TestStreamDeleteCmd::new(
- 1,
+ 0,
String::from("testing"),
- TestStreamId::Numeric,
+ TestStreamId::Named,
))
.await;
iggy_cmd_test
.execute_test(TestStreamDeleteCmd::new(
- 2,
+ 0,
String::from("production"),
TestStreamId::Named,
))
diff --git a/core/integration/tests/cli/stream/test_stream_get_command.rs b/core/integration/tests/cli/stream/test_stream_get_command.rs
index 9b8c9511a..a46543ef6 100644
--- a/core/integration/tests/cli/stream/test_stream_get_command.rs
+++ b/core/integration/tests/cli/stream/test_stream_get_command.rs
@@ -28,7 +28,7 @@ use serial_test::parallel;
struct TestStreamGetCmd {
stream_id: u32,
- name: String,
+ stream_name: String,
using_identifier: TestStreamId,
}
@@ -36,14 +36,14 @@ impl TestStreamGetCmd {
fn new(stream_id: u32, name: String, using_identifier: TestStreamId) -> Self {
Self {
stream_id,
- name,
+ stream_name: name,
using_identifier,
}
}
fn to_arg(&self) -> String {
match self.using_identifier {
- TestStreamId::Named => self.name.clone(),
+ TestStreamId::Named => self.stream_name.clone(),
TestStreamId::Numeric => format!("{}", self.stream_id),
}
}
@@ -52,7 +52,7 @@ impl TestStreamGetCmd {
#[async_trait]
impl IggyCmdTestCase for TestStreamGetCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client.create_stream(&self.name, Some(self.stream_id)).await;
+ let stream = client.create_stream(&self.stream_name).await;
assert!(stream.is_ok());
}
@@ -66,7 +66,10 @@ impl IggyCmdTestCase for TestStreamGetCmd {
fn verify_command(&self, command_state: Assert) {
let start_message = match self.using_identifier {
- TestStreamId::Named => format!("Executing get stream with ID: {}\n", self.name.clone()),
+ TestStreamId::Named => format!(
+ "Executing get stream with ID: {}\n",
+ self.stream_name.clone()
+ ),
TestStreamId::Numeric => format!("Executing get stream with ID: {}\n", self.stream_id),
};
@@ -74,10 +77,9 @@ impl IggyCmdTestCase for TestStreamGetCmd {
.success()
.stdout(starts_with(start_message))
.stdout(contains(format!(
- "Stream ID | {}",
- self.stream_id
+ "Stream name | {}",
+ self.stream_name
)))
- .stdout(contains(format!("Stream name | {}", self.name)))
.stdout(contains("Stream size | 0"))
.stdout(contains("Stream message count | 0"))
.stdout(contains("Stream topics count | 0"));
@@ -94,16 +96,16 @@ pub async fn should_be_successful() {
iggy_cmd_test.setup().await;
iggy_cmd_test
.execute_test(TestStreamGetCmd::new(
- 1,
+ 0,
String::from("production"),
TestStreamId::Named,
))
.await;
iggy_cmd_test
.execute_test(TestStreamGetCmd::new(
- 2,
+ 0,
String::from("testing"),
- TestStreamId::Numeric,
+ TestStreamId::Named,
))
.await;
}
diff --git a/core/integration/tests/cli/stream/test_stream_list_command.rs b/core/integration/tests/cli/stream/test_stream_list_command.rs
index 3c4d4db57..610e03841 100644
--- a/core/integration/tests/cli/stream/test_stream_list_command.rs
+++ b/core/integration/tests/cli/stream/test_stream_list_command.rs
@@ -27,18 +27,13 @@ use predicates::str::{contains, starts_with};
use serial_test::parallel;
struct TestStreamListCmd {
... 55054 lines suppressed ...