mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 15:46:37 +08:00
[优化] 增加 noop apply 报告逻辑,确保在配置未变更时记录应用日志
This commit is contained in:
@@ -92,6 +92,7 @@ func (s *Service) sync(ctx context.Context, startup bool, target *protocol.Activ
|
|||||||
|
|
||||||
if currentChecksum == target.Checksum {
|
if currentChecksum == target.Checksum {
|
||||||
slog.Debug("local openresty config already up to date", "mode", mode, "version", target.Version)
|
slog.Debug("local openresty config already up to date", "mode", mode, "version", target.Version)
|
||||||
|
shouldReport := shouldReportNoopApply(snapshot, target.Version, target.Checksum)
|
||||||
if startup {
|
if startup {
|
||||||
slog.Debug("ensuring openresty runtime on startup", "version", target.Version)
|
slog.Debug("ensuring openresty runtime on startup", "version", target.Version)
|
||||||
if err = s.nginxManager.EnsureRuntime(ctx, true); err != nil {
|
if err = s.nginxManager.EnsureRuntime(ctx, true); err != nil {
|
||||||
@@ -104,6 +105,11 @@ func (s *Service) sync(ctx context.Context, startup bool, target *protocol.Activ
|
|||||||
snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy
|
snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy
|
||||||
snapshot.OpenrestyMessage = ""
|
snapshot.OpenrestyMessage = ""
|
||||||
}
|
}
|
||||||
|
if shouldReport {
|
||||||
|
if err = s.reportNoopApply(ctx, snapshot.NodeID, target.Version, target.Checksum, "", "", 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
snapshot.CurrentVersion = target.Version
|
snapshot.CurrentVersion = target.Version
|
||||||
snapshot.CurrentChecksum = target.Checksum
|
snapshot.CurrentChecksum = target.Checksum
|
||||||
clearBlockedTarget(snapshot)
|
clearBlockedTarget(snapshot)
|
||||||
@@ -152,6 +158,7 @@ func (s *Service) ForceSyncOnce(ctx context.Context, target *protocol.ActiveConf
|
|||||||
func (s *Service) applyIfNeeded(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, target *protocol.ActiveConfigMeta, config *protocol.ActiveConfigResponse) error {
|
func (s *Service) applyIfNeeded(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, target *protocol.ActiveConfigMeta, config *protocol.ActiveConfigResponse) error {
|
||||||
if currentChecksum == config.Checksum {
|
if currentChecksum == config.Checksum {
|
||||||
slog.Debug("local openresty config already up to date", "mode", mode, "version", config.Version)
|
slog.Debug("local openresty config already up to date", "mode", mode, "version", config.Version)
|
||||||
|
shouldReport := shouldReportNoopApply(snapshot, config.Version, config.Checksum)
|
||||||
if startup {
|
if startup {
|
||||||
slog.Debug("ensuring openresty runtime on startup", "version", config.Version)
|
slog.Debug("ensuring openresty runtime on startup", "version", config.Version)
|
||||||
if err := s.nginxManager.EnsureRuntime(ctx, true); err != nil {
|
if err := s.nginxManager.EnsureRuntime(ctx, true); err != nil {
|
||||||
@@ -164,6 +171,15 @@ func (s *Service) applyIfNeeded(ctx context.Context, mode string, startup bool,
|
|||||||
snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy
|
snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy
|
||||||
snapshot.OpenrestyMessage = ""
|
snapshot.OpenrestyMessage = ""
|
||||||
}
|
}
|
||||||
|
if shouldReport {
|
||||||
|
routeConfig := config.RouteConfig
|
||||||
|
if routeConfig == "" {
|
||||||
|
routeConfig = config.RenderedConfig
|
||||||
|
}
|
||||||
|
if err := s.reportNoopApply(ctx, snapshot.NodeID, config.Version, config.Checksum, checksumString(config.MainConfig), checksumString(routeConfig), len(config.SupportFiles)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
snapshot.CurrentVersion = config.Version
|
snapshot.CurrentVersion = config.Version
|
||||||
snapshot.CurrentChecksum = config.Checksum
|
snapshot.CurrentChecksum = config.Checksum
|
||||||
clearBlockedTarget(snapshot)
|
clearBlockedTarget(snapshot)
|
||||||
@@ -266,6 +282,33 @@ func (s *Service) applyIfNeeded(ctx context.Context, mode string, startup bool,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldReportNoopApply(snapshot *state.Snapshot, version string, checksum string) bool {
|
||||||
|
if snapshot == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(snapshot.CurrentVersion) != strings.TrimSpace(version) ||
|
||||||
|
strings.TrimSpace(snapshot.CurrentChecksum) != strings.TrimSpace(checksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) reportNoopApply(ctx context.Context, nodeID string, version string, checksum string, mainConfigChecksum string, routeConfigChecksum string, supportFileCount int) error {
|
||||||
|
message := "local config already matches active version; apply skipped"
|
||||||
|
if err := s.client.ReportApplyLog(ctx, protocol.ApplyLogPayload{
|
||||||
|
NodeID: nodeID,
|
||||||
|
Version: strings.TrimSpace(version),
|
||||||
|
Result: ApplyResultSuccess,
|
||||||
|
Message: message,
|
||||||
|
Checksum: strings.TrimSpace(checksum),
|
||||||
|
MainConfigChecksum: strings.TrimSpace(mainConfigChecksum),
|
||||||
|
RouteConfigChecksum: strings.TrimSpace(routeConfigChecksum),
|
||||||
|
SupportFileCount: supportFileCount,
|
||||||
|
}); err != nil {
|
||||||
|
slog.Error("report noop apply log failed", "version", version, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slog.Debug("noop apply log reported", "version", version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func outcomeError(version string, message string) error {
|
func outcomeError(version string, message string) error {
|
||||||
trimmed := strings.TrimSpace(message)
|
trimmed := strings.TrimSpace(message)
|
||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
|
|||||||
@@ -323,8 +323,11 @@ func TestSyncOnStartupRecreatesRuntimeWhenChecksumMatches(t *testing.T) {
|
|||||||
if len(manager.ensureCalls) != 1 || !manager.ensureCalls[0] {
|
if len(manager.ensureCalls) != 1 || !manager.ensureCalls[0] {
|
||||||
t.Fatal("expected startup sync to recreate runtime")
|
t.Fatal("expected startup sync to recreate runtime")
|
||||||
}
|
}
|
||||||
if len(client.reports) != 0 {
|
if len(client.reports) != 1 || client.reports[0].Result != ApplyResultSuccess {
|
||||||
t.Fatal("expected no apply report when checksum already matches")
|
t.Fatal("expected startup sync to report noop success when state is refreshed")
|
||||||
|
}
|
||||||
|
if client.reports[0].Message != "local config already matches active version; apply skipped" {
|
||||||
|
t.Fatalf("unexpected noop apply message: %q", client.reports[0].Message)
|
||||||
}
|
}
|
||||||
snapshot, err := stateStore.Load()
|
snapshot, err := stateStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -338,6 +341,78 @@ func TestSyncOnStartupRecreatesRuntimeWhenChecksumMatches(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSyncOnceReportsNoopWhenVersionChangesButChecksumMatches(t *testing.T) {
|
||||||
|
client := &fakeClient{}
|
||||||
|
stateStore := state.NewStore(filepath.Join(t.TempDir(), "state.json"))
|
||||||
|
nodeID, err := stateStore.EnsureNodeID()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("EnsureNodeID failed: %v", err)
|
||||||
|
}
|
||||||
|
if err = stateStore.Save(&state.Snapshot{
|
||||||
|
NodeID: nodeID,
|
||||||
|
CurrentVersion: "20260309-002",
|
||||||
|
CurrentChecksum: "checksum-3",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("failed to seed state: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := &fakeManager{currentChecksum: "checksum-3"}
|
||||||
|
service := New(client, manager, stateStore)
|
||||||
|
if err = service.SyncOnce(context.Background(), &protocol.ActiveConfigMeta{
|
||||||
|
Version: "20260309-003",
|
||||||
|
Checksum: "checksum-3",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("SyncOnce failed: %v", err)
|
||||||
|
}
|
||||||
|
if client.fetchCalls != 0 {
|
||||||
|
t.Fatalf("expected checksum match to skip config fetch, got %d", client.fetchCalls)
|
||||||
|
}
|
||||||
|
if len(manager.applyMainContents) != 0 {
|
||||||
|
t.Fatal("expected checksum match to skip apply")
|
||||||
|
}
|
||||||
|
if len(client.reports) != 1 || client.reports[0].Result != ApplyResultSuccess {
|
||||||
|
t.Fatalf("expected noop apply success report, got %+v", client.reports)
|
||||||
|
}
|
||||||
|
if client.reports[0].Version != "20260309-003" || client.reports[0].Checksum != "checksum-3" {
|
||||||
|
t.Fatalf("unexpected noop apply report: %+v", client.reports[0])
|
||||||
|
}
|
||||||
|
snapshot, err := stateStore.Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load state: %v", err)
|
||||||
|
}
|
||||||
|
if snapshot.CurrentVersion != "20260309-003" || snapshot.CurrentChecksum != "checksum-3" {
|
||||||
|
t.Fatalf("expected state to refresh active version, got %+v", snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncOnceDoesNotRepeatNoopReportWhenStateAlreadyMatches(t *testing.T) {
|
||||||
|
client := &fakeClient{}
|
||||||
|
stateStore := state.NewStore(filepath.Join(t.TempDir(), "state.json"))
|
||||||
|
nodeID, err := stateStore.EnsureNodeID()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("EnsureNodeID failed: %v", err)
|
||||||
|
}
|
||||||
|
if err = stateStore.Save(&state.Snapshot{
|
||||||
|
NodeID: nodeID,
|
||||||
|
CurrentVersion: "20260309-003",
|
||||||
|
CurrentChecksum: "checksum-3",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("failed to seed state: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := &fakeManager{currentChecksum: "checksum-3"}
|
||||||
|
service := New(client, manager, stateStore)
|
||||||
|
if err = service.SyncOnce(context.Background(), &protocol.ActiveConfigMeta{
|
||||||
|
Version: "20260309-003",
|
||||||
|
Checksum: "checksum-3",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("SyncOnce failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(client.reports) != 0 {
|
||||||
|
t.Fatalf("expected matching state to skip duplicate noop report, got %+v", client.reports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSyncOnStartupRecordsRuntimeFailure(t *testing.T) {
|
func TestSyncOnStartupRecordsRuntimeFailure(t *testing.T) {
|
||||||
client := &fakeClient{
|
client := &fakeClient{
|
||||||
config: protocol.ActiveConfigResponse{
|
config: protocol.ActiveConfigResponse{
|
||||||
|
|||||||
@@ -631,6 +631,22 @@ func TestPhase2AgentLifecycle(t *testing.T) {
|
|||||||
if logs.Total != 0 || len(logs.Rows) != 0 || logs.Current != 1 || logs.TotalPage != 0 {
|
if logs.Total != 0 || len(logs.Rows) != 0 || logs.Current != 1 || logs.TotalPage != 0 {
|
||||||
t.Fatalf("expected empty apply log page after delete-all cleanup, got %+v", logs)
|
t.Fatalf("expected empty apply log page after delete-all cleanup, got %+v", logs)
|
||||||
}
|
}
|
||||||
|
postDeleteApplyResp := performAgentJSONRequestWithToken(t, engine, createdNode.AgentToken, http.MethodPost, "/api/agent/apply-logs", map[string]any{
|
||||||
|
"version": activeConfig.Version,
|
||||||
|
"result": service.ApplyResultOK,
|
||||||
|
"message": "local config already matches active version; apply skipped",
|
||||||
|
"checksum": activeConfig.Checksum,
|
||||||
|
})
|
||||||
|
var postDeleteApplyLog model.ApplyLog
|
||||||
|
decodeResponseData(t, postDeleteApplyResp, &postDeleteApplyLog)
|
||||||
|
if postDeleteApplyLog.ID == 0 || postDeleteApplyLog.NodeID != createdNode.NodeID {
|
||||||
|
t.Fatalf("expected apply log to be recreated after delete-all cleanup, got %+v", postDeleteApplyLog)
|
||||||
|
}
|
||||||
|
postDeleteLogsResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/apply-logs/?node_id="+createdNode.NodeID, nil)
|
||||||
|
decodeResponseData(t, postDeleteLogsResp, &logs)
|
||||||
|
if logs.Total != 1 || len(logs.Rows) != 1 || logs.Rows[0].ID != postDeleteApplyLog.ID {
|
||||||
|
t.Fatalf("expected new apply log after delete-all cleanup, got %+v", logs)
|
||||||
|
}
|
||||||
|
|
||||||
updatedNodeResp := performJSONRequest(t, engine, adminToken, http.MethodPost, "/api/nodes/"+toString(createdNode.ID)+"/update", map[string]any{
|
updatedNodeResp := performJSONRequest(t, engine, adminToken, http.MethodPost, "/api/nodes/"+toString(createdNode.ID)+"/update", map[string]any{
|
||||||
"name": "shanghai-edge-1-renamed",
|
"name": "shanghai-edge-1-renamed",
|
||||||
|
|||||||
@@ -86,6 +86,18 @@ export function ApplyLogsPage() {
|
|||||||
const [customRetentionDays, setCustomRetentionDays] = useState('30');
|
const [customRetentionDays, setCustomRetentionDays] = useState('30');
|
||||||
const [feedback, setFeedback] = useState<FeedbackState | null>(null);
|
const [feedback, setFeedback] = useState<FeedbackState | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const nodeID = new URLSearchParams(window.location.search)
|
||||||
|
.get('node_id')
|
||||||
|
?.trim();
|
||||||
|
if (!nodeID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setNodeFilterInput(nodeID);
|
||||||
|
setNodeFilter(nodeID);
|
||||||
|
setPageNo(1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const logsQuery = useQuery({
|
const logsQuery = useQuery({
|
||||||
queryKey: applyLogsQueryKey(nodeFilter, pageNo, pageSize),
|
queryKey: applyLogsQueryKey(nodeFilter, pageNo, pageSize),
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
|
|||||||
Reference in New Issue
Block a user