-
Notifications
You must be signed in to change notification settings - Fork 0
<feature>[zns]: add ZNS-Cloud data sync reconciliation #3777
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package org.zstack.sdnController; | ||
|
|
||
| /** | ||
| * Tracks which SDN controllers need a full reconciliation sync. | ||
| * | ||
| * When external operations (VM NIC create/delete, migration rollback, etc.) encounter failures | ||
| * that may leave Cloud and SDN controller out of sync, they call {@link #markNeedSync} to flag | ||
| * the affected controller. | ||
| * | ||
| * The ping tracker checks this flag after consecutive successful pings and triggers a full sync | ||
| * when needed. | ||
| */ | ||
| public interface DirtySyncTracker { | ||
| /** | ||
| * Mark a controller as needing a full sync. | ||
| * Idempotent — calling multiple times has no additional effect. | ||
| */ | ||
| void markNeedSync(String controllerUuid); | ||
|
|
||
| /** | ||
| * Check if a controller needs a sync. | ||
| */ | ||
| boolean needsSync(String controllerUuid); | ||
|
|
||
| /** | ||
| * Atomically clear the needsSync flag and return its previous value. | ||
| * @return true if the controller was marked as needing sync, false otherwise. | ||
| */ | ||
| boolean clearNeedSync(String controllerUuid); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package org.zstack.sdnController; | ||
|
|
||
| import org.zstack.utils.Utils; | ||
| import org.zstack.utils.logging.CLogger; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| /** | ||
| * In-memory implementation of DirtySyncTracker. | ||
| * Thread-safe via ConcurrentHashMap. | ||
| * Dirty state is ephemeral — lost on MN restart, which is acceptable because | ||
| * the ping-triggered sync provides eventual consistency. | ||
| */ | ||
| public class DirtySyncTrackerImpl implements DirtySyncTracker { | ||
| private static final CLogger logger = Utils.getLogger(DirtySyncTrackerImpl.class); | ||
|
|
||
| private final Map<String, Boolean> needsSyncMap = new ConcurrentHashMap<>(); | ||
|
|
||
| @Override | ||
| public void markNeedSync(String controllerUuid) { | ||
| needsSyncMap.put(controllerUuid, Boolean.TRUE); | ||
| logger.debug(String.format("[sync-tracker] marked controller[uuid:%s] needsSync", controllerUuid)); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean needsSync(String controllerUuid) { | ||
| return Boolean.TRUE.equals(needsSyncMap.get(controllerUuid)); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean clearNeedSync(String controllerUuid) { | ||
| Boolean prev = needsSyncMap.remove(controllerUuid); | ||
| if (Boolean.TRUE.equals(prev)) { | ||
| logger.info(String.format("[sync-tracker] cleared needsSync for controller[uuid:%s]", controllerUuid)); | ||
| } | ||
| return Boolean.TRUE.equals(prev); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,4 +26,12 @@ public interface SdnControllerFactory { | |
| default SdnControllerDhcp getSdnControllerDhcp(String l2NetworkUuid) {return null;}; | ||
|
|
||
| default FlowChain getSyncChain() {return FlowChainBuilder.newSimpleFlowChain();}; | ||
|
|
||
| default boolean isSyncEnabled() { return false; } | ||
|
|
||
| default boolean isDryRun() { return false; } | ||
|
Comment on lines
+30
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== 查找 SdnControllerFactory 实现类 =="
rg -nP --type=java -C2 'class\s+\w+.*implements\s+[^{]*\bSdnControllerFactory\b'
echo
echo "== 查找 isSyncEnabled 覆写 =="
rg -nP --type=java -C2 '\bboolean\s+isSyncEnabled\s*\('
echo
echo "== 查找 isDryRun 覆写 =="
rg -nP --type=java -C2 '\bboolean\s+isDryRun\s*\('Repository: MatheMatrix/zstack Length of output: 8881 确认目标厂商实现需覆写 当前 🤖 Prompt for AI Agents
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment from shixin.ruan: 这两个类原来有sync功能吗?如果没有这个这个功能,本次修改也不需要增加功能 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 4702
脚本确认 我之前的 review 评论有误,抱歉造成困扰,可以忽略该建议。 (∩`-´)⊃━☆゚.*・。゚ ✏️ Learnings added
🧠 Learnings used |
||
|
|
||
| default int getSyncFailAlarmThreshold() { return 3; } | ||
|
|
||
| default int getSyncPingSuccessThreshold() { return 10; } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||
| package org.zstack.sdnController; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||||||||||||||||||||||||||||
| import org.zstack.core.cloudbus.CloudBusCallBack; | ||||||||||||||||||||||||||||||
| import org.zstack.core.cloudbus.ResourceDestinationMaker; | ||||||||||||||||||||||||||||||
| import org.zstack.core.componentloader.PluginRegistry; | ||||||||||||||||||||||||||||||
| import org.zstack.core.config.GlobalConfig; | ||||||||||||||||||||||||||||||
|
|
@@ -27,6 +28,9 @@ | |||||||||||||||||||||||||||||
| import org.zstack.utils.logging.CLogger; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentHashMap; | ||||||||||||||||||||||||||||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * Created by shixin on 06/26/2025. | ||||||||||||||||||||||||||||||
|
|
@@ -43,6 +47,10 @@ public class SdnControllerPingTracker extends PingTracker implements | |||||||||||||||||||||||||||||
| protected PluginRegistry pluginRgty; | ||||||||||||||||||||||||||||||
| @Autowired | ||||||||||||||||||||||||||||||
| protected SdnControllerManager sdnMgr; | ||||||||||||||||||||||||||||||
| @Autowired | ||||||||||||||||||||||||||||||
| private DirtySyncTracker dirtySyncTracker; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private final Map<String, AtomicInteger> consecutivePingSuccessCount = new ConcurrentHashMap<>(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| public String getResourceName() { | ||||||||||||||||||||||||||||||
| return "sdn controller"; | ||||||||||||||||||||||||||||||
|
|
@@ -81,23 +89,59 @@ public void handleReply(final String resourceUuid, MessageReply reply) { | |||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (!reply.isSuccess()) { | ||||||||||||||||||||||||||||||
| logger.warn(String.format("[SDN Ping Tracker]: unable to ping the sdn controller[uuid: %s], %s", resourceUuid, reply.getError())); | ||||||||||||||||||||||||||||||
| consecutivePingSuccessCount.remove(resourceUuid); | ||||||||||||||||||||||||||||||
| new SdnControllerBase(vo).changeSdnControllerStatus(SdnControllerStatus.Disconnected); | ||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| SdnControllerStatus oldStatus = vo.getStatus(); | ||||||||||||||||||||||||||||||
| if (oldStatus == SdnControllerStatus.Disconnected) { | ||||||||||||||||||||||||||||||
| // when reconnect successfully, it will fire event: SdnControllerStatus.Connected | ||||||||||||||||||||||||||||||
| consecutivePingSuccessCount.remove(resourceUuid); | ||||||||||||||||||||||||||||||
| ReconnectSdnControllerMsg msg = new ReconnectSdnControllerMsg(); | ||||||||||||||||||||||||||||||
| msg.setControllerUuid(resourceUuid); | ||||||||||||||||||||||||||||||
| bus.makeTargetServiceIdByResourceUuid(msg, SdnControllerConstant.SERVICE_ID, resourceUuid); | ||||||||||||||||||||||||||||||
| bus.send(msg); | ||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Ping success on Connected controller: count consecutive successes and trigger sync if needed | ||||||||||||||||||||||||||||||
| if (vo.getStatus() == SdnControllerStatus.Connected) { | ||||||||||||||||||||||||||||||
| int count = consecutivePingSuccessCount | ||||||||||||||||||||||||||||||
| .computeIfAbsent(resourceUuid, k -> new AtomicInteger(0)) | ||||||||||||||||||||||||||||||
| .incrementAndGet(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| SdnControllerFactory factory = sdnMgr.getSdnControllerFactory(vo.getVendorType()); | ||||||||||||||||||||||||||||||
| if (factory.isSyncEnabled() && dirtySyncTracker.needsSync(resourceUuid)) { | ||||||||||||||||||||||||||||||
| int threshold = factory.getSyncPingSuccessThreshold(); | ||||||||||||||||||||||||||||||
| if (count >= threshold) { | ||||||||||||||||||||||||||||||
| consecutivePingSuccessCount.remove(resourceUuid); | ||||||||||||||||||||||||||||||
| dirtySyncTracker.clearNeedSync(resourceUuid); | ||||||||||||||||||||||||||||||
| triggerSync(resourceUuid); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+117
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 将 当前先 建议修改- if (factory.isSyncEnabled() && dirtySyncTracker.needsSync(resourceUuid)) {
- int threshold = factory.getSyncPingSuccessThreshold();
- if (count >= threshold) {
- consecutivePingSuccessCount.remove(resourceUuid);
- dirtySyncTracker.clearNeedSync(resourceUuid);
- triggerSync(resourceUuid);
- }
- }
+ if (factory.isSyncEnabled()) {
+ int threshold = Math.max(1, factory.getSyncPingSuccessThreshold());
+ if (count >= threshold && dirtySyncTracker.clearNeedSync(resourceUuid)) {
+ consecutivePingSuccessCount.remove(resourceUuid);
+ triggerSync(resourceUuid);
+ }
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private void triggerSync(String controllerUuid) { | ||||||||||||||||||||||||||||||
| logger.info(String.format("[SDN Ping Tracker]: triggering sync for controller[uuid:%s] after consecutive ping successes", controllerUuid)); | ||||||||||||||||||||||||||||||
| SyncSdnControllerDataMsg msg = new SyncSdnControllerDataMsg(); | ||||||||||||||||||||||||||||||
| msg.setControllerUuid(controllerUuid); | ||||||||||||||||||||||||||||||
| bus.makeTargetServiceIdByResourceUuid(msg, SdnControllerConstant.SERVICE_ID, controllerUuid); | ||||||||||||||||||||||||||||||
| bus.send(msg, new CloudBusCallBack(null) { | ||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||
| public void run(MessageReply reply) { | ||||||||||||||||||||||||||||||
| if (!reply.isSuccess()) { | ||||||||||||||||||||||||||||||
| logger.warn(String.format("[SDN Ping Tracker]: sync failed for controller[uuid:%s], re-marking needsSync: %s", | ||||||||||||||||||||||||||||||
| controllerUuid, reply.getError())); | ||||||||||||||||||||||||||||||
| dirtySyncTracker.markNeedSync(controllerUuid); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private void trackOurs() { | ||||||||||||||||||||||||||||||
| List<String> sdnControllerUuids = Q.New(SdnControllerVO.class) | ||||||||||||||||||||||||||||||
| .select(SdnControllerVO_.uuid).listValues(); | ||||||||||||||||||||||||||||||
|
|
@@ -154,6 +198,7 @@ protected void trackHook(String resourceUuid) { | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||
| public void deleteNetworkServiceOfSdnController(String sdnControllerUuid, Completion completion) { | ||||||||||||||||||||||||||||||
| consecutivePingSuccessCount.remove(sdnControllerUuid); | ||||||||||||||||||||||||||||||
| super.untrack(sdnControllerUuid); | ||||||||||||||||||||||||||||||
| completion.success(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: MatheMatrix/zstack
Length of output: 50375
🏁 Script executed:
Repository: MatheMatrix/zstack
Length of output: 1753
🏁 Script executed:
Repository: MatheMatrix/zstack
Length of output: 106
🏁 Script executed:
Repository: MatheMatrix/zstack
Length of output: 2544
🏁 Script executed:
Repository: MatheMatrix/zstack
Length of output: 1258
🏁 Script executed:
Repository: MatheMatrix/zstack
Length of output: 897
添加
@Component注解到DirtySyncTrackerImpl以完成 Spring Bean 注册。SdnControllerManagerImpl在第 69-70 行通过@Autowired注入DirtySyncTracker接口,但当前DirtySyncTrackerImpl缺少任何 Spring Bean 注册注解(@Component、@Service等)。由于这是该接口的唯一实现,且找不到其他显式的 Bean 注册机制(XML 配置、@Configuration类或@Bean工厂方法),应用启动时将抛出UnsatisfiedDependencyException。🤖 Prompt for AI Agents