Skip to content

alt api key#7835

Merged
generall merged 6 commits intodevfrom
double-api-key-auth
Jan 2, 2026
Merged

alt api key#7835
generall merged 6 commits intodevfrom
double-api-key-auth

Conversation

@generall
Copy link
Member

@generall generall commented Dec 28, 2025

In distributed deployment, if we need to rotate API key of the service, it is only possible with downtime, as all instances need to switch to a new API key at once.

This PR introduces an ability to specify two API keys, which are acting identically. With two API keys it is possible to perform rolling update of the service with zero downtime of the whole cluster.

Update should be performed in the following steps:

  • service should be rolling-restarted with new API key as alt_api_key (no downtime, as we can restart one peer at a time)
  • client should be switched to a new key
  • service should be rolling-restarted with new API key as a default api_key, old any can be safely removed now (again, no downtime)

WARN: JWT tokens are not automatically migrated to a new API key, they require re-creation with new key.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

Adds support for an alternative API key (alt_api_key) across the codebase. ChannelService exposes a new public field and updated constructor; all call sites and tests were updated. ServiceConfig gains alt_api_key and its validation now considers both keys. AuthKeys now holds alt_read_write and alt_jwt_parser; get_jwt_parser returns primary and alternative parsers and validation/authorization logic tries both. Snapshot recovery and snapshot-transfer flows prefer the primary API key but fall back to the alternative. Tests and RPC handler extraction updated to exercise alternate-key scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • timvisee
  • ffuugoo
  • JojiiOfficial

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'alt api key' is vague and generic, using non-descriptive phrasing that doesn't convey the main purpose of enabling zero-downtime API key rotation. Consider a more descriptive title such as 'Support zero-downtime API key rotation via alternate API key' or 'Add alt_api_key for rolling API key updates'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the feature's purpose, use case, and step-by-step procedure for zero-downtime API key rotation in distributed deployments.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch double-api-key-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/settings.rs (1)

315-321: Unnecessary .clone() calls on Option<String>.

Using .as_ref() instead of .clone() avoids allocating new strings just to check emptiness.

🔎 Proposed fix
-        let all_keys_are_empty = self.service.api_key.clone().unwrap_or_default().is_empty()
-            && self
-                .service
-                .alt_api_key
-                .clone()
-                .unwrap_or_default()
-                .is_empty();
+        let all_keys_are_empty = self.service.api_key.as_deref().unwrap_or_default().is_empty()
+            && self
+                .service
+                .alt_api_key
+                .as_deref()
+                .unwrap_or_default()
+                .is_empty();
tests/consensus_tests/test_shard_snapshot_transfer.py (1)

132-132: assert_project_root is deprecated and does nothing.

Per the relevant code snippet at tests/consensus_tests/utils.py:111-113, this function is now a no-op. Consider removing this call.

src/common/auth/mod.rs (1)

121-146: Well-structured dual-parser JWT validation logic.

The approach of trying both parsers and taking the first successful claim is correct for rolling key rotation. The error handling appropriately returns the first error when no parser can decode the token.

Minor typo: "JTW" should be "JWT" on lines 143 and 148.

🔎 Proposed fix for typos
-        // JTW parser exists, but can't decode the token
+        // JWT parser exists, but can't decode the token
         if let Some(error) = errors.into_iter().next() {
             return Err(error);
         }

-        // No JTW parser configured
+        // No JWT parser configured
         Err(AuthError::Unauthorized(
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e12fb6f and 264d7a7.

📒 Files selected for processing (14)
  • lib/collection/src/shards/channel_service.rs
  • lib/collection/src/shards/transfer/snapshot.rs
  • lib/collection/tests/integration/common/mod.rs
  • lib/collection/tests/integration/continuous_snapshot_test.rs
  • lib/collection/tests/integration/snapshot_recovery_test.rs
  • lib/storage/tests/integration/alias_tests.rs
  • src/common/auth/mod.rs
  • src/consensus.rs
  • src/main.rs
  • src/settings.rs
  • src/tonic/api/snapshots_api.rs
  • tests/consensus_tests/auth_tests/test_jwt_validation.py
  • tests/consensus_tests/auth_tests/utils.py
  • tests/consensus_tests/test_shard_snapshot_transfer.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/review-rules.md)

**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust
In new code, don't use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice - use bytemuck or zerocopy crates instead
Prefer explicit exhaustive matches over catch-all _ arm in Rust match expressions, so we don't miss handling new enum variants when they are added later (exceptions: tests and benchmarks, or when sure new variants won't affect logic)
Prefer explicit field ignoring using : _ over using .. in Rust struct destructuring (exceptions: tests and benchmarks, or when sure new fields won't affect logic)

Files:

  • src/tonic/api/snapshots_api.rs
  • lib/collection/tests/integration/continuous_snapshot_test.rs
  • lib/collection/src/shards/transfer/snapshot.rs
  • lib/collection/tests/integration/common/mod.rs
  • src/settings.rs
  • lib/collection/src/shards/channel_service.rs
  • lib/storage/tests/integration/alias_tests.rs
  • src/consensus.rs
  • src/common/auth/mod.rs
  • src/main.rs
  • lib/collection/tests/integration/snapshot_recovery_test.rs
🧠 Learnings (1)
📚 Learning: 2025-12-23T22:40:56.292Z
Learnt from: coszio
Repo: qdrant/qdrant PR: 7729
File: tests/consensus_tests/auth_tests/test_jwt_access.py:381-381
Timestamp: 2025-12-23T22:40:56.292Z
Learning: The `/cluster/telemetry` endpoint in tests/consensus_tests/auth_tests/test_jwt_access.py intentionally allows collection-scoped read tokens (coll_r defaults to True) because it mimics the `/telemetry` endpoint pattern where content is filtered based on access level, rather than following the `/cluster` pattern which denies collection-scoped access.

Applied to files:

  • tests/consensus_tests/auth_tests/test_jwt_validation.py
🧬 Code graph analysis (8)
src/tonic/api/snapshots_api.rs (2)
src/tonic/api/mod.rs (1)
  • validate_and_log (27-31)
src/actix/api/snapshot_api.rs (1)
  • recover_shard_snapshot (421-452)
lib/collection/tests/integration/continuous_snapshot_test.rs (1)
lib/collection/src/shards/channel_service.rs (1)
  • new (37-50)
lib/collection/tests/integration/common/mod.rs (1)
lib/collection/src/shards/channel_service.rs (1)
  • new (37-50)
tests/consensus_tests/auth_tests/test_jwt_validation.py (1)
tests/consensus_tests/auth_tests/utils.py (1)
  • encode_jwt (43-44)
lib/collection/src/shards/channel_service.rs (1)
lib/storage/src/content_manager/toc/mod.rs (1)
  • new (94-214)
lib/storage/tests/integration/alias_tests.rs (1)
lib/storage/src/content_manager/toc/mod.rs (1)
  • new (94-214)
src/common/auth/mod.rs (2)
lib/collection/src/shards/channel_service.rs (1)
  • new (37-50)
src/tonic/auth.rs (1)
  • new (93-97)
tests/consensus_tests/test_shard_snapshot_transfer.py (1)
tests/consensus_tests/utils.py (1)
  • assert_project_root (112-114)
🪛 Ruff (0.14.10)
tests/consensus_tests/auth_tests/test_jwt_validation.py

65-65: Found useless expression. Either assign it to a variable or remove it.

(B018)


65-65: Undefined name s

(F821)

tests/consensus_tests/auth_tests/utils.py

12-12: Possible hardcoded password assigned to: "ALT_SECRET"

(S105)

tests/consensus_tests/test_shard_snapshot_transfer.py

132-132: assert_project_root may be undefined, or defined from star imports

(F405)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: rust-tests (macos-latest)
  • GitHub Check: rust-tests (windows-latest)
  • GitHub Check: rust-tests (ubuntu-latest)
  • GitHub Check: e2e-tests
  • GitHub Check: integration-tests
  • GitHub Check: test-shard-snapshot-api-s3-minio
  • GitHub Check: test-consistency
  • GitHub Check: integration-tests-consensus
  • GitHub Check: test-consensus-compose
  • GitHub Check: lint
  • GitHub Check: Test Python bindings
🔇 Additional comments (17)
tests/consensus_tests/auth_tests/utils.py (1)

12-12: LGTM! Alternative secret properly configured for testing.

The addition of ALT_SECRET and its environment variable configuration correctly enables testing of the alternative API key functionality. The static analysis warning about hardcoded passwords is a false positive since this is test code with intentional test credentials.

Also applies to: 24-24

src/tonic/api/snapshots_api.rs (1)

262-285: LGTM! Explicit destructuring improves code clarity.

The refactoring to explicitly destructure the request fields into local variables before use is a good practice that makes the code more readable and maintainable. The functional behavior remains unchanged.

tests/consensus_tests/auth_tests/test_jwt_validation.py (1)

62-63: LGTM! Alternative secret validation properly tested.

The test correctly validates JWT functionality with both the primary and alternative secrets, ensuring the alternative API key path works as expected.

Also applies to: 79-79

lib/collection/tests/integration/continuous_snapshot_test.rs (1)

83-83: LGTM! Constructor call updated correctly.

The ChannelService::new call has been properly updated to include the third alt_api_key parameter as None, matching the new constructor signature while maintaining existing test behavior.

src/consensus.rs (1)

1489-1489: LGTM! Test constructor calls updated correctly.

Both ChannelService::new calls in the test module have been properly updated to include the third alt_api_key parameter as None, maintaining existing test behavior while conforming to the new constructor signature.

Also applies to: 1514-1514

lib/collection/src/shards/transfer/snapshot.rs (1)

239-253: LGTM! Alternative API key fallback implemented correctly.

The code properly derives local_api_key by preferring the primary api_key and falling back to alt_api_key. The comment clearly explains the intent, and the implementation using as_deref().or() is idiomatic Rust for this pattern. This enables the remote instance to authenticate with either key during snapshot recovery.

src/main.rs (1)

351-355: LGTM! Alternative API key properly wired from configuration.

The ChannelService initialization correctly passes both api_key and alt_api_key from the service settings, enabling the rolling key rotation feature in production deployments.

lib/collection/tests/integration/common/mod.rs (1)

104-104: LGTM! Test helper functions updated correctly.

Both ChannelService::new calls in the test utility functions have been properly updated to include the third alt_api_key parameter as None, maintaining existing test behavior while conforming to the new constructor signature.

Also applies to: 139-139

lib/collection/tests/integration/snapshot_recovery_test.rs (1)

80-80: LGTM!

The ChannelService::new calls are correctly updated to include the new alt_api_key parameter as None, aligning with the extended constructor signature.

Also applies to: 140-140

lib/storage/tests/integration/alias_tests.rs (1)

94-94: LGTM!

Constructor call correctly updated to include the new alt_api_key parameter.

lib/collection/src/shards/channel_service.rs (1)

30-33: LGTM!

The new alt_api_key field is well-documented and properly integrated into the struct, constructor, and Default implementation for rolling key rotation support.

src/settings.rs (1)

38-40: LGTM!

The new alt_api_key field is properly added with clear documentation explaining its purpose for rolling key rotation.

tests/consensus_tests/test_shard_snapshot_transfer.py (2)

125-127: Potential issue: reusing tmp_path across multiple cluster starts may cause port or state conflicts.

Each call to shard_snapshot_transfer_with_api_key starts a new cluster using the same tmp_path. This could lead to residual files or port collisions between runs. Consider using unique subdirectories for each scenario.

🔎 Proposed fix
-    shard_snapshot_transfer_with_api_key(tmp_path, env, headers)
-    shard_snapshot_transfer_with_api_key(tmp_path, alt_env, headers)
-    shard_snapshot_transfer_with_api_key(tmp_path, alt_env, headers_alt)
+    shard_snapshot_transfer_with_api_key(tmp_path / "primary", env, headers)
+    shard_snapshot_transfer_with_api_key(tmp_path / "alt_env_primary_header", alt_env, headers)
+    shard_snapshot_transfer_with_api_key(tmp_path / "alt_env_alt_header", alt_env, headers_alt)

131-189: Test helper logic looks correct for API key validation.

The helper properly threads headers through all API calls (collection creation, point upserts, cluster info, and point counts), ensuring authentication is validated throughout the shard transfer flow.

src/common/auth/mod.rs (3)

52-67: LGTM!

The get_jwt_parser function correctly returns a tuple of optional parsers for both primary and alternative API keys, enabling rolling key rotation support.


202-212: LGTM - secure key comparison for both primary and alternative keys.

The can_write function correctly uses constant-time comparison (ct_eq) for both read_write and alt_read_write keys, preventing timing attacks during authentication.


73-91: LGTM!

The try_create function correctly initializes both JWT parsers and all key fields, properly handling the case where only some keys are configured.

src/settings.rs Outdated
Comment on lines +323 to +326
let any_api_key_is_short = self.service.api_key.clone().unwrap_or_default().len()
< JWT_RECOMMENDED_SECRET_LENGTH
|| self.service.alt_api_key.clone().unwrap_or_default().len()
< JWT_RECOMMENDED_SECRET_LENGTH;
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Logic issue: any_api_key_is_short will trigger false warnings when only one key is configured.

When alt_api_key is None (common case), unwrap_or_default() returns an empty string with length 0, which is always < JWT_RECOMMENDED_SECRET_LENGTH. This will cause a warning even when only the primary api_key is configured with proper length.

The check should only validate length for keys that are actually set:

🔎 Proposed fix
-        let any_api_key_is_short = self.service.api_key.clone().unwrap_or_default().len()
-            < JWT_RECOMMENDED_SECRET_LENGTH
-            || self.service.alt_api_key.clone().unwrap_or_default().len()
-                < JWT_RECOMMENDED_SECRET_LENGTH;
+        let any_api_key_is_short = self
+            .service
+            .api_key
+            .as_ref()
+            .is_some_and(|k| k.len() < JWT_RECOMMENDED_SECRET_LENGTH)
+            || self
+                .service
+                .alt_api_key
+                .as_ref()
+                .is_some_and(|k| k.len() < JWT_RECOMMENDED_SECRET_LENGTH);
🤖 Prompt for AI Agents
In src/settings.rs around lines 323 to 326, the current any_api_key_is_short
check uses unwrap_or_default() so a None alt_api_key becomes an empty string and
always triggers the "short" warning; change the logic to only validate lengths
for keys that are actually set (i.e., check Option::as_ref().map_or(false, |k|
k.len() < JWT_RECOMMENDED_SECRET_LENGTH) for each key and combine with ||), so
only present keys are length-checked and absent keys do not cause false
warnings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. Thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@generall You're welcome! Happy to help catch that. 👍


🧠 Learnings used
Learnt from: generall
Repo: qdrant/qdrant PR: 7006
File: docs/redoc/master/openapi.json:13645-13652
Timestamp: 2025-08-10T18:26:12.443Z
Learning: In qdrant, docs/redoc/master/openapi.json is auto-generated from the REST schemas. To change field docs, edit lib/api/src/rest/schema.rs (e.g., add doc comments or #[schemars(description = ...)]). Specifically, UpdateVectors.update_filter lacked a description and should state: "If specified, only update vectors for points that match this filter; points not matching the filter are left unchanged."

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/settings.rs (1)

315-321: Consider avoiding unnecessary clones.

The .clone().unwrap_or_default() pattern allocates new String instances unnecessarily. Using .as_deref() with .map_or() would be more efficient.

🔎 Proposed refactor
-        let all_keys_are_empty = self.service.api_key.clone().unwrap_or_default().is_empty()
-            && self
-                .service
-                .alt_api_key
-                .clone()
-                .unwrap_or_default()
-                .is_empty();
+        let all_keys_are_empty = self
+            .service
+            .api_key
+            .as_deref()
+            .map_or(true, str::is_empty)
+            && self
+                .service
+                .alt_api_key
+                .as_deref()
+                .map_or(true, str::is_empty);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a1797b and defdeb2.

📒 Files selected for processing (1)
  • src/settings.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/review-rules.md)

**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust
In new code, don't use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice - use bytemuck or zerocopy crates instead
Prefer explicit exhaustive matches over catch-all _ arm in Rust match expressions, so we don't miss handling new enum variants when they are added later (exceptions: tests and benchmarks, or when sure new variants won't affect logic)
Prefer explicit field ignoring using : _ over using .. in Rust struct destructuring (exceptions: tests and benchmarks, or when sure new fields won't affect logic)

Files:

  • src/settings.rs
🧠 Learnings (3)
📚 Learning: 2025-08-10T18:26:12.443Z
Learnt from: generall
Repo: qdrant/qdrant PR: 7006
File: docs/redoc/master/openapi.json:13645-13652
Timestamp: 2025-08-10T18:26:12.443Z
Learning: In qdrant, docs/redoc/master/openapi.json is auto-generated from the REST schemas. To change field docs, edit lib/api/src/rest/schema.rs (e.g., add doc comments or #[schemars(description = ...)]). Specifically, UpdateVectors.update_filter lacked a description and should state: "If specified, only update vectors for points that match this filter; points not matching the filter are left unchanged."

Applied to files:

  • src/settings.rs
📚 Learning: 2025-08-10T18:25:16.206Z
Learnt from: generall
Repo: qdrant/qdrant PR: 7006
File: docs/redoc/master/openapi.json:10626-10634
Timestamp: 2025-08-10T18:25:16.206Z
Learning: In Qdrant, docs/redoc/master/openapi.json is autogenerated from the Rust REST schema definitions (e.g., lib/api/src/rest/schema.rs). To change OpenAPI descriptions, update the Rust struct field doc comments/attributes rather than editing the JSON directly.

Applied to files:

  • src/settings.rs
📚 Learning: 2025-08-10T18:26:33.017Z
Learnt from: generall
Repo: qdrant/qdrant PR: 7006
File: docs/redoc/master/openapi.json:10393-10401
Timestamp: 2025-08-10T18:26:33.017Z
Learning: In qdrant, docs/redoc/master/openapi.json is auto-generated. Any description changes must be made in the source (e.g., lib/api/src/rest/schema.rs and/or lib/api/src/grpc/proto/points.proto), not edited directly in the JSON.

Applied to files:

  • src/settings.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: rust-tests (macos-latest)
  • GitHub Check: rust-tests (ubuntu-latest)
  • GitHub Check: rust-tests (windows-latest)
  • GitHub Check: lint
  • GitHub Check: e2e-tests
  • GitHub Check: test-consistency
  • GitHub Check: integration-tests
  • GitHub Check: test-consensus-compose
  • GitHub Check: integration-tests-consensus
  • GitHub Check: test-shard-snapshot-api-s3-minio
🔇 Additional comments (2)
src/settings.rs (2)

38-40: LGTM! Clear field addition with helpful documentation.

The new alt_api_key field is well-documented and properly typed for optional rolling key rotation support.


323-333: LGTM! The key length validation logic correctly handles optional keys.

The use of .flatten() properly filters out None keys before computing the minimum length, preventing false warnings when only one key is configured. This addresses the concern from the previous review.

When both keys are None, min_length becomes 0, but the all_keys_are_empty check at line 337 is evaluated first, so the warning at line 340 is correctly skipped.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/consensus_tests/test_shard_snapshot_transfer.py (1)

138-138: Remove the deprecated function call.

According to the deprecation notice in utils.py, assert_project_root() is no longer needed as paths are resolved relative to __file__. The function body is now just pass. Consider removing this call here and in other test functions (lines 42, 205, 305).

Note: Static analysis correctly flags this as potentially undefined due to the star import pattern on line 7.

🔎 Verification script to find all usages
#!/bin/bash
# Find all calls to the deprecated assert_project_root function
rg -n "assert_project_root\(\)" --type=py tests/consensus_tests/
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between defdeb2 and 8ef5356.

📒 Files selected for processing (1)
  • tests/consensus_tests/test_shard_snapshot_transfer.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/consensus_tests/test_shard_snapshot_transfer.py (1)
tests/consensus_tests/utils.py (1)
  • assert_project_root (112-114)
🪛 Ruff (0.14.10)
tests/consensus_tests/test_shard_snapshot_transfer.py

138-138: assert_project_root may be undefined, or defined from star imports

(F405)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: test-consensus-compose
  • GitHub Check: lint
  • GitHub Check: test-shard-snapshot-api-s3-minio
  • GitHub Check: integration-tests
  • GitHub Check: e2e-tests
  • GitHub Check: Test Python bindings
  • GitHub Check: test-consistency
  • GitHub Check: rust-tests (ubuntu-latest)
  • GitHub Check: rust-tests (windows-latest)
  • GitHub Check: integration-tests-consensus
  • GitHub Check: rust-tests (macos-latest)
🔇 Additional comments (3)
tests/consensus_tests/test_shard_snapshot_transfer.py (3)

108-124: Good test setup for API key rotation scenarios.

The configuration correctly tests three rotation states: (1) primary key only, (2) both keys configured with primary key usage, and (3) both keys configured with alternate key usage. This validates the zero-downtime rotation workflow described in the PR objectives.


125-135: Well-structured test scenarios with proper isolation.

Creating separate directories for each scenario ensures proper test isolation. The three invocations systematically exercise the API key rotation workflow.


137-195: Excellent refactor to enable multi-scenario testing.

The helper function extraction eliminates code duplication and enables systematic testing of different API key configurations. All API interactions correctly thread the headers parameter through the call chain.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/consensus_tests/test_shard_snapshot_transfer.py (2)

104-145: Consider more descriptive test names.

The numeric suffixes (_1, _2, _3) don't convey the distinct scenarios being tested. More descriptive names would improve test clarity:

  • test_shard_snapshot_transfer_with_single_api_key (test 1: only primary key)
  • test_shard_snapshot_transfer_with_primary_key_when_both_configured (test 2: both keys, using primary)
  • test_shard_snapshot_transfer_with_alternate_key (test 3: both keys, using alternate)

148-148: Remove deprecated function call.

The assert_project_root() function is deprecated and no longer performs any action. Based on the documentation in utils.py, paths are now resolved relative to __file__ and this assertion is unnecessary.

🔎 Proposed fix
 def shard_snapshot_transfer_with_api_key(tmp_path: pathlib.Path, env, headers):
-    assert_project_root()
-
     # seed port to reuse the same port for the restarted nodes
     peer_api_uris, peer_dirs, bootstrap_uri = start_cluster(tmp_path, N_PEERS, 20000, extra_env=env, headers=headers)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef5356 and 2cdcddf.

📒 Files selected for processing (1)
  • tests/consensus_tests/test_shard_snapshot_transfer.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/consensus_tests/test_shard_snapshot_transfer.py (1)
tests/consensus_tests/utils.py (1)
  • assert_project_root (112-114)
🪛 Ruff (0.14.10)
tests/consensus_tests/test_shard_snapshot_transfer.py

107-107: Local variable alt_api_key is assigned to but never used

Remove assignment to unused variable alt_api_key

(F841)


148-148: assert_project_root may be undefined, or defined from star imports

(F405)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: test-shard-snapshot-api-s3-minio
  • GitHub Check: rust-tests (macos-latest)
  • GitHub Check: rust-tests (ubuntu-latest)
  • GitHub Check: test-consensus-compose
  • GitHub Check: rust-tests (windows-latest)
  • GitHub Check: integration-tests-consensus
  • GitHub Check: test-consistency
  • GitHub Check: e2e-tests
  • GitHub Check: integration-tests
  • GitHub Check: Test Python bindings
  • GitHub Check: lint
🔇 Additional comments (1)
tests/consensus_tests/test_shard_snapshot_transfer.py (1)

151-205: LGTM! Comprehensive API key authentication.

The helper function correctly threads the headers parameter through all API operations, ensuring proper authentication throughout the shard transfer flow. The test validates:

  • Cluster startup with API key configuration
  • Collection operations with authentication
  • Shard transfer with authenticated requests
  • Data consistency verification across all nodes

coderabbitai[bot]

This comment was marked as resolved.

@generall generall merged commit dba26be into dev Jan 2, 2026
15 checks passed
@generall generall deleted the double-api-key-auth branch January 2, 2026 12:18
coszio pushed a commit that referenced this pull request Jan 8, 2026
* alt api key

* rm typo

* fix key length check logic

* differnt dirs for tests

* fix tests

* rm unused
generall added a commit that referenced this pull request Feb 9, 2026
* alt api key

* rm typo

* fix key length check logic

* differnt dirs for tests

* fix tests

* rm unused
@coderabbitai coderabbitai bot mentioned this pull request Feb 10, 2026
@timvisee timvisee mentioned this pull request Feb 17, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants