Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix user ERR NOAUTH Authentication required #2939

Merged
merged 5 commits into from
Nov 22, 2024

Conversation

buzhimingyonghu
Copy link
Contributor

@buzhimingyonghu buzhimingyonghu commented Oct 31, 2024

Issue

(#2920)

描述

配置如下:

  • requirepass : xxx (管理员密码)
  • userpass:
  • userblacklist : FLUSHALL

设置了管理员密码,然后将 userpass 设置为空,这样普通用户无需密码即可使用。对于管理命令,如 FLUSHALL 等,需要管理员密码登录才可使用。在 3.5.2 版本中,工作正常,升级到 3.5.5 后,普通用户无法连接 Pika,提示:ERR NOAUTH Authentication required

需要去除 requirepass 才可以正常连接使用,如下:

  • requirepass :
  • userpass:
  • userblacklist : FLUSHALL

只有两项均为空,才可以无需密码连接,无法单独设置管理员密码。

Issue 背景

在 3.5.5 后,添加了 ACL 认证,改变了传统的认证方式 auth_stat_.IsAuthed(c_ptr)。每一个 PikaClientConn 都拥有一个用户,里面存储了相关的 flag、password、limit 等信息。每来一个请求,PikaClientConn 都会做一次 Check authed,即检查当前 PikaClientConn 是否有密码、是否被禁用、是否需要认证。

问题

由于 PikaClientConn 在初始化时一直使用的是 DefaultUser,所以在配置文件中无论是否配置密码或 blacklist,均使用的是 DefaultUser

解决方案

需要根据配置文件,去调整用户信息。在 PikaClientConn 初始化时,获取 g_pika_confrequirepass() 是否为空;如果为空,则使用默认的用户;如果设置了密码,则使用 limit 的用户。

经过测试可以实现普通用户无密码登陆

image

image

有密码登陆也正常

image
image

limit也正常

image

Summary by CodeRabbit

  • New Features

    • Introduced a new configuration parameter best-delete-min-ratio for improved compaction strategy.
    • Added a new method InitUser() to enhance user initialization in the Pika client connection.
    • Implemented version-specific handling for metrics collection in the exporter.
  • Bug Fixes

    • Improved user password management by ensuring default users are set to "nopass" when no password is specified.
  • Style

    • Minor formatting changes for improved code readability across various files.
  • Documentation

    • Updated comments for clarity in the Pika client connection methods and enhanced logging context in the exporter.

Copy link

coderabbitai bot commented Oct 31, 2024

Walkthrough

The pull request introduces changes across multiple files, focusing on configuration updates, code formatting, and enhancements to user management. A new configuration parameter best-delete-min-ratio is added to conf/pika.conf. Minor formatting adjustments are made in include/acl.h and include/pika_client_conn.h. The PikaClientConn class sees the introduction of the InitUser method, centralizing user initialization logic. Additionally, modifications in src/acl.cc and src/pika_client_conn.cc enhance user authentication handling and improve code readability. Changes in the tools/pika_exporter files introduce version checking functionality for metrics parsing.

Changes

File Change Summary
conf/pika.conf Added new configuration parameter: best-delete-min-ratio : 10.
include/acl.h Minor formatting changes in AclSelector constructor and EqualChannel method signature.
include/pika_client_conn.h Added new method: void InitUser(). Minor formatting changes in TimeStat struct methods.
src/acl.cc Modified user password management logic in UpdateDefaultUserPassword and InitLimitUser.
src/pika_client_conn.cc Added InitUser method in constructor. Updated AuthRequired method logic.
tools/pika_exporter/exporter/metrics/parser.go Added new version checking functionality with CurrentVersion and VersionChecker interface.
tools/pika_exporter/exporter/pika.go Enhanced collectInfo method with version handling and added selectversion method.

Possibly related PRs

  • feat: a new strategy for auto compaction (ospp 2024) #2816: The changes in this PR enhance the compaction logic in the PikaServer class, specifically incorporating the best_delete_min_ratio parameter, which directly relates to the new configuration parameter added in the main PR for the Pika server's compaction strategy.

Suggested labels

✏️ Feature, 4.0.1

Suggested reviewers

  • dingxiaoshuai123
  • wangshao1

Poem

In the burrow where code does play,
New settings bloom, bright as day.
With InitUser now in sight,
Authentication feels just right.
A hop, a skip, through changes vast,
Pika's future, bright and fast! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added ☢️ Bug Something isn't working Invalid PR Title labels Oct 31, 2024
Copy link

@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

🧹 Outside diff range and nitpick comments (2)
include/pika_client_conn.h (1)

87-87: Consider documenting the authentication flow.

The authentication mechanism spans multiple methods (InitUser, DoAuth, UnAuth, IsAuthed) and maintains state via authenticated_ and user_ members. Consider adding documentation to clarify:

  1. The role of each authentication method
  2. The state transitions between authenticated/unauthenticated
  3. The interaction with the ACL system

Also applies to: 129-130

conf/pika.conf (1)

694-694: Document the new configuration parameter.

The newly added best-delete-min-ratio parameter appears to be related to the OBD_Compact strategy, but lacks documentation explaining its purpose and impact.

Consider adding a comment block above the parameter explaining:

  • Its purpose and relationship with OBD_Compact
  • Valid value range and default value
  • Impact on compaction behavior
  • Relationship with other compaction parameters like force-compact-min-delete-ratio
+ # The minimum ratio of deleted entries required for considering a file
+ # as a candidate for compaction in the OBD_Compact strategy.
+ # Valid range: [1-100], Default: 10
 best-delete-min-ratio : 10
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b5cdb2f and 0054e94.

📒 Files selected for processing (5)
  • conf/pika.conf (1 hunks)
  • include/acl.h (3 hunks)
  • include/pika_client_conn.h (3 hunks)
  • src/acl.cc (1 hunks)
  • src/pika_client_conn.cc (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • include/acl.h
🔇 Additional comments (8)
include/pika_client_conn.h (2)

25-33: LGTM! Improved readability of TimeStat methods.

The formatting changes make these simple getter methods more concise while maintaining functionality.


87-87: LGTM! InitUser method addition aligns with authentication fix.

The new InitUser() method centralizes user initialization logic, working alongside existing authentication methods (DoAuth, UnAuth, IsAuthed) to fix the ERR NOAUTH issues when upgrading from v3.5.2 to v3.5.5.

Let's verify the relationship between authentication methods:

✅ Verification successful

InitUser() implementation correctly integrates with existing auth flow

The verification confirms that InitUser() is properly integrated into the authentication system:

  • It's called in the constructor to initialize user state
  • Sets up initial authentication state based on password requirements and user flags
  • Works in conjunction with other auth methods:
    • DoAuth(): Sets authenticated state after successful auth
    • UnAuth(): Resets auth state and user
    • AuthRequired(): Uses authenticated state set by InitUser/DoAuth
    • IsAuthed(): Returns the authenticated state

The implementation shows proper initialization of both user and authentication state, with correct handling of default users and password requirements. The method fits well into the existing authentication flow.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the relationship between auth-related methods
# Look for calls to InitUser and its interaction with other auth methods

# Search for InitUser implementation and usage
echo "=== InitUser implementation and usage ==="
rg "InitUser.*\(" -A 10

# Search for auth-related method interactions
echo "=== Auth method interactions ==="
rg "DoAuth|UnAuth|IsAuthed|AuthRequired" -A 5

Length of output: 9664

src/pika_client_conn.cc (4)

33-33: LGTM: Improved user initialization in constructor.

The change centralizes user initialization logic in a dedicated method, making it more maintainable.


291-293: LGTM: Clear documentation of pipeline batch handling.

The comment clearly explains that pipeline batches are not intercepted by the cache.


358-368: LGTM: Comprehensive ACL check implementation.

The ACL check properly handles all permission cases (CMD, KEY, CHANNEL, NO_SUB_CMD, NO_AUTH).


554-560: LGTM: Clear and correct authentication check logic.

The authentication check properly handles all cases in the correct order:

  1. Disabled users always require authentication
  2. Users with NO_PASS flag don't require authentication
  3. Other users require explicit authentication
src/acl.cc (2)

492-496: LGTM: Blacklist processing for existing limit user.

The implementation correctly processes blacklisted commands by trimming whitespace and disabling them for the limit user.


509-513: LGTM: Password handling logic fixes the authentication issue.

The implementation correctly sets either:

  • nopass when the password is empty, allowing ordinary users to connect without a password
  • The provided password when one is configured

This change directly addresses the PR objective of fixing the ERR NOAUTH Authentication required issue.

Comment on lines 543 to 550
void PikaClientConn::InitUser() {
if (g_pika_conf->requirepass().empty()) {
user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultUser);
}
user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultLimitUser);
authenticated_ = user_->HasFlags(static_cast<uint32_t>(AclUserFlag::NO_PASS)) &&
!user_->HasFlags(static_cast<uint32_t>(AclUserFlag::DISABLED));
}
Copy link

@coderabbitai coderabbitai bot Oct 31, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix logical error in InitUser implementation.

There's a critical bug in the control flow. The requirepass check is ineffective because user_ is always set to DefaultLimitUser regardless of the condition.

Apply this fix to correct the logic:

void PikaClientConn::InitUser() {
  if (g_pika_conf->requirepass().empty()) {
    user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultUser);
+  } else {
+    user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultLimitUser);
  }
-  user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultLimitUser);
  authenticated_ = user_->HasFlags(static_cast<uint32_t>(AclUserFlag::NO_PASS)) &&
                  !user_->HasFlags(static_cast<uint32_t>(AclUserFlag::DISABLED));
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void PikaClientConn::InitUser() {
if (g_pika_conf->requirepass().empty()) {
user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultUser);
}
user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultLimitUser);
authenticated_ = user_->HasFlags(static_cast<uint32_t>(AclUserFlag::NO_PASS)) &&
!user_->HasFlags(static_cast<uint32_t>(AclUserFlag::DISABLED));
}
void PikaClientConn::InitUser() {
if (g_pika_conf->requirepass().empty()) {
user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultUser);
} else {
user_ = g_pika_server->Acl()->GetUserLock(Acl::DefaultLimitUser);
}
authenticated_ = user_->HasFlags(static_cast<uint32_t>(AclUserFlag::NO_PASS)) &&
!user_->HasFlags(static_cast<uint32_t>(AclUserFlag::DISABLED));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

已修改

Copy link

Choose a reason for hiding this comment

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

@buzhimingyonghu 谢谢您的修正!🐰🎉


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

src/acl.cc Outdated
Comment on lines 504 to 508
for (auto& cmd : blacklist) {
cmd = pstd::StringTrim(cmd, " ");
u->SetUser("-" + cmd);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider refactoring duplicated blacklist processing logic.

The blacklist processing logic is duplicated between the two loops. Consider extracting this into a helper method.

+void ProcessBlacklist(const std::vector<std::string>& blacklist, std::shared_ptr<User>& user) {
+  for (auto& cmd : blacklist) {
+    cmd = pstd::StringTrim(cmd, " ");
+    user->SetUser("-" + cmd);
+  }
+}

 void Acl::InitLimitUser(const std::string& bl, bool limit_exist) {
   auto pass = g_pika_conf->userpass();
   std::vector<std::string> blacklist;
   pstd::StringSplit(bl, ',', blacklist);
   std::unique_lock wl(mutex_);
   auto u = GetUser(DefaultLimitUser);
   if (limit_exist) {
     if (!bl.empty()) {
-      for (auto& cmd : blacklist) {
-        cmd = pstd::StringTrim(cmd, " ");
-        u->SetUser("-" + cmd);
-      }
+      ProcessBlacklist(blacklist, u);
       u->SetUser("on");
     }
   } else {
     u->SetUser("on");
     u->SetUser("+@all");
     u->SetUser("~*");
     u->SetUser("&*");
-    for (auto& cmd : blacklist) {
-      cmd = pstd::StringTrim(cmd, " ");
-      u->SetUser("-" + cmd);
-    }
+    ProcessBlacklist(blacklist, u);
   }

Committable suggestion was skipped due to low confidence.

Copy link

@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

🧹 Outside diff range and nitpick comments (2)
src/pika_client_conn.cc (2)

261-261: Enhance comment clarity for RTC interception.

The current comment could be more descriptive about why only these commands are intercepted.

-  // currently we only Intercept: Get, HGet
+  // Only Get and HGet commands are intercepted for RTC cache optimization
+  // as they are the most common read operations

358-368: Add descriptive comment for ACL check logic.

The ACL check implementation would benefit from a clear comment explaining the permission verification process.

+  // Verify user permissions before cache read:
+  // - Check command level permissions
+  // - Check key level access
+  // - Check channel permissions
+  // - Verify sub-command validity
+  // - Confirm authentication status
   int8_t subCmdIndex = -1;
   std::string errKey;
   auto checkRes = user_->CheckUserPermission(c_ptr, argv, subCmdIndex, &errKey);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0054e94 and 62754fa.

📒 Files selected for processing (1)
  • src/pika_client_conn.cc (5 hunks)
🔇 Additional comments (2)
src/pika_client_conn.cc (2)

543-551: LGTM! Implementation correctly fixes the authentication issue.

The InitUser implementation properly addresses the ERR NOAUTH issue by:

  1. Using DefaultUser for passwordless access
  2. Using DefaultLimitUser when password is required
  3. Correctly setting the authenticated state

554-561: LGTM! Improved authentication check logic.

The new AuthRequired implementation follows a clear and logical flow for determining authentication requirements, making the code more maintainable and robust.

Comment on lines +291 to +293
// we don't intercept pipeline batch (argvs.size() > 1)
if (g_pika_conf->rtc_cache_read_enabled() && argvs.size() == 1 && IsInterceptedByRTC(opt) &&
PIKA_CACHE_NONE != g_pika_conf->cache_mode() && !IsInTxn()) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider splitting complex condition for better readability.

The multiple conditions could be split into separate checks with meaningful variable names for better maintainability.

-    if (g_pika_conf->rtc_cache_read_enabled() && argvs.size() == 1 && IsInterceptedByRTC(opt) &&
-        PIKA_CACHE_NONE != g_pika_conf->cache_mode() && !IsInTxn()) {
+    bool is_cache_enabled = g_pika_conf->rtc_cache_read_enabled();
+    bool is_single_command = argvs.size() == 1;
+    bool is_cacheable_command = IsInterceptedByRTC(opt);
+    bool has_valid_cache_mode = PIKA_CACHE_NONE != g_pika_conf->cache_mode();
+    bool not_in_transaction = !IsInTxn();
+    
+    if (is_cache_enabled && is_single_command && is_cacheable_command && 
+        has_valid_cache_mode && not_in_transaction) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// we don't intercept pipeline batch (argvs.size() > 1)
if (g_pika_conf->rtc_cache_read_enabled() && argvs.size() == 1 && IsInterceptedByRTC(opt) &&
PIKA_CACHE_NONE != g_pika_conf->cache_mode() && !IsInTxn()) {
// we don't intercept pipeline batch (argvs.size() > 1)
bool is_cache_enabled = g_pika_conf->rtc_cache_read_enabled();
bool is_single_command = argvs.size() == 1;
bool is_cacheable_command = IsInterceptedByRTC(opt);
bool has_valid_cache_mode = PIKA_CACHE_NONE != g_pika_conf->cache_mode();
bool not_in_transaction = !IsInTxn();
if (is_cache_enabled && is_single_command && is_cacheable_command &&
has_valid_cache_mode && not_in_transaction) {

@buzhimingyonghu
Copy link
Contributor Author

@Mixficsol Mixficsol changed the title fix user ERR NOAUTH Authentication required fix: fix user ERR NOAUTH Authentication required Oct 31, 2024
Copy link

@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

🧹 Outside diff range and nitpick comments (1)
src/pika_client_conn.cc (1)

291-293: Consider splitting complex condition for better readability.

The multiple conditions could be split into separate checks with meaningful variable names for better maintainability.

-    if (g_pika_conf->rtc_cache_read_enabled() && argvs.size() == 1 && IsInterceptedByRTC(opt) &&
-        PIKA_CACHE_NONE != g_pika_conf->cache_mode() && !IsInTxn()) {
+    bool is_cache_enabled = g_pika_conf->rtc_cache_read_enabled();
+    bool is_single_command = argvs.size() == 1;
+    bool is_cacheable_command = IsInterceptedByRTC(opt);
+    bool has_valid_cache_mode = PIKA_CACHE_NONE != g_pika_conf->cache_mode();
+    bool not_in_transaction = !IsInTxn();
+    
+    if (is_cache_enabled && is_single_command && is_cacheable_command && 
+        has_valid_cache_mode && not_in_transaction) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 62754fa and e643c87.

📒 Files selected for processing (2)
  • src/acl.cc (1 hunks)
  • src/pika_client_conn.cc (5 hunks)
🔇 Additional comments (4)
src/pika_client_conn.cc (3)

543-551: LGTM! Clean implementation of InitUser.

The implementation correctly initializes the user based on the blacklist configuration and sets the authentication state based on user flags.


554-564: LGTM! Well-structured authentication checks.

The AuthRequired method has been improved with clear, early returns making the authentication logic more readable and maintainable.


358-365: LGTM! Proper ACL permission checks added.

The implementation correctly checks ACL permissions and returns early if the checks fail, maintaining security.

src/acl.cc (1)

499-499: LGTM: Password handling is consistent.

The password handling logic is consistent between both paths (existing and new limit user). The code correctly sets the password using the > prefix to indicate a plaintext password that should be hashed.

Also applies to: 505-505

Comment on lines +492 to 496
for (auto& cmd : blacklist) {
cmd = pstd::StringTrim(cmd, " ");
u->SetUser("-" + cmd);
}
u->SetUser("on");
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor duplicated blacklist processing logic.

The blacklist processing logic is duplicated between the two code blocks. Consider extracting this into a helper method to improve maintainability and reduce code duplication.

Apply this diff to extract the common logic:

+void ProcessBlacklist(std::shared_ptr<User>& user, const std::vector<std::string>& blacklist) {
+  for (auto& cmd : blacklist) {
+    cmd = pstd::StringTrim(cmd, " ");
+    user->SetUser("-" + cmd);
+  }
+}

 void Acl::InitLimitUser(const std::string& bl, bool limit_exist) {
   auto pass = g_pika_conf->userpass();
   std::vector<std::string> blacklist;
   pstd::StringSplit(bl, ',', blacklist);
   std::unique_lock wl(mutex_);
   auto u = GetUser(DefaultLimitUser);
   if (limit_exist) {
     if (!bl.empty()) {
-      for (auto& cmd : blacklist) {
-        cmd = pstd::StringTrim(cmd, " ");
-        u->SetUser("-" + cmd);
-      }
+      ProcessBlacklist(u, blacklist);
       u->SetUser("on");
     }
     if (!pass.empty()) {
       u->SetUser(">" + pass);
     }
   } else {
     if (pass.empty()) {
       u->SetUser("nopass");
     } else {
       u->SetUser(">" + pass);
     }
     u->SetUser("on");
     u->SetUser("+@all");
     u->SetUser("~*");
     u->SetUser("&*");
-    for (auto& cmd : blacklist) {
-      cmd = pstd::StringTrim(cmd, " ");
-      u->SetUser("-" + cmd);
-    }
+    ProcessBlacklist(u, blacklist);
   }
 }

Also applies to: 512-515

Copy link

@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: 3

🧹 Outside diff range and nitpick comments (6)
tools/pika_exporter/exporter/metrics/parser.go (4)

33-57: Optimize CheckContainsEmptyValue method in VersionChecker336

To improve the performance of the CheckContainsEmptyValue method, consider using a map[string]struct{} for the EmptyValueName field. This change reduces the lookup time from O(n) to O(1).

Apply this diff to optimize the lookup:

 type VersionChecker336 struct {
-	EmptyValueName []string
+	EmptyValueName map[string]struct{}
 }

 func (v *VersionChecker336) CheckContainsEmptyValue(key string) bool {
 	if v.EmptyValueName == nil {
-		v.EmptyValueName = []string{
+		v.EmptyValueName = map[string]struct{}{
-			"instantaneous_output_repl_kbps",
+			"instantaneous_output_repl_kbps": {},
 			"total_net_output_bytes":                    {},
 			"cache_db_num":                              {},
 			"hits_per_sec":                              {},
 			"cache_status":                              {},
 			"total_net_input_bytes":                     {},
 			"instantaneous_output_kbps":                 {},
 			"instantaneous_input_kbps":                  {},
 			"total_net_repl_input_bytes":                {},
 			"instantaneous_input_repl_kbps":             {},
 			"slow_logs_count":                           {},
 			"total_net_repl_output_bytes":               {},
 			"cache_memory":                              {},
 		}
 	}
-	for _, str := range v.EmptyValueName {
-		if str == key {
+	if _, exists := v.EmptyValueName[key]; exists {
 		return true
 	}
 	return false
 }

63-78: Optimize CheckContainsEmptyValue method in VersionChecker355

Similar to VersionChecker336, using a map[string]struct{} for EmptyValueName will enhance performance by providing constant-time lookups.

Apply this diff to optimize the method:

 type VersionChecker355 struct {
-	EmptyValueName []string
+	EmptyValueName map[string]struct{}
 }

 func (v *VersionChecker355) CheckContainsEmptyValue(key string) bool {
 	if v.EmptyValueName == nil {
-		v.EmptyValueName = []string{
+		v.EmptyValueName = map[string]struct{}{
-			"cache_db_num",
+			"cache_db_num":    {},
 			"cache_status":    {},
 			"cache_memory":    {},
 			"hits_per_sec":    {},
 		}
 	}
-	for _, str := range v.EmptyValueName {
-		if str == key {
+	if _, exists := v.EmptyValueName[key]; exists {
 		return true
 	}
 	return false
 }

84-99: Optimize CheckContainsEmptyValue method in VersionChecker350

Again, implementing a map[string]struct{} for EmptyValueName enhances lookup efficiency.

Apply this diff to optimize the method:

 type VersionChecker350 struct {
-	EmptyValueName []string
+	EmptyValueName map[string]struct{}
 }

 func (v *VersionChecker350) CheckContainsEmptyValue(key string) bool {
 	if v.EmptyValueName == nil {
-		v.EmptyValueName = []string{
+		v.EmptyValueName = map[string]struct{}{
-			"cache_db_num",
+			"cache_db_num":    {},
 			"cache_status":    {},
 			"cache_memory":    {},
 			"hits_per_sec":    {},
 			"slow_logs_count": {},
 		}
 	}
-	for _, str := range v.EmptyValueName {
-		if str == key {
+	if _, exists := v.EmptyValueName[key]; exists {
 		return true
 	}
 	return false
 }

309-309: Redundant empty line

The added empty line at line 309 is unnecessary and can be removed to maintain code consistency and readability.

tools/pika_exporter/exporter/pika.go (2)

267-282: Enhance version handling in selectversion function

The selectversion function currently handles specific version strings explicitly. This approach may not scale well with future versions and could lead to maintenance challenges. Consider using semantic version parsing to handle version comparisons more robustly, supporting version ranges and defaulting to the appropriate VersionChecker implementations.

Apply the following changes to improve version handling:

+import (
+    "github.com/blang/semver"
+)

 func selectversion(versionStr string) metrics.VersionChecker {
-    var v metrics.VersionChecker
-
-    switch version {
-    case "3.3.6":
-        v = &metrics.VersionChecker336{}
-    case "3.5.5":
-        v = &metrics.VersionChecker355{}
-    case "3.5.0":
-        v = &metrics.VersionChecker350{}
-    default:
-        return nil
-    }
-    return v
+    version, err := semver.Parse(versionStr)
+    if err != nil {
+        log.Warnf("Invalid version format: %s", versionStr)
+        return nil
+    }
+
+    if version.Major == 3 {
+        if version.Minor == 3 && version.Patch >= 6 {
+            return &metrics.VersionChecker336{}
+        } else if version.Minor == 5 {
+            if version.Patch >= 5 {
+                return &metrics.VersionChecker355{}
+            } else if version.Patch >= 0 {
+                return &metrics.VersionChecker350{}
+            }
+        }
+    }
+
+    log.Warnf("Unsupported Pika version: %s", versionStr)
+    return nil
 }

This refactor:

  • Utilizes the semver package for semantic version parsing.
  • Supports version ranges, allowing for easier future maintenance.
  • Logs a warning when an unsupported or invalid version is encountered.

Remember to update your go.mod file to include the github.com/blang/semver package.


267-282: Use consistent naming conventions for function names

In Go, the convention is to use mixedCaps (camelCase) for function names. The function selectversion should be renamed to selectVersion to follow Go naming conventions, improving code readability and consistency.

Apply this diff:

-func selectversion(versionStr string) metrics.VersionChecker {
+func selectVersion(versionStr string) metrics.VersionChecker {
     // function body
 }

Also, update all references to this function:

 parseOpt := metrics.ParseOption{
     Version:        version,
     Extracts:       extracts,
     Info:           info,
-    CurrentVersion: selectversion(version.Original()),
+    CurrentVersion: selectVersion(version.Original()),
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 19c1c8b and 93e6e6d.

📒 Files selected for processing (2)
  • tools/pika_exporter/exporter/metrics/parser.go (4 hunks)
  • tools/pika_exporter/exporter/pika.go (2 hunks)
🔇 Additional comments (3)
tools/pika_exporter/exporter/metrics/parser.go (3)

20-25: Addition of CurrentVersion field in ParseOption struct

The introduction of the CurrentVersion field to the ParseOption struct allows for version-specific parsing logic, enhancing the flexibility of the parser.


26-28: Implementation of VersionChecker interface

Defining the VersionChecker interface provides a clean abstraction for version-specific checks, improving code maintainability.


191-193: Conditional logging in regexParser

The conditional logging appropriately checks if the CurrentVersion is nil or if it does not contain the empty value, ensuring that warnings are logged only when necessary.

Comment on lines 29 to 99
type VersionChecker336 struct {
EmptyValueName []string
}

func (v *VersionChecker336) CheckContainsEmptyValue(key string) bool {
if v.EmptyValueName == nil {
v.EmptyValueName = []string{
"instantaneous_output_repl_kbps",
"total_net_output_bytes",
"cache_db_num",
"hits_per_sec",
"cache_status",
"total_net_input_bytes",
"instantaneous_output_kbps",
"instantaneous_input_kbps",
"total_net_repl_input_bytes",
"instantaneous_input_repl_kbps",
"slow_logs_count",
"total_net_repl_output_bytes",
"cache_memory",
}
}
for _, str := range v.EmptyValueName {
if str == key {
return true
}
}
return false
}

type VersionChecker355 struct {
EmptyValueName []string
}

func (v *VersionChecker355) CheckContainsEmptyValue(key string) bool {
if v.EmptyValueName == nil {
v.EmptyValueName = []string{
"cache_db_num",
"cache_status",
"cache_memory",
"hits_per_sec",
}
}
for _, str := range v.EmptyValueName {
if str == key {
return true
}
}
return false
}

type VersionChecker350 struct {
EmptyValueName []string
}

func (v *VersionChecker350) CheckContainsEmptyValue(key string) bool {
if v.EmptyValueName == nil {
v.EmptyValueName = []string{
"cache_db_num",
"cache_status",
"cache_memory",
"hits_per_sec",
"slow_logs_count",
}
}
for _, str := range v.EmptyValueName {
if str == key {
return true
}
}
return false
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor to eliminate code duplication among VersionChecker implementations

The three VersionChecker types have similar structures and methods. Consider refactoring to a single VersionChecker type with parameterized empty value names to reduce code duplication and improve maintainability.

Here's how you might refactor the code:

type VersionChecker struct {
	EmptyValueName map[string]struct{}
}

func NewVersionChecker(emptyValueNames []string) *VersionChecker {
	emptyValueMap := make(map[string]struct{})
	for _, name := range emptyValueNames {
		emptyValueMap[name] = struct{}{}
	}
	return &VersionChecker{EmptyValueName: emptyValueMap}
}

func (v *VersionChecker) CheckContainsEmptyValue(key string) bool {
	if v.EmptyValueName == nil {
		return false
	}
	_, exists := v.EmptyValueName[key]
	return exists
}

// Initialize VersionCheckers for different versions
var VersionChecker336 = NewVersionChecker([]string{
	"instantaneous_output_repl_kbps",
	"total_net_output_bytes",
	// ... other names
})

var VersionChecker355 = NewVersionChecker([]string{
	"cache_db_num",
	"cache_status",
	// ... other names
})

var VersionChecker350 = NewVersionChecker([]string{
	"cache_db_num",
	"cache_status",
	// ... other names
})

Comment on lines 252 to 256
if opt.CurrentVersion == nil || !opt.CurrentVersion.CheckContainsEmptyValue(m.ValueName) {
log.Warnf("normalParser::Parse not found value. metricName:%s valueName:%s", m.Name, m.ValueName)
}
return

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unintended early return in normalParser

The unconditional return statement at line 255 may prevent further parsing after logging the warning. If the intention is to skip processing only when the value is genuinely not found, consider moving the return inside the conditional block.

Apply this diff to adjust the control flow:

 if m.ValueName != "" {
 	if v, ok := findInMap(m.ValueName, opt.Extracts); !ok {
 		if opt.CurrentVersion == nil || !opt.CurrentVersion.CheckContainsEmptyValue(m.ValueName) {
 			log.Warnf("normalParser::Parse not found value. metricName:%s valueName:%s", m.Name, m.ValueName)
 		}
+		return
 	} else {
 		metric.Value = convertToFloat64(v)
 	}
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 255 to 258
Version: version,
Extracts: extracts,
Info: info,
CurrentVersion: selectversion(version.Original()),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential nil dereference due to unhandled nil CurrentVersion

In the collectInfo function, the ParseOption struct now includes CurrentVersion, which is assigned via selectversion(version.Original()). If selectversion returns nil (which happens when an unsupported version is encountered), this could lead to a nil pointer dereference when CurrentVersion methods are called later in the code. To prevent this, ensure that you check for nil and handle unsupported versions appropriately.

Apply the following changes to handle the nil case:

 func (e *exporter) collectInfo(c *client, ch chan<- prometheus.Metric) error {
     // update info config
     if err := LoadConfig(); err != nil {
         log.Errorln("load config failed:", err)
         return err
     }
     info, err := c.GetInfo()
     if err != nil {
         return err
     }

     version, extracts, err := parseInfo(info)
     if err != nil {
         return err
     }
     extracts[metrics.LabelNameAddr] = c.Addr()
     extracts[metrics.LabelNameAlias] = c.Alias()
     extracts[metrics.LabelInstanceMode], err = c.InstanceModeInfo()
     if err != nil {
         return err
     }
     extracts[metrics.LabelConsensusLevel], err = c.LabelConsensusLevelInfo()
     if err != nil {
         return err
     }
     collector := metrics.CollectFunc(func(m metrics.Metric) error {
         promMetric, err := prometheus.NewConstMetric(
             prometheus.NewDesc(prometheus.BuildFQName(e.namespace, "", m.Name), m.Help, m.Labels, nil),
             m.MetricsType(), m.Value, m.LabelValues...)
         if err != nil {
             return err
         }

         ch <- promMetric
         return nil
     })
     parseOpt := metrics.ParseOption{
         Version:        version,
         Extracts:       extracts,
         Info:           info,
         CurrentVersion: selectversion(version.Original()),
     }
+    if parseOpt.CurrentVersion == nil {
+        log.Errorf("Unsupported Pika version: %s", version.Original())
+        return fmt.Errorf("unsupported Pika version: %s", version.Original())
+    }
     for _, m := range metrics.MetricConfigs {
         m.Parse(m, collector, parseOpt)
     }

     return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Version: version,
Extracts: extracts,
Info: info,
CurrentVersion: selectversion(version.Original()),
Version: version,
Extracts: extracts,
Info: info,
CurrentVersion: selectversion(version.Original()),
}
if parseOpt.CurrentVersion == nil {
log.Errorf("Unsupported Pika version: %s", version.Original())
return fmt.Errorf("unsupported Pika version: %s", version.Original())
}

}
} else {
if (pass.empty()) {
u->SetUser("nopass");
} else {
u->SetUser(">"+pass);
u->SetUser(">" + pass);
Copy link
Collaborator

Choose a reason for hiding this comment

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

别的地方没啥问题, 就是这里和Acl::UpdateDefaultUserPassword这个函数里,要多次判断和设置密码

@Mixficsol Mixficsol merged commit 8095602 into OpenAtomFoundation:unstable Nov 22, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
☢️ Bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants