Skip to content

Conversation

@zstack-robot-2
Copy link
Collaborator

GlobalPropertyImpact
APIImpact
DBImpact

Resolves: ZSTAC-79223

Change-Id: I7771696566617172666f776a797a756672767774

sync from gitlab !9030

@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Walkthrough

新增 ACL 条目字段 redirectPort 的持久化与传递;扩展 ACL API/Inventory/VO;将向负载均衡添加 ACL 的处理改为 FlowChain 并在添加时设置/同步 redirectPort;启动时提供缺失 redirectPort 的升级逻辑;虚拟路由后端与集成测试覆盖相应变化。

Changes

Cohort / File(s) 变更摘要
数据库架构
conf/db/upgrade/V5.5.6__schema.sql
在 AccessControlListEntry 表新增可空整数列 redirectPort
ACL VO / Inventory / 元模型
plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java, .../AccessControlListEntryInventory.java, .../AccessControlListEntryVO_.java
在 VO、Inventory、元模型中添加 redirectPort 字段及 getter/setter;Inventory 构造函数从 VO 初始化该字段
ACL API / 文档 / 示例
plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java, .../APIAddAccessControlListEntryEvent.java, .../APIChangeAccessControlListRedirectRuleEvent.java, plugin/acl/src/main/resources/.../APIAddAccessControlListRedirectRuleMsgDoc_zh_cn.groovy
在 APIAddAccessControlListRedirectRuleMsg 增加可选参数 redirectPort(1–65535)与访问器;更新示例事件/文档示例值为 8080
ACL 管理逻辑
plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java
在创建重定向 ACL 条目时,将入参的 redirectPort 赋值到新建的 VO 上
负载均衡:FlowChain 与添加 ACL 流程
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
将添加 ACL 到 LB 的处理重构为 FlowChain,新增 doAddAccessControlListToLoadBalancer():持久化 ACL refs、将 ACL 条目 redirectPort 更新为 listener 的 instancePort,并按需触发 listener 刷新
LB 升级逻辑与开关
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java, .../LoadBalancerGlobalProperty.java
新增 upgradeLoadBalancerRedirectRule() 并在 start() 调用以填充缺失的 redirectPort;新增全局属性 UPGRADE_LB_REDIRECT_RULE
虚拟路由后端 DTO
plugin/virtualRouterProvider/src/main/java/.../VirtualRouterLoadBalancerBackend.java
在 LbTO.RedirectRule 中添加 redirectPort 字段及 getter/setter;构建后端重定向规则时传入该字段
集成测试
test/src/test/groovy/org/zstack/test/integration/.../VirtualRouterLoadBalancerForwardRuleCase.groovy
新增综合测试:验证 ACL 重定向规则创建、后端命令包含 redirectPort、以及升级逻辑将 null 填充为 listener 端口(示例 8080)

序列图

sequenceDiagram
    participant Client
    participant LBBase as LoadBalancerBase
    participant ACLMgr as ACLManager
    participant DB as Database
    participant Listener as LBListener
    participant VR as VRBackend

    Client->>LBBase: APIAddAccessControlListToLoadBalancerMsg
    LBBase->>LBBase: doAddAccessControlListToLoadBalancer()
    LBBase->>ACLMgr: 查询/准备 ACL 条目
    ACLMgr->>DB: 读取/持久化 ACL 条目
    DB-->>ACLMgr: 返回条目
    ACLMgr-->>LBBase: ACL 条目列表

    LBBase->>Listener: 获取 listener.instancePort
    Listener-->>LBBase: 返回 instancePort

    LBBase->>DB: 更新 ACL 条目 redirectPort=instancePort
    DB-->>LBBase: 确认持久化

    LBBase->>VR: 发送 RefreshLoadBalancerMsg (包含 LbTO, redirectPort)
    VR-->>LBBase: 后端配置确认

    LBBase-->>Client: APIAddAccessControlListToLoadBalancerEvent
Loading
sequenceDiagram
    participant System
    participant LBMgr as LoadBalancerManagerImpl
    participant DB as Database
    participant Listener as LBListener

    System->>LBMgr: start()
    LBMgr->>LBMgr: upgradeLoadBalancerRedirectRule()

    alt UPGRADE_LB_REDIRECT_RULE enabled
        LBMgr->>DB: 查询 redirectPort IS NULL 的 ACL 条目
        DB-->>LBMgr: 返回条目列表
        loop 对每个条目
            LBMgr->>DB: 通过 ACL refs 载入关联的 LoadBalancerListener
            DB-->>LBMgr: 返回 listener (含 instancePort)
            LBMgr->>LBMgr: 设置 redirectPort = instancePort
            LBMgr->>DB: 持久化更新的 ACL 条目
        end
    end

    LBMgr-->>System: 升级完成
Loading

估计代码审查工作量

🎯 4 (复杂) | ⏱️ ~60 分钟

诗歌

🐰 我在代码地里挖个洞,
新端口跳进了 ACL 的篓,
链式流程把步骤串成行,
刷新命令把端口带到后端,
负载均衡与我一起欢蹦跳! 🎉


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)
Check name Status Explanation Resolution
Title check ❌ Error 标题格式不符合要求。应为 '[scope]: ' 格式,但提供的标题为 '[lb]: feature-lb-forwarding-rules',其中描述部分 'feature-lb-forwarding-rules' 使用了不规范的连字符且信息不够清晰。 重新格式化标题为 '[lb]: Add redirect port support for ACL load balancer rules' 或类似的描述,使标题更清晰地说明主要功能。
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed 拉取请求描述与变更集相关,提及了全局属性、API和数据库的影响,以及关联的问题号 ZSTAC-79223。
✨ Finishing touches
  • 📝 Generate docstrings

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java (1)

860-876: formatRedirectRules 中遗漏了 redirectPort 的拷贝

在创建 formatRule 时,复制了 redirectRuleredirectRuleUuidaclUuidserverGroupUuid,但遗漏了新增的 redirectPort 字段。这会导致经过 formatRedirectRules 处理的规则丢失重定向端口配置。

🐛 建议修复
 for (LbTO.RedirectRule rule : redirectRules) {
     LbTO.RedirectRule formatRule = new LbTO.RedirectRule();
     formatRule.setRedirectRule(rule.getRedirectRule());
     formatRule.setRedirectRuleUuid(rule.getRedirectRuleUuid());
     formatRule.setAclUuid(rule.getAclUuid());
     formatRule.setServerGroupUuid(rule.getServerGroupUuid());
+    formatRule.setRedirectPort(rule.getRedirectPort());

     String matchMethod = Q.New(AccessControlListEntryVO.class).select(AccessControlListEntryVO_.matchMethod).eq(AccessControlListEntryVO_.uuid, rule.getRedirectRuleUuid()).findValue();
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java (1)

2026-2120: 建议对空集合持久化与重复输入做保护,避免边界场景异常/重复数据

这里 refs 在某些情况下可能为空(例如重复添加相同 ACL+serverGroup),建议避免对空 refs 调用 dbf.persistCollection(refs);同时对 aclUuids/serverGroupUuidsdistinct() 可减少重复 ref/重复 update。

Proposed diff (guard empty persist + dedupe inputs)
         public void run(FlowTrigger trigger, Map data) {
             List<LoadBalancerListenerACLRefVO> refs = new ArrayList<>();
+            List<String> distinctAclUuids = msg.getAclUuids().stream().distinct().collect(Collectors.toList());
             if (msg.getServerGroupUuids() != null && !msg.getServerGroupUuids().isEmpty()) {
-                for (String aclUuid : msg.getAclUuids()) {
+                List<String> distinctServerGroupUuids = msg.getServerGroupUuids().stream().distinct().collect(Collectors.toList());
+                for (String aclUuid : distinctAclUuids) {
                     List<String> sgUuids = lblVo.getAclRefs().stream().filter(ref -> ref.getAclUuid().equals(aclUuid))
                             .map(LoadBalancerListenerACLRefVO::getServerGroupUuid).collect(Collectors.toList());
-                    List<String> serverGroupUuids = msg.getServerGroupUuids().stream().filter(sgUuid -> !sgUuids.contains(sgUuid)).collect(Collectors.toList());
-                    for (String sgUuid : serverGroupUuids) {
+                    for (String sgUuid : distinctServerGroupUuids.stream().filter(sgUuid -> !sgUuids.contains(sgUuid)).collect(Collectors.toList())) {
                         LoadBalancerListenerACLRefVO ref = new LoadBalancerListenerACLRefVO();
                         ref.setAclUuid(aclUuid);
                         ref.setType(LoadBalancerAclType.valueOf(msg.getAclType()));
                         ref.setListenerUuid(msg.getListenerUuid());
                         ref.setServerGroupUuid(sgUuid);
                         refs.add(ref);
                     }
                 }
             } else {
-                refs = msg.getAclUuids().stream().map(aclUuid -> {
+                refs = distinctAclUuids.stream().map(aclUuid -> {
                     LoadBalancerListenerACLRefVO ref = new LoadBalancerListenerACLRefVO();
                     ref.setAclUuid(aclUuid);
                     ref.setType(LoadBalancerAclType.valueOf(msg.getAclType()));
                     ref.setListenerUuid(msg.getListenerUuid());
                     return ref;
                 }).collect(Collectors.toList());
             }
-            dbf.persistCollection(refs);
+            if (!refs.isEmpty()) {
+                dbf.persistCollection(refs);
+            }

             Integer instancePort = lblVo.getInstancePort();
-            msg.getAclUuids().forEach(aclUuid -> {
+            distinctAclUuids.forEach(aclUuid -> {
                 SQL.New(AccessControlListEntryVO.class)
                         .eq(AccessControlListEntryVO_.aclUuid, aclUuid)
                         .eq(AccessControlListEntryVO_.type, AclEntryType.RedirectRule.toString())
                         .isNull(AccessControlListEntryVO_.redirectPort)
                         .set(AccessControlListEntryVO_.redirectPort, instancePort)
                         .update();
             });

Based on learnings, 尽量保持同步/Cherry-pick 改动面最小,上面建议也可以只做“空集合保护”这一条。

🤖 Fix all issues with AI agents
In
`@test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy`:
- Around line 180-189: The test sets the global static flag
LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE but never restores it,
risking cross-test interference; update the test() method (and the other
occurrence around lines 327-330) to capture the original value before modifying,
set the flag to true for the upgrade step, and ensure you restore the original
value in a finally block (or after env.create) so
LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE is always reset regardless
of test outcome.
- Around line 191-297: The test may NPE when using cmd after attaching acl2
because the refresh callback might not have run; add an assertion that cmd !=
null before accessing cmd.lbs after creating acl2 and attaching it (i.e., insert
assert cmd != null right before "lbTO = cmd.lbs.find { it.lbUuid == lb1.uuid
}"), ensuring the RefreshLbCmd cmd captured by the
env.afterSimulator(VirtualRouterLoadBalancerBackend.REFRESH_LB_PATH) handler is
present.
🧹 Nitpick comments (3)
plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventoryDoc_zh_cn.groovy (2)

52-57: 文档字段 redirectPort 缺少描述

新增的 redirectPort 字段的 desc 为空字符串,应添加有意义的描述以便生成完整的 API 文档。

建议修改
 	field {
 		name "redirectPort"
-		desc ""
+		desc "重定向端口"
 		type "Integer"
 		since "5.5.6"
 	}

3-4: 重复导入 java.sql.Timestamp

Timestamp 被导入了两次,请删除重复的导入语句。

建议修改
 import java.sql.Timestamp
-import java.sql.Timestamp
plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java (1)

335-340: 预先存在的逻辑问题:handleLocalMessage 中缺少 return 语句

当消息是 DeleteAccessControlListMsg 类型时,处理完成后会继续执行到 bus.dealWithUnknownMessage(msg),这会导致已知消息被当作未知消息处理。

虽然这不是本次 PR 引入的问题,但建议修复以避免潜在的问题。

建议修改
 protected void handleLocalMessage(Message msg) {
     if (msg instanceof DeleteAccessControlListMsg) {
         handle((DeleteAccessControlListMsg)msg);
+        return;
     }
     bus.dealWithUnknownMessage(msg);
 }
📜 Review details

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b28558a and d3c9981.

⛔ Files ignored due to path filters (2)
  • sdk/src/main/java/org/zstack/sdk/AccessControlListEntryInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/AddAccessControlListRedirectRuleAction.java is excluded by !sdk/**
📒 Files selected for processing (14)
  • conf/db/upgrade/V5.5.6__schema.sql
  • plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventoryDoc_zh_cn.groovy
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
  • plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java
  • test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy
🧰 Additional context used
📓 Path-based instructions (3)
**/*.*

⚙️ CodeRabbit configuration file

**/*.*: - 代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写

Files:

  • plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventoryDoc_zh_cn.groovy
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy
  • conf/db/upgrade/V5.5.6__schema.sql
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: ## 1. API 设计要求

  • API 命名:
    • API 名称必须唯一,不能重复。
    • API 消息类需要继承 APIMessage;其返回类必须继承 APIReplyAPIEvent,并在注释中用 @RestResponse 进行标注。
    • API 消息上必须添加注解 @RestRequest,并满足如下规范:
      • path:
        • 针对资源使用复数形式。
        • 当 path 中引用消息类变量时,使用 {variableName} 格式。
      • HTTP 方法对应:
        • 查询操作 → HttpMethod.GET
        • 更新操作 → HttpMethod.PUT
        • 创建操作 → HttpMethod.POST
        • 删除操作 → HttpMethod.DELETE
    • API 类需要实现 __example__ 方法以便生成 API 文档,并确保生成对应的 Groovy API Template 与 API Markdown 文件。

2. 命名与格式规范

  • 类名:

    • 使用 UpperCamelCase 风格。
    • 特殊情况:
      • VO/AO/EO 类型类除外。
      • 抽象类采用 AbstractBase 前缀/后缀。
      • 异常类应以 Exception 结尾。
      • 测试类需要以 TestCase 结尾。
  • 方法名、参数名、成员变量和局部变量:

    • 使用 lowerCamelCase 风格。
  • 常量命名:

    • 全部大写,使用下划线分隔单词。
    • 要求表达清楚,避免使用含糊或不准确的名称。
  • 包名:

    • 统一使用小写,使用点分隔符,每个部分应是一个具有自然语义的英文单词(参考 Spring 框架的结构)。
  • 命名细节:

    • 避免在父子类或同一代码块中出现相同名字的成员或局部变量,防止混淆。
    • 命名缩写:
      • 不允许使用不必要的缩写,如:AbsSchedulerJobcondiFu 等。应使用完整单词提升可读性。

3. 编写自解释代码

  • 意图表达:

    • 避免使用布尔型参数造成含义不明确。例如:
      • 对于 stopAgent(boolean ignoreError),建议拆分为不同函数(如 stopAgentIgnoreError()),或使用枚举表达操作类型。
    • 命名应尽量用完整的单词组合表达意图,并在名称中体现数据类型或用途(例如在常量与变量名称中,将类型词放在末尾)。
    • 避免使用魔法值(Magic Value):
      • 直接使用未经定义的数值或字符串(如 if (status == 5))应替换为枚举或常量。

      • 示例:

      • // 错误示例:魔法值

      • if (user.getStatus() == 5) { ... }

      • // 正确示例:常量或枚举

      • public static final int STATUS_ACTIVE = 5;

      • if (user.getStatus() == STATUS_ACTIVE) { ... }

      • // 或使用枚举

      • enum UserStatus { ACTIVE, INACTIVE }

  • 注释:

    • 代码应尽量做到自解释,对少于两行的说明可以直接写在代码中。
    • 对于较长的注释,需要仔细校对并随代码更新,确保内容正确。
    • 接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。

4. 流程控制和结构优化

  • if...else 的使用:

    • 应尽量减少 if...else 结构的使用,建议:
      • 限制嵌套层级最多为两层,且内层不应再出现 else 分支。
      • 尽早返回(Early Return),将条件判断中的处理逻辑提前结束或抽成独立方法。
      • 使用 Java Stream 或 Lambda 表达式代替冗长的循环与条件判断。
  • 条件判断:

    • if 条件表达不宜过长或过于复杂,必要时可以将条件抽成 boolean 变量描述。
  • 代码块长度:
    ...

Files:

  • plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
**/*.sql

⚙️ CodeRabbit configuration file

**/*.sql: - Review the SQL code, make sure has no errors and confirm that:

  • Upgrading scene has been carefully handled
  • Do not use DEFAULT 0000-00-00 00:00:00 , use DEFAULT CURRENT_TIMESTAMP instead
  • When NOT NULL exists, must use stored procedure or other functions to process historical data, this is very very important
  • 数据库记录中,如果字符串长度不可控,不要用 vchar,用 text 类型

Files:

  • conf/db/upgrade/V5.5.6__schema.sql
🧠 Learnings (1)
📓 Common learnings
Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 2360
File: network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java:449-490
Timestamp: 2025-08-04T04:48:19.103Z
Learning: ZStack项目在cherry-pick操作中,即使发现了性能优化机会(如IP地址批量保存的内存优化),也严格遵循不做额外修改的政策,优先保证cherry-pick的完整性和一致性。
🧬 Code graph analysis (1)
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java (2)
plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java (1)
  • RedirectRule (346-391)
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerConstants.java (1)
  • LoadBalancerConstants (15-180)
🔍 Remote MCP AtlassianMCP

Perfect! Now I have gathered comprehensive information. Let me compile the summary with proper citations.

Summary of Additional Context for PR Review

Based on the gathered information, here is the relevant context for reviewing this pull request:

Feature Background and Objectives

This PR resolves ZSTAC-79223, which is a P0 priority feature for ZStack v5.5.6 that enables load balancer listeners to support forwarding rules configured by domain names to direct traffic to different hosts on different ports.

The feature addresses a customer requirement where frontend access is unified to address and port 10.204.20.3:443, and the system needs to support configuring domain name forwarding rules to route to different hosts on different ports. Previously, users faced limitations where load balancer forwarding rules could only route to backend server groups without the ability to configure corresponding backend listening ports, and creating multiple listeners would result in "port conflict" errors since frontend access ports are all on port 443.

The target implementation approach is to support adding a "manual configuration of backend server group specified port" when adding forwarding rules (referred to as "方案二" - Solution 2 in the feature spec).

Implementation Strategy

The PR implements this feature by:

  1. Database Schema Change: Adds a nullable redirectPort integer column to the AccessControlListEntryVO table for storing the port configuration.

  2. API Enhancement: Introduces a new redirectPort parameter to APIAddAccessControlListRedirectRuleMsg with validation constraints (port range 1-65535).

  3. Load Balancer Integration: In LoadBalancerBase, implements a new doAddAccessControlListToLoadBalancer method that uses a FlowChain-based approach to:

    • Record ACL references
    • Update redirect ports for ACL entries to match the listener's instance port
    • Conditionally refresh the load balancer listener when needed
  4. Upgrade Mechanism: Adds a upgradeLoadBalancerRedirectRule() method in LoadBalancerManagerImpl with a global property flag (UPGRADE_LB_REDIRECT_RULE) to handle migration of existing redirect rules by assigning the listener port to rules missing a redirectPort value during system startup.

  5. Virtual Router Backend: Propagates the redirectPort field from ACL entries to the virtual router's load balancer transmission objects (LbTO.RedirectRule).

  6. Test Coverage: Includes a comprehensive test case VirtualRouterLoadBalancerForwardRuleCase that validates redirect port functionality and the upgrade mechanism.

Key Technical Details

  • The assignee is 刘鹏超 (Pengchao Liu) with time estimate of 80 hours (80000 seconds remaining time)
  • The changes maintain backward compatibility through the upgrade mechanism for existing redirect rules
  • All changes follow the established patterns in the codebase for data model extensions and API parameter additions,,
🔇 Additional comments (16)
conf/db/upgrade/V5.5.6__schema.sql (1)

1-1: LGTM!

数据库升级脚本正确地添加了可为空的 redirectPort 列。由于没有 NOT NULL 约束,现有记录将自动获得 NULL 值,无需额外处理历史数据。升级逻辑由 LoadBalancerManagerImpl.upgradeLoadBalancerRedirectRule() 方法在启动时处理。

plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java (1)

28-38: 示例可能存在不一致

APIAddAccessControlListEntryEvent 对应的是添加 IP 条目(IpEntry)的场景,而 redirectPort 字段仅对重定向规则(RedirectRule)有意义。在 AccessControlListManagerImpl.handle(APIAddAccessControlListEntryMsg) 中,创建的条目类型为 IpEntry,并未设置 redirectPort

建议确认此示例是否应包含 redirectPort,或者此示例是否应仅展示 IP 条目的典型字段。

plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java (1)

24-36: LGTM!

__example__() 方法中正确添加了 redirectPort 示例值,与重定向规则的使用场景一致。

plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java (1)

212-284: LGTM!

handle(APIAddAccessControlListRedirectRuleMsg) 方法中正确地将 redirectPort 从 API 消息传递到 AccessControlListEntryVO。端口范围验证(1-65535)已在 APIAddAccessControlListRedirectRuleMsg@APIParam 注解中处理。

plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java (1)

28-28: LGTM!

redirectPort 字段的添加遵循了现有 Inventory 类的模式,包括字段声明、构造函数初始化和标准的 getter/setter。

Also applies to: 58-58, 121-127

plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java (1)

47-48: LGTM!

API 参数 redirectPort 的添加符合规范:

  • 端口范围 {1, 65535} 正确
  • required = false 保证向后兼容
  • __example__ 方法正确演示了用法

Also applies to: 107-113, 124-124

plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java (2)

351-351: LGTM!

LbTO.RedirectRule 内部类中添加 redirectPort 字段,用于向虚拟路由器后端传输重定向端口配置。实现符合现有模式。

Also applies to: 384-390


785-785: LGTM!

单服务器组和多服务器组两种路径都正确地从 entry.getRedirectPort() 传播重定向端口到 redirectRule 对象。

Also applies to: 791-791

plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java (1)

10-11: LGTM!

新增的 UPGRADE_LB_REDIRECT_RULE 全局属性遵循现有的 UPGRADE_LB_SERVER_GROUP 模式,默认值为 false 确保向后兼容性。升级行为需要显式启用,符合编码规范中的向后兼容原则。

plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java (1)

23-23: LGTM!

JPA 元模型属性 redirectPort 正确反映了 AccessControlListEntryVO 中新增的字段,类型为 Integer,遵循标准元模型模式。

plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java (2)

20-23: 新增 AclEntryType 引用合理

用于后续按 RedirectRule 类型筛选,方向正确。


451-486: start() 触发升级流程符合预期

升级逻辑有全局开关保护,放在初始化阶段执行合理。

plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java (1)

49-52: redirectPort 字段与访问器添加 OK

使用 Integer 支持 null(兼容老数据)也便于后续升级补齐。

Also applies to: 171-177

plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java (2)

23-26: 导入新增与新逻辑匹配

引入 ACL/RedirectRule 相关 VO/enum 供后续更新使用,变更合理。


2122-2155: 拆分 doAddAccessControlListToLoadBalancer() 提升可读性

将原逻辑抽到独立方法、并用 FlowChain 串起“写 DB →(必要时)refresh”流程,结构更清晰。

test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy (1)

299-325: 升级路径断言覆盖到位

先将 redirectPort 置空再触发升级、并校验写回 8080,能有效回归升级逻辑。

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +1006 to +1035
public void upgradeLoadBalancerRedirectRule() {
if (!LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE) {
return;
}
List<AccessControlListEntryVO> redirectRules = Q.New(AccessControlListEntryVO.class)
.eq(AccessControlListEntryVO_.type, AclEntryType.RedirectRule.toString())
.isNull(AccessControlListEntryVO_.redirectPort)
.list();
if (redirectRules.isEmpty()) {
return;
}

List<AccessControlListEntryVO> toUpgradeRules = new ArrayList<>();
for (AccessControlListEntryVO rule : redirectRules) {
LoadBalancerListenerVO lbListenerVO = SQL.New("select lbListener from LoadBalancerListenerVO lbListener, LoadBalancerListenerACLRefVO ref, AccessControlListVO acl " +
"where acl.uuid = ref.aclUuid and ref.listenerUuid = lbListener.uuid and acl.uuid = :aclUuid")
.param("aclUuid", rule.getAclUuid())
.find();
if (lbListenerVO == null) {
continue;
}

rule.setRedirectPort(lbListenerVO.getInstancePort());
toUpgradeRules.add(rule);
}

if (!toUpgradeRules.isEmpty()) {
dbf.updateCollection(toUpgradeRules);
}
}
Copy link

@coderabbitai coderabbitai bot Jan 15, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

升级逻辑需要避免“同一 ACL 关联多个 listener”时的不确定性/潜在异常

当前按 aclUuid 反查一个 LoadBalancerListenerVO 后写入 redirectPort,如果同一 aclUuid 被多个 listener 引用(且 instancePort 不同),这里会出现选取不确定/覆盖语义不清的问题;另外也建议确认 SQL.find() 在多结果场景的行为。建议改为显式处理多结果:仅在能唯一确定 instancePort 时升级,否则跳过并记录日志(保留 null 让后续 attach 时再补齐)。

Proposed diff (deterministic + safe upgrade)
 public void upgradeLoadBalancerRedirectRule() {
     if (!LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE) {
         return;
     }
     List<AccessControlListEntryVO> redirectRules = Q.New(AccessControlListEntryVO.class)
             .eq(AccessControlListEntryVO_.type, AclEntryType.RedirectRule.toString())
             .isNull(AccessControlListEntryVO_.redirectPort)
             .list();
     if (redirectRules.isEmpty()) {
         return;
     }

     List<AccessControlListEntryVO> toUpgradeRules = new ArrayList<>();
     for (AccessControlListEntryVO rule : redirectRules) {
-        LoadBalancerListenerVO lbListenerVO = SQL.New("select lbListener from LoadBalancerListenerVO lbListener, LoadBalancerListenerACLRefVO ref, AccessControlListVO acl " +
-                "where acl.uuid = ref.aclUuid and ref.listenerUuid = lbListener.uuid and acl.uuid = :aclUuid")
-                .param("aclUuid", rule.getAclUuid())
-                .find();
-        if (lbListenerVO == null) {
-            continue;
-        }
-
-        rule.setRedirectPort(lbListenerVO.getInstancePort());
-        toUpgradeRules.add(rule);
+        List<Integer> instancePorts = SQL.New(
+                "select distinct lbListener.instancePort from LoadBalancerListenerVO lbListener, LoadBalancerListenerACLRefVO ref " +
+                        "where ref.listenerUuid = lbListener.uuid and ref.aclUuid = :aclUuid",
+                Integer.class)
+                .param("aclUuid", rule.getAclUuid())
+                .list();
+
+        if (instancePorts.isEmpty()) {
+            continue;
+        }
+        if (instancePorts.size() > 1) {
+            logger.warn(String.format("skip upgrading redirectPort for acl[uuid:%s], multiple listener instancePorts found: %s",
+                    rule.getAclUuid(), instancePorts));
+            continue;
+        }
+
+        rule.setRedirectPort(instancePorts.get(0));
+        toUpgradeRules.add(rule);
     }

     if (!toUpgradeRules.isEmpty()) {
         dbf.updateCollection(toUpgradeRules);
     }
 }

Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment from pengchao.liu:

可以优化,下次提pr时补上

Copy link

Choose a reason for hiding this comment

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

@ZStack-Robot 好的,感谢确认!既然计划在下次 PR 中优化,是否需要我创建一个 follow-up issue 来跟踪这个优化项?这样可以确保后续不会遗漏。

您希望使用哪个 issue tracking 平台?

  • Jira Project: ZSTAC
  • Jira Project: TIC
  • Jira Project: ZBS
  • GitHub

🧠 Learnings used
Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 2360
File: network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java:449-490
Timestamp: 2025-08-04T04:48:19.103Z
Learning: ZStack项目在cherry-pick操作中,即使发现了性能优化机会(如IP地址批量保存的内存优化),也严格遵循不做额外修改的政策,优先保证cherry-pick的完整性和一致性。

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 2489
File: storage/src/main/java/org/zstack/storage/snapshot/VolumeTree.java:471-545
Timestamp: 2025-08-22T05:36:40.467Z
Learning: In ZStack项目中,对于核心逻辑相关的代码,团队倾向于暂时不进行重构,即使代码复杂度较高,优先保证核心功能的稳定性。

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 2325
File: compute/src/main/java/org/zstack/compute/vm/VmMigrateCallExtensionFlow.java:29-29
Timestamp: 2025-07-24T05:53:10.246Z
Learning: 在ZStack项目的重构过程中,团队采用务实的优先级策略:优先完成影响运行时功能的代码重构,对于注释中的旧引用等非功能性问题可以延后处理,因为这些不会影响系统的实际运行。

Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 2360
File: compute/src/main/java/org/zstack/compute/vm/StaticIpOperator.java:364-501
Timestamp: 2025-08-04T03:19:07.454Z
Learning: In ZStack project, developers may prefer to keep related validation logic together in a single method (like validateStaticIpTagsInApiMessage with 130+ lines) when the architecture is clear and there's no code reuse, rather than splitting into smaller methods based purely on single responsibility principle.

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 2419
File: plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java:986-1067
Timestamp: 2025-08-13T02:31:40.023Z
Learning: 在ZStack项目中,团队倾向于保持统一的业务流程方法不进行拆分,即使方法较长也会维持在单个方法中以保持业务逻辑的完整性。

Learnt from: zstack-robot-2
Repo: MatheMatrix/zstack PR: 2496
File: plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/KvmBackend.java:2545-2566
Timestamp: 2025-08-24T07:35:42.793Z
Learning: 在 ZStack 代码库中,当响应类包含数值字段(如 size、actualSize)时,优先使用原始类型(long)而不是包装类型(Long),以避免 NPE 风险和不必要的装箱/拆箱操作。如果 Agent 端可能不设置该字段,应在 Agent 端确保设置默认值,而不是在使用端做 null 检查。

Comment on lines +180 to +189
@Override
void test() {
dbf = bean(DatabaseFacade.class)
lbMgr = bean(LoadBalancerManagerImpl.class)
env.create {
testLoadBalancerForwardRule()
LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE = true
testUpgradeLoadBalancerForwardRule()
}
}
Copy link

@coderabbitai coderabbitai bot Jan 15, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

测试修改全局开关需恢复,避免影响后续用例

LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE 是全局静态状态,建议在用例结束(或 finally)恢复原值,降低串扰风险。

Proposed diff (restore global flag)
 class VirtualRouterLoadBalancerForwardRuleCase extends SubCase {
     DatabaseFacade dbf
     EnvSpec env
     LoadBalancerManagerImpl lbMgr
+    boolean oldUpgradeFlag

     `@Override`
     void test() {
         dbf = bean(DatabaseFacade.class)
         lbMgr = bean(LoadBalancerManagerImpl.class)
+        oldUpgradeFlag = LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE
         env.create {
-            testLoadBalancerForwardRule()
-            LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE = true
-            testUpgradeLoadBalancerForwardRule()
+            try {
+                testLoadBalancerForwardRule()
+                LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE = true
+                testUpgradeLoadBalancerForwardRule()
+            } finally {
+                LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE = oldUpgradeFlag
+            }
         }
     }

Also applies to: 327-330

🤖 Prompt for AI Agents
In
`@test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy`
around lines 180 - 189, The test sets the global static flag
LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE but never restores it,
risking cross-test interference; update the test() method (and the other
occurrence around lines 327-330) to capture the original value before modifying,
set the flag to true for the upgrade step, and ensure you restore the original
value in a finally block (or after env.create) so
LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE is always reset regardless
of test outcome.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment from pengchao.liu:

won't do

Copy link

Choose a reason for hiding this comment

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

@zstack-robot-2 Understood, thanks for clarifying. Based on the retrieved learning about ZStack's test framework, test cases are isolated from each other (case by case), so modifying global configurations like LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE at the end of a test case doesn't require restoration since it won't affect other test executions.


🧠 Learnings used
Learnt from: MatheMatrix
Repo: MatheMatrix/zstack PR: 2250
File: test/src/test/groovy/org/zstack/test/integration/network/l3network/IpRangeCase.groovy:528-531
Timestamp: 2025-07-07T04:01:27.830Z
Learning: 在ZStack测试框架中,测试用例之间是相互隔离的(case by case),在测试用例结尾修改全局配置(如VmGlobalConfig.VM_DELETION_POLICY)不需要恢复原始值,因为不会影响其他测试用例的执行。

Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 2360
File: network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java:449-490
Timestamp: 2025-08-04T04:48:19.103Z
Learning: ZStack项目在cherry-pick操作中,即使发现了性能优化机会(如IP地址批量保存的内存优化),也严格遵循不做额外修改的政策,优先保证cherry-pick的完整性和一致性。

Comment on lines +191 to +297
void testLoadBalancerForwardRule() {
def lb1 = env.inventoryByName("lb1") as LoadBalancerInventory
def vm1 = env.inventoryByName("vm1") as VmInstanceInventory
def vm2 = env.inventoryByName("vm2") as VmInstanceInventory
def vm3 = env.inventoryByName("vm3") as VmInstanceInventory
def l3 = env.inventoryByName("l3") as L3NetworkInventory

def listener_1 = createLoadBalancerListener {
name = "listener-1"
loadBalancerUuid = lb1.uuid
protocol = "http"
loadBalancerPort = 80
instancePort = 8080
} as LoadBalancerListenerInventory

def serverGroup_1 = createLoadBalancerServerGroup {
name = "server-group-1"
loadBalancerUuid = lb1.uuid
} as LoadBalancerServerGroupInventory

addServerGroupToLoadBalancerListener {
serverGroupUuid = serverGroup_1.uuid
listenerUuid = listener_1.uuid
}

def vmNic1 = vm1.vmNics.find{ nic -> nic.l3NetworkUuid == l3.uuid } as VmNicInventory
assert vmNic1 != null

VirtualRouterLoadBalancerBackend.RefreshLbCmd cmd = null
env.afterSimulator(VirtualRouterLoadBalancerBackend.REFRESH_LB_PATH) { rsp, HttpEntity<String> e ->
cmd = JSONObjectUtil.toObject(e.body, VirtualRouterLoadBalancerBackend.RefreshLbCmd.class)
return rsp
}

def acl1 = createAccessControlList {
name = "acl-1"
} as AccessControlListInventory

def aclRule1 = addAccessControlListRedirectRule {
name = "acl-rule-1"
aclUuid = acl1.uuid
domain = "zstack.local"
url = "/test1"
} as AccessControlListEntryInventory

def rvo = Q.New(AccessControlListEntryVO.class)
.eq(AccessControlListEntryVO_.uuid, aclRule1.uuid)
.find()
assert rvo != null
assert rvo.getRedirectPort() == null

addAccessControlListToLoadBalancer {
listenerUuid = listener_1.uuid
serverGroupUuids = [serverGroup_1.uuid]
aclUuids = [acl1.uuid]
aclType = "redirect"
}

assert Q.New(AccessControlListEntryVO.class)
.eq(AccessControlListEntryVO_.uuid, aclRule1.uuid)
.eq(AccessControlListEntryVO_.redirectPort, 8080)
.isExists()

addBackendServerToServerGroup {
serverGroupUuid = serverGroup_1.uuid
vmNics = [[
uuid: vmNic1.uuid,
ipVersion: "4"
]]
}
assert cmd != null
def lbTO = cmd.lbs.find { it.lbUuid == lb1.uuid }
assert lbTO != null
def redirectRuleTO = lbTO.redirectRules.find { it.redirectRuleUuid == aclRule1.uuid }
assert redirectRuleTO != null
assert redirectRuleTO.redirectPort == 8080

cmd = null
def acl2 = createAccessControlList {
name = "acl-2"
} as AccessControlListInventory
def aclRule2 = addAccessControlListRedirectRule {
name = "acl-rule-2"
aclUuid = acl2.uuid
domain = "zstack.local"
url = "/test2"
redirectPort = 9090
} as AccessControlListEntryInventory

assert Q.New(AccessControlListEntryVO.class)
.eq(AccessControlListEntryVO_.uuid, aclRule2.uuid)
.eq(AccessControlListEntryVO_.redirectPort, 9090)
.isExists()

addAccessControlListToLoadBalancer {
listenerUuid = listener_1.uuid
serverGroupUuids = [serverGroup_1.uuid]
aclUuids = [acl2.uuid]
aclType = "redirect"
}

lbTO = cmd.lbs.find { it.lbUuid == lb1.uuid }
assert lbTO != null
redirectRuleTO = lbTO.redirectRules.find { it.redirectRuleUuid == aclRule2.uuid }
assert redirectRuleTO != null
assert redirectRuleTO.redirectPort == 9090
}
Copy link

@coderabbitai coderabbitai bot Jan 15, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

用例覆盖点清晰,但建议补一个 cmd != null 断言提升稳定性

第二段 acl2 attach 后直接使用 cmd.lbs,建议先 assert cmd != null(避免 refresh 未触发时 NPE 导致误判)。

Proposed diff (extra assertion)
         addAccessControlListToLoadBalancer {
             listenerUuid = listener_1.uuid
             serverGroupUuids = [serverGroup_1.uuid]
             aclUuids = [acl2.uuid]
             aclType = "redirect"
         }
-        
+        assert cmd != null
         lbTO =  cmd.lbs.find { it.lbUuid == lb1.uuid }
🤖 Prompt for AI Agents
In
`@test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy`
around lines 191 - 297, The test may NPE when using cmd after attaching acl2
because the refresh callback might not have run; add an assertion that cmd !=
null before accessing cmd.lbs after creating acl2 and attaching it (i.e., insert
assert cmd != null right before "lbTO = cmd.lbs.find { it.lbUuid == lb1.uuid
}"), ensuring the RefreshLbCmd cmd captured by the
env.afterSimulator(VirtualRouterLoadBalancerBackend.REFRESH_LB_PATH) handler is
present.

Copy link
Owner

Choose a reason for hiding this comment

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

Comment from pengchao.liu:

won't do

Copy link

Choose a reason for hiding this comment

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

@MatheMatrix, acknowledged. I understand you've decided not to add the additional assertion.

@MatheMatrix MatheMatrix force-pushed the sync/pengchao.liu/ZSTAC-79223@@3 branch from d3c9981 to 4512d2e Compare January 15, 2026 10:52
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

🤖 Fix all issues with AI agents
In
`@plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java`:
- Around line 2063-2071: The current two-step flow (dbf.persistCollection(refs)
then SQL.New(AccessControlListEntryVO)...update()) risks concurrent overwrite
and lacks transactional atomicity; wrap the persist and redirectPort update in a
single transaction and replace the blind batch UPDATE with a selective, locked
read-modify-write: after starting a transaction (or annotating the enclosing
method with `@Transactional`), for each aclUuid from msg.getAclUuids() load the
corresponding AccessControlListEntryVO row(s) with a "FOR UPDATE" lock, check if
AccessControlListEntryVO.getRedirectPort() is null and only then
setRedirectPort(instancePort) and save that VO (instead of the current
SQL.New(...) bulk update); ensure this uses the same transaction that calls
dbf.persistCollection(refs) so persistence and redirectPort updates are atomic
and cannot be concurrently overwritten.
🧹 Nitpick comments (1)
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java (1)

2026-2031: 建议使用泛型参数化 Map 类型

Line 2028 使用了原始类型 Map data = new HashMap();,建议使用参数化类型以提高类型安全性。

♻️ 建议的修改
-        Map data = new HashMap();
+        Map<String, Object> data = new HashMap<>();
📜 Review details

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3c9981 and 4512d2e.

⛔ Files ignored due to path filters (2)
  • sdk/src/main/java/org/zstack/sdk/AccessControlListEntryInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/AddAccessControlListRedirectRuleAction.java is excluded by !sdk/**
📒 Files selected for processing (14)
  • conf/db/upgrade/V5.5.6__schema.sql
  • plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventoryDoc_zh_cn.groovy
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
  • plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java
  • test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy
🚧 Files skipped from review as they are similar to previous changes (8)
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
  • conf/db/upgrade/V5.5.6__schema.sql
  • plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventoryDoc_zh_cn.groovy
  • test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy
  • plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java
🧰 Additional context used
📓 Path-based instructions (2)
**/*.*

⚙️ CodeRabbit configuration file

**/*.*: - 代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写

Files:

  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: ## 1. API 设计要求

  • API 命名:
    • API 名称必须唯一,不能重复。
    • API 消息类需要继承 APIMessage;其返回类必须继承 APIReplyAPIEvent,并在注释中用 @RestResponse 进行标注。
    • API 消息上必须添加注解 @RestRequest,并满足如下规范:
      • path:
        • 针对资源使用复数形式。
        • 当 path 中引用消息类变量时,使用 {variableName} 格式。
      • HTTP 方法对应:
        • 查询操作 → HttpMethod.GET
        • 更新操作 → HttpMethod.PUT
        • 创建操作 → HttpMethod.POST
        • 删除操作 → HttpMethod.DELETE
    • API 类需要实现 __example__ 方法以便生成 API 文档,并确保生成对应的 Groovy API Template 与 API Markdown 文件。

2. 命名与格式规范

  • 类名:

    • 使用 UpperCamelCase 风格。
    • 特殊情况:
      • VO/AO/EO 类型类除外。
      • 抽象类采用 AbstractBase 前缀/后缀。
      • 异常类应以 Exception 结尾。
      • 测试类需要以 TestCase 结尾。
  • 方法名、参数名、成员变量和局部变量:

    • 使用 lowerCamelCase 风格。
  • 常量命名:

    • 全部大写,使用下划线分隔单词。
    • 要求表达清楚,避免使用含糊或不准确的名称。
  • 包名:

    • 统一使用小写,使用点分隔符,每个部分应是一个具有自然语义的英文单词(参考 Spring 框架的结构)。
  • 命名细节:

    • 避免在父子类或同一代码块中出现相同名字的成员或局部变量,防止混淆。
    • 命名缩写:
      • 不允许使用不必要的缩写,如:AbsSchedulerJobcondiFu 等。应使用完整单词提升可读性。

3. 编写自解释代码

  • 意图表达:

    • 避免使用布尔型参数造成含义不明确。例如:
      • 对于 stopAgent(boolean ignoreError),建议拆分为不同函数(如 stopAgentIgnoreError()),或使用枚举表达操作类型。
    • 命名应尽量用完整的单词组合表达意图,并在名称中体现数据类型或用途(例如在常量与变量名称中,将类型词放在末尾)。
    • 避免使用魔法值(Magic Value):
      • 直接使用未经定义的数值或字符串(如 if (status == 5))应替换为枚举或常量。

      • 示例:

      • // 错误示例:魔法值

      • if (user.getStatus() == 5) { ... }

      • // 正确示例:常量或枚举

      • public static final int STATUS_ACTIVE = 5;

      • if (user.getStatus() == STATUS_ACTIVE) { ... }

      • // 或使用枚举

      • enum UserStatus { ACTIVE, INACTIVE }

  • 注释:

    • 代码应尽量做到自解释,对少于两行的说明可以直接写在代码中。
    • 对于较长的注释,需要仔细校对并随代码更新,确保内容正确。
    • 接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。

4. 流程控制和结构优化

  • if...else 的使用:

    • 应尽量减少 if...else 结构的使用,建议:
      • 限制嵌套层级最多为两层,且内层不应再出现 else 分支。
      • 尽早返回(Early Return),将条件判断中的处理逻辑提前结束或抽成独立方法。
      • 使用 Java Stream 或 Lambda 表达式代替冗长的循环与条件判断。
  • 条件判断:

    • if 条件表达不宜过长或过于复杂,必要时可以将条件抽成 boolean 变量描述。
  • 代码块长度:
    ...

Files:

  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java
🧠 Learnings (1)
📓 Common learnings
Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 2360
File: network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java:449-490
Timestamp: 2025-08-04T04:48:19.103Z
Learning: ZStack项目在cherry-pick操作中,即使发现了性能优化机会(如IP地址批量保存的内存优化),也严格遵循不做额外修改的政策,优先保证cherry-pick的完整性和一致性。
🔍 Remote MCP AtlassianMCP

Additional context from Jira (ZSTAC-79223)

  • Issue: ZSTAC-79223 — "[浙江大学]负载均衡监听器支持通过配置域名转发规则指定到不同主机的不同端口" (New Feature, P0).
  • Fix version: 5.5.6.
  • Goal / description (concise): allow configuring per-forwarding-rule backend port so domain-based forwarding can route to different hosts on different ports; also allow multiple listeners with duplicate frontend ports. (Includes two example images in attachments.)
  • Assignee: 刘鹏超 (pengchao.liu).
  • Status: Open. Priority: P0. Issue type: New Feature.
  • Worklog/comments: development activity recorded by 张建军 and 刘鹏超 (worklogs show coding on control/data plane and UT fixes on 2026-01-13 to 2026-01-15).
  • Related link: outward issue SUG-2212 (implements/related).

Sources:

  • AtlassianMCP jira_get_issue for ZSTAC-79223 (full issue, worklog, comments, attachments).
🔇 Additional comments (11)
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java (1)

10-11: LGTM!

新增的全局属性 UPGRADE_LB_REDIRECT_RULE 遵循了现有的命名规范和模式,与 UPGRADE_LB_SERVER_GROUP 保持一致,默认值设为 false 符合升级标志的最佳实践。

plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java (3)

28-28: LGTM!

新增的 redirectPort 字段使用 Integer 类型允许空值,适合可选字段的场景。


51-63: LGTM!

构造函数正确地从 VO 映射了 redirectPort 字段,与其他字段的处理方式保持一致。


121-127: LGTM!

Getter 和 Setter 方法遵循 JavaBean 规范,实现正确。

plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java (2)

2076-2107: LGTM - FlowChain 刷新逻辑实现正确

刷新监听器的流程正确处理了以下情况:

  • refs 为空时直接跳过
  • 仅在监听器需要刷新时才触发 RefreshLoadBalancerMsg
  • 失败时正确回滚已持久化的 refs

2122-2155: LGTM - handle 方法重构合理

将 ACL 添加逻辑委托给 doAddAccessControlListToLoadBalancer 方法,使用 Completion 回调模式统一处理成功和失败情况,代码结构更清晰。

plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java (1)

28-38: LGTM!

示例方法正确展示了新增的 redirectPort 字段,使用 8080 作为示例端口值是合理的。

plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java (1)

24-36: LGTM!

示例方法正确展示了重定向规则变更事件中的 redirectPort 字段,与其他 API 示例保持一致。

plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java (3)

47-48: 代码实现规范,端口验证正确。

新增的 redirectPort 字段设计合理:

  • 使用 Integer 类型允许为 null(表示可选)
  • 端口范围校验 {1, 65535} 符合 TCP/UDP 端口规范
  • required = false 适用于可选覆盖后端端口的场景

107-113: LGTM!

Getter 和 Setter 方法实现符合 Java Bean 规范,命名遵循 lowerCamelCase 风格。


115-126: 示例方法更新正确。

__example__() 方法已正确更新,包含新增的 redirectPort 参数示例值 8080,有助于生成准确的 API 文档。

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +2063 to +2071
Integer instancePort = lblVo.getInstancePort();
msg.getAclUuids().forEach(aclUuid -> {
SQL.New(AccessControlListEntryVO.class)
.eq(AccessControlListEntryVO_.aclUuid, aclUuid)
.eq(AccessControlListEntryVO_.type, AclEntryType.RedirectRule.toString())
.isNull(AccessControlListEntryVO_.redirectPort)
.set(AccessControlListEntryVO_.redirectPort, instancePort)
.update();
});
Copy link

@coderabbitai coderabbitai bot Jan 15, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file structure and get context around the code
cd plugin/loadBalancer && find . -name "LoadBalancerBase.java" -type f | head -5

Repository: MatheMatrix/zstack

Length of output: 129


🏁 Script executed:

# Get more context about the method containing these lines
sed -n '2040,2090p' plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java

Repository: MatheMatrix/zstack

Length of output: 2819


🏁 Script executed:

# Check the relationship between LoadBalancerListener and ACL
rg "LoadBalancerListenerACLRefVO" plugin/loadBalancer/src/main/java --type java -B 2 -A 5 | head -150

Repository: MatheMatrix/zstack

Length of output: 21632


🏁 Script executed:

# Search for transaction boundaries in this file
rg "@Transactional|SQL\.New\(" plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java --type java -B 1 -A 1 | head -100

Repository: MatheMatrix/zstack

Length of output: 5361


ACL 条目 redirectPort 更新存在并发安全隐患

代码在两个不同的操作中更新 redirectPort:先通过 dbf.persistCollection(refs) 持久化 ACL 绑定关系,再通过 SQL.New(AccessControlListEntryVO.class)...update() 批量更新 ACL 条目。存在以下问题:

  1. 并发覆盖风险:同一个 ACL 可被多个监听器绑定(LoadBalancerListenerACLRefVO 中 listenerUuid 不同而 aclUuid 相同)。当两个监听器同时绑定该 ACL 但 instancePort 不同时,后一个操作会覆盖先前的 redirectPort 值,导致配置错误。

  2. 事务原子性缺失:持久化和更新操作不在同一事务内执行。若两操作间发生异常,ACL 绑定关系已保存但条目 redirectPort 未更新,造成数据不一致。

🤖 Prompt for AI Agents
In
`@plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java`
around lines 2063 - 2071, The current two-step flow (dbf.persistCollection(refs)
then SQL.New(AccessControlListEntryVO)...update()) risks concurrent overwrite
and lacks transactional atomicity; wrap the persist and redirectPort update in a
single transaction and replace the blind batch UPDATE with a selective, locked
read-modify-write: after starting a transaction (or annotating the enclosing
method with `@Transactional`), for each aclUuid from msg.getAclUuids() load the
corresponding AccessControlListEntryVO row(s) with a "FOR UPDATE" lock, check if
AccessControlListEntryVO.getRedirectPort() is null and only then
setRedirectPort(instancePort) and save that VO (instead of the current
SQL.New(...) bulk update); ensure this uses the same transaction that calls
dbf.persistCollection(refs) so persistence and redirectPort updates are atomic
and cannot be concurrently overwritten.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment from pengchao.liu:

一个 ACL 不会绑定多个监听器,won't do

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

@zstack-robot-1 I need to verify the constraint you mentioned. Let me check the codebase to understand the ACL-to-listener relationship and whether it's enforced.


🏁 Script executed:

#!/bin/bash
# Check the LoadBalancerListenerACLRefVO entity definition for unique constraints
rg -A 30 "class LoadBalancerListenerACLRefVO" plugin/acl plugin/loadBalancer --type java

Length of output: 5529


🏁 Script executed:

#!/bin/bash
# Check for unique constraints or indexes on the ACL ref table
rg "@Table|@UniqueConstraint|unique|UNIQUE" plugin/acl plugin/loadBalancer --type java -B 2 -A 5 | grep -A 10 -B 10 "LoadBalancerListenerACLRefVO"

Length of output: 3148


🏁 Script executed:

#!/bin/bash
# Check API validation logic for adding ACL to listener
rg "APIAddAccessControlListToLoadBalancerMsg" plugin/loadBalancer plugin/acl --type java -A 20 | head -100

Length of output: 15721


🏁 Script executed:

#!/bin/bash
# Look for any validation that prevents one ACL from binding to multiple listeners
rg "prevent|validate|check" plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java --type java -B 3 -A 3 | grep -i acl -A 5 -B 5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the SQL schema definition for the ACL ref table
cat conf/db/upgrade/V5.5.6__schema.sql 2>/dev/null || echo "Schema file not found in this path"

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Search for the ACL ref table creation in other schema files
fd -e sql . conf/db 2>/dev/null | xargs grep -l "LoadBalancerListenerACLRef" | head -5

Length of output: 69


🏁 Script executed:

#!/bin/bash
# Check the table creation SQL for LoadBalancerListenerACLRef
grep -A 20 "LoadBalancerListenerACLRef" conf/db/upgrade/V3.9.0__schema.sql conf/db/upgrade/V4.1.3__schema.sql

Length of output: 2941


🏁 Script executed:

#!/bin/bash
# Look for the actual scenario - can same ACL be added to multiple listeners?
# Check if there's code that checks for existing ACL-listener bindings
rg "Q\.New\(LoadBalancerListenerACLRefVO" plugin/loadBalancer --type java -B 2 -A 8 | head -80

Length of output: 11491


🏁 Script executed:

#!/bin/bash
# Check the doAddAccessControlListToLoadBalancer method more carefully for duplicate prevention
sed -n '2038,2130p' plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java

Length of output: 4622


🏁 Script executed:

#!/bin/bash
# Search for evidence that same ACL can bind to multiple listeners or servergroups
rg "serverGroupUuid|ServerGroup" plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerListenerACLRefVO.java --type java -B 3 -A 3

Length of output: 395


@zstack-robot-1 感谢澄清设计意图。不过从代码实现来看,目前并没有技术手段强制这一约束,存在以下证据:


🏁 Script executed:

#!/bin/bash
# Show that LoadBalancerExtension actively queries multiple listeners for one ACL
grep -B 3 -A 8 "refreshLoadBalancerByAcl" plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerExtension.java | head -20

Length of output: 744


🏁 Script executed:

#!/bin/bash
# Show the database schema has no unique constraint to prevent duplicate ACL-listener bindings
echo "=== Database Schema Evidence ==="
grep -A 15 "CREATE TABLE.*LoadBalancerListenerACLRefVO" conf/db/upgrade/V3.9.0__schema.sql | grep -E "UNIQUE|PRIMARY|CONSTRAINT"

Length of output: 404


🏁 Script executed:

#!/bin/bash
# Check if there's any API validation preventing duplicate ACL-listener binding
rg "duplicate|already.*exist|already.*bind" plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java --type java -B 3 -A 3 | grep -i acl -A 5 -B 5

Length of output: 3538


@zstack-robot-1 根据代码分析,当前实现确实支持一个 ACL 绑定多个监听器

证据 1:系统主动查询多监听器场景

// LoadBalancerExtension.java - refreshLoadBalancerByAcl
List<String> listenerUuids = Q.New(LoadBalancerListenerACLRefVO.class)
    .select(LoadBalancerListenerACLRefVO_.listenerUuid)
    .eq(LoadBalancerListenerACLRefVO_.aclUuid, aclUuid)
    .listValues();  // 返回列表,不是单个值

证据 2:数据库无唯一约束

-- LoadBalancerListenerACLRefVO 表仅 id 列有 UNIQUE 约束
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
-- 缺少 UNIQUE KEY (aclUuid, listenerUuid) 防止重复绑定

证据 3:API 校验仅限同一监听器内

// LoadBalancerApiInterceptor.java
// 校验信息:"the access-control-list groups are already on the load balancer listener"
// 仅阻止 同一ACL 重复绑定到 同一监听器,不阻止绑定到不同监听器

关于并发问题:
虽然代码使用了 .isNull(AccessControlListEntryVO_.redirectPort) 条件减少覆盖风险,但 dbf.persistCollection(refs)SQL.New(...).update() 仍是两个独立操作,缺乏事务原子性保护。

理解您"won't do"的决定。如果团队有业务层约束确保不会发生多监听器绑定场景,可以保持现状。不过建议考虑在数据库层或 API 校验层显式添加约束,避免未来误用导致数据不一致。

GlobalPropertyImpact
APIImpact
DBImpact

Resolves: ZSTAC-79223

Change-Id: I7771696566617172666f776a797a756672767774
@MatheMatrix MatheMatrix force-pushed the sync/pengchao.liu/ZSTAC-79223@@3 branch from 4512d2e to 1681738 Compare January 16, 2026 02:21
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

🤖 Fix all issues with AI agents
In
`@plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsgDoc_zh_cn.groovy`:
- Around line 105-113: 在 API 文档片段中补全 redirectPort 字段的 desc 字段:在
org.zstack.header.acl.APIAddAccessControlListRedirectRuleMsgDoc_zh_cn.groovy 中找到
column 定义的 name "redirectPort" 并为 desc
提供简洁说明(例如指明这是目标重定向端口、端口范围或默认行为、此字段为可选),保持与 type "Integer" 和 since "5.5.0"
一致且语义清晰以避免文档空白。
♻️ Duplicate comments (4)
test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy (2)

180-188: 全局开关未恢复,可能污染后续用例
LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE 为全局静态状态,建议在用例结束后恢复原值以避免串扰。

🧪 建议修复(恢复全局开关)
 class VirtualRouterLoadBalancerForwardRuleCase extends SubCase {
     DatabaseFacade dbf
     EnvSpec env
     LoadBalancerManagerImpl lbMgr
+    boolean oldUpgradeFlag

     `@Override`
     void test() {
         dbf = bean(DatabaseFacade.class)
         lbMgr = bean(LoadBalancerManagerImpl.class)
+        oldUpgradeFlag = LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE
         env.create {
-            testLoadBalancerForwardRule()
-            LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE = true
-            testUpgradeLoadBalancerForwardRule()
+            try {
+                testLoadBalancerForwardRule()
+                LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE = true
+                testUpgradeLoadBalancerForwardRule()
+            } finally {
+                LoadBalancerGlobalProperty.UPGRADE_LB_REDIRECT_RULE = oldUpgradeFlag
+            }
         }
     }
 }

292-295: 使用 cmd 前先断言非空
第二次 attach 后直接访问 cmd.lbs 可能导致 NPE,建议先断言 cmd != null

🧪 建议修复(补充断言)
         addAccessControlListToLoadBalancer {
             listenerUuid = listener_1.uuid
             serverGroupUuids = [serverGroup_1.uuid]
             aclUuids = [acl2.uuid]
             aclType = "redirect"
         }
-        
+        assert cmd != null
         lbTO =  cmd.lbs.find { it.lbUuid == lb1.uuid }
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java (1)

2063-2071: redirectPort 批量更新仍有并发覆盖/非原子风险
同一 ACL 可能被不同 listener 绑定且 instancePort 不同,目前批量 UPDATE 会造成覆盖不确定性;且与 refs 持久化不在同一事务内,异常时会留下一致性问题。建议在同一事务里进行持久化与逐条判空更新,并在多 listener 场景下明确处理策略(如冲突时跳过并记录日志)。

plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java (1)

1006-1034: 升级逻辑在“同一 ACL 关联多个 listener”时不确定
当前按 aclUuid 只取一条 listener 记录,若存在多个 listener(且 instancePort 不同),会导致 redirectPort 选择不可预期。建议改为收集 distinct instancePort:仅在唯一值时更新,否则跳过并记录日志。

🧭 处理思路(确定性升级)
-        LoadBalancerListenerVO lbListenerVO = SQL.New("select lbListener from LoadBalancerListenerVO lbListener, LoadBalancerListenerACLRefVO ref, AccessControlListVO acl " +
-                "where acl.uuid = ref.aclUuid and ref.listenerUuid = lbListener.uuid and acl.uuid = :aclUuid")
-                .param("aclUuid", rule.getAclUuid())
-                .find();
-        if (lbListenerVO == null) {
-            continue;
-        }
-
-        rule.setRedirectPort(lbListenerVO.getInstancePort());
-        toUpgradeRules.add(rule);
+        List<Integer> instancePorts = SQL.New(
+                "select distinct lbListener.instancePort from LoadBalancerListenerVO lbListener, LoadBalancerListenerACLRefVO ref " +
+                        "where ref.listenerUuid = lbListener.uuid and ref.aclUuid = :aclUuid",
+                Integer.class)
+                .param("aclUuid", rule.getAclUuid())
+                .list();
+        if (instancePorts.isEmpty()) {
+            continue;
+        }
+        if (instancePorts.size() > 1) {
+            logger.warn(String.format("skip upgrading redirectPort for acl[uuid:%s], multiple instancePorts found: %s",
+                    rule.getAclUuid(), instancePorts));
+            continue;
+        }
+        rule.setRedirectPort(instancePorts.get(0));
+        toUpgradeRules.add(rule);
📜 Review details

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4512d2e and 1681738.

⛔ Files ignored due to path filters (2)
  • sdk/src/main/java/org/zstack/sdk/AccessControlListEntryInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/AddAccessControlListRedirectRuleAction.java is excluded by !sdk/**
📒 Files selected for processing (15)
  • conf/db/upgrade/V5.5.6__schema.sql
  • plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsgDoc_zh_cn.groovy
  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventoryDoc_zh_cn.groovy
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
  • plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java
  • test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy
🚧 Files skipped from review as they are similar to previous changes (6)
  • plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerGlobalProperty.java
  • plugin/acl/src/main/java/org/zstack/acl/AccessControlListManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventory.java
  • conf/db/upgrade/V5.5.6__schema.sql
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryInventoryDoc_zh_cn.groovy
🧰 Additional context used
📓 Path-based instructions (2)
**/*.*

⚙️ CodeRabbit configuration file

**/*.*: - 代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写

Files:

  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java
  • test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsgDoc_zh_cn.groovy
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: ## 1. API 设计要求

  • API 命名:
    • API 名称必须唯一,不能重复。
    • API 消息类需要继承 APIMessage;其返回类必须继承 APIReplyAPIEvent,并在注释中用 @RestResponse 进行标注。
    • API 消息上必须添加注解 @RestRequest,并满足如下规范:
      • path:
        • 针对资源使用复数形式。
        • 当 path 中引用消息类变量时,使用 {variableName} 格式。
      • HTTP 方法对应:
        • 查询操作 → HttpMethod.GET
        • 更新操作 → HttpMethod.PUT
        • 创建操作 → HttpMethod.POST
        • 删除操作 → HttpMethod.DELETE
    • API 类需要实现 __example__ 方法以便生成 API 文档,并确保生成对应的 Groovy API Template 与 API Markdown 文件。

2. 命名与格式规范

  • 类名:

    • 使用 UpperCamelCase 风格。
    • 特殊情况:
      • VO/AO/EO 类型类除外。
      • 抽象类采用 AbstractBase 前缀/后缀。
      • 异常类应以 Exception 结尾。
      • 测试类需要以 TestCase 结尾。
  • 方法名、参数名、成员变量和局部变量:

    • 使用 lowerCamelCase 风格。
  • 常量命名:

    • 全部大写,使用下划线分隔单词。
    • 要求表达清楚,避免使用含糊或不准确的名称。
  • 包名:

    • 统一使用小写,使用点分隔符,每个部分应是一个具有自然语义的英文单词(参考 Spring 框架的结构)。
  • 命名细节:

    • 避免在父子类或同一代码块中出现相同名字的成员或局部变量,防止混淆。
    • 命名缩写:
      • 不允许使用不必要的缩写,如:AbsSchedulerJobcondiFu 等。应使用完整单词提升可读性。

3. 编写自解释代码

  • 意图表达:

    • 避免使用布尔型参数造成含义不明确。例如:
      • 对于 stopAgent(boolean ignoreError),建议拆分为不同函数(如 stopAgentIgnoreError()),或使用枚举表达操作类型。
    • 命名应尽量用完整的单词组合表达意图,并在名称中体现数据类型或用途(例如在常量与变量名称中,将类型词放在末尾)。
    • 避免使用魔法值(Magic Value):
      • 直接使用未经定义的数值或字符串(如 if (status == 5))应替换为枚举或常量。

      • 示例:

      • // 错误示例:魔法值

      • if (user.getStatus() == 5) { ... }

      • // 正确示例:常量或枚举

      • public static final int STATUS_ACTIVE = 5;

      • if (user.getStatus() == STATUS_ACTIVE) { ... }

      • // 或使用枚举

      • enum UserStatus { ACTIVE, INACTIVE }

  • 注释:

    • 代码应尽量做到自解释,对少于两行的说明可以直接写在代码中。
    • 对于较长的注释,需要仔细校对并随代码更新,确保内容正确。
    • 接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。

4. 流程控制和结构优化

  • if...else 的使用:

    • 应尽量减少 if...else 结构的使用,建议:
      • 限制嵌套层级最多为两层,且内层不应再出现 else 分支。
      • 尽早返回(Early Return),将条件判断中的处理逻辑提前结束或抽成独立方法。
      • 使用 Java Stream 或 Lambda 表达式代替冗长的循环与条件判断。
  • 条件判断:

    • if 条件表达不宜过长或过于复杂,必要时可以将条件抽成 boolean 变量描述。
  • 代码块长度:
    ...

Files:

  • plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
  • plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java
  • plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
  • plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java
🧠 Learnings (1)
📓 Common learnings
Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 2360
File: network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java:449-490
Timestamp: 2025-08-04T04:48:19.103Z
Learning: ZStack项目在cherry-pick操作中,即使发现了性能优化机会(如IP地址批量保存的内存优化),也严格遵循不做额外修改的政策,优先保证cherry-pick的完整性和一致性。
🧬 Code graph analysis (2)
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java (2)
plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java (1)
  • RedirectRule (346-391)
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerConstants.java (1)
  • LoadBalancerConstants (15-180)
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java (2)
core/src/main/java/org/zstack/core/db/Q.java (1)
  • Q (16-193)
plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lb/VirtualRouterLoadBalancerBackend.java (1)
  • RedirectRule (346-391)
🔍 Remote MCP AtlassianMCP

Relevant Jira context (ZSTAC-79223)

  • Issue: ZSTAC-79223 — "[浙江大学]负载均衡监听器支持通过配置域名转发规则指定到不同主机的不同端口"

  • Status / Priority / Assignee: Open, P0, assignee 刘鹏超 (pengchao.liu@zstack.io)

  • Fix version: 5.5.6

  • Goal / functional requirements (from description):

    • Allow per-forwarding-rule backend port configuration so domain-based forwarding can route to different hosts on different ports.
    • Allow multiple listeners with duplicate frontend ports (avoid port-conflict errors when creating multiple listeners on same frontend port).
  • Artifacts / attachments: two diagrams illustrating desired domain->port forwarding behavior are attached to the issue. URLs provided in Jira attachments.

  • Development activity / worklogs: multiple worklogs and recent activity (Jan 13–15, 2026) recorded by authors including 刘鹏超 and 张建军, indicating implementation and UT work.

Summary relevance to this PR: the PR adds a persistent redirectPort field (DB + VO + API + inventory), populates and propagates it through ACL, load balancer manager/flows, virtual router backend, and includes an upgrade path and UT — matching the Jira requirements to support per-rule backend ports and rollout for existing rules.

🔇 Additional comments (11)
plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryEvent.java (1)

32-34: 示例值同步新增字段,OK。
示例包含 redirectPort 有助于 API 文档一致性。

plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleEvent.java (1)

28-34: 示例值补充到位,OK。
示例对象新增 redirectPort 有助于前后端一致性。

plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO.java (2)

49-51: 字段持久化新增合理,OK。


171-176: 访问器补充完整,OK。

plugin/acl/src/main/java/org/zstack/header/acl/AccessControlListEntryVO_.java (1)

22-24: Metamodel 同步新增字段,OK。

plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java (3)

47-48: redirectPort 参数校验清晰
范围限制和可选性定义合理。


107-113: 访问器新增 OK
Getter/Setter 与字段一致。


124-124: 示例覆盖新字段
示例里补充 redirectPort 有助于 API 文档一致性。

test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/loadbalancer/VirtualRouterLoadBalancerForwardRuleCase.groovy (3)

46-178: 环境构建覆盖面充足
测试环境包含 VR/LB/VM 等关键路径,场景完整。


299-325: 升级路径断言清晰
对升级前后 redirectPort 的校验覆盖到位。


334-345: 资源清理完整
LB 与环境删除覆盖充分。

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +105 to +113
column {
name "redirectPort"
enclosedIn "params"
desc ""
location "body"
type "Integer"
optional true
since "5.5.0"
}
Copy link

@coderabbitai coderabbitai bot Jan 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

补齐 redirectPort 字段描述,避免文档空白。
desc 为空会导致 API 文档信息缺失,请补充清晰说明。

🛠️ 建议补充描述
 				column {
 					name "redirectPort"
 					enclosedIn "params"
-					desc ""
+					desc "Redirect backend port for the matched rule."
 					location "body"
 					type "Integer"
 					optional true
 					since "5.5.0"
 				}
📝 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
column {
name "redirectPort"
enclosedIn "params"
desc ""
location "body"
type "Integer"
optional true
since "5.5.0"
}
column {
name "redirectPort"
enclosedIn "params"
desc "Redirect backend port for the matched rule."
location "body"
type "Integer"
optional true
since "5.5.0"
}
🤖 Prompt for AI Agents
In
`@plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsgDoc_zh_cn.groovy`
around lines 105 - 113, 在 API 文档片段中补全 redirectPort 字段的 desc 字段:在
org.zstack.header.acl.APIAddAccessControlListRedirectRuleMsgDoc_zh_cn.groovy 中找到
column 定义的 name "redirectPort" 并为 desc
提供简洁说明(例如指明这是目标重定向端口、端口范围或默认行为、此字段为可选),保持与 type "Integer" 和 since "5.5.0"
一致且语义清晰以避免文档空白。

Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment from pengchao.liu:

下次再提pr的时候补上

Copy link

Choose a reason for hiding this comment

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

@ZStack-Robot 好的,理解了。期待在下一个 PR 中看到 redirectPort 字段的描述补充 👍


🧠 Learnings used
Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 2360
File: network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java:449-490
Timestamp: 2025-08-04T04:48:19.103Z
Learning: ZStack项目在cherry-pick操作中,即使发现了性能优化机会(如IP地址批量保存的内存优化),也严格遵循不做额外修改的政策,优先保证cherry-pick的完整性和一致性。

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 2489
File: storage/src/main/java/org/zstack/storage/snapshot/VolumeTree.java:471-545
Timestamp: 2025-08-22T05:36:40.467Z
Learning: In ZStack项目中,对于核心逻辑相关的代码,团队倾向于暂时不进行重构,即使代码复杂度较高,优先保证核心功能的稳定性。

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 2325
File: compute/src/main/java/org/zstack/compute/vm/VmMigrateCallExtensionFlow.java:29-29
Timestamp: 2025-07-24T05:53:10.246Z
Learning: 在ZStack项目的重构过程中,团队采用务实的优先级策略:优先完成影响运行时功能的代码重构,对于注释中的旧引用等非功能性问题可以延后处理,因为这些不会影响系统的实际运行。

Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 2360
File: compute/src/main/java/org/zstack/compute/vm/StaticIpOperator.java:364-501
Timestamp: 2025-08-04T03:19:07.454Z
Learning: In ZStack project, developers may prefer to keep related validation logic together in a single method (like validateStaticIpTagsInApiMessage with 130+ lines) when the architecture is clear and there's no code reuse, rather than splitting into smaller methods based purely on single responsibility principle.

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 2419
File: plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java:986-1067
Timestamp: 2025-08-13T02:31:40.023Z
Learning: 在ZStack项目中,团队倾向于保持统一的业务流程方法不进行拆分,即使方法较长也会维持在单个方法中以保持业务逻辑的完整性。

Learnt from: zstack-robot-2
Repo: MatheMatrix/zstack PR: 2496
File: plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/KvmBackend.java:2545-2566
Timestamp: 2025-08-24T07:35:42.793Z
Learning: 在 ZStack 代码库中,当响应类包含数值字段(如 size、actualSize)时,优先使用原始类型(long)而不是包装类型(Long),以避免 NPE 风险和不必要的装箱/拆箱操作。如果 Agent 端可能不设置该字段,应在 Agent 端确保设置默认值,而不是在使用端做 null 检查。

}
List<AccessControlListEntryVO> redirectRules = Q.New(AccessControlListEntryVO.class)
.eq(AccessControlListEntryVO_.type, AclEntryType.RedirectRule.toString())
.isNull(AccessControlListEntryVO_.redirectPort)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment from shixin.ruan:

默认值为啥不设置为0,是不是就不需要升级处理了

Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment from pengchao.liu:

acl rule还有另一种类型,默认设置为0反而会有歧义。综合考虑默认为NULL处理起来更方便

}

private void handle(APIAddAccessControlListToLoadBalancerMsg msg) {
DebugUtils.Assert(msg.getAclType() != null && msg.getAclUuids() != null, "parameters cannot be null");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment from shixin.ruan:

参数判断统一api interceptor, 而且现在有新的error log个格式。

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment from pengchao.liu:

这是原有代码,不过确实是个好建议。下次提pr时再修改

@zstack-robot-2 zstack-robot-2 deleted the sync/pengchao.liu/ZSTAC-79223@@3 branch January 19, 2026 05:40
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.

5 participants