소스 검색

fix(mtproto): zero sidecar quotas only for clients whose usage was reset

Invariant: the sidecar's quota counter for a client is zeroed exactly when
the panel zeroes that client's usage. InboundService.ResetAllTraffics
resets only inbound counters, yet it cleared every MTProto client's
sidecar quota - on the master and, through its node propagation, on every
node - handing out a fresh quota while the panel still counted the old
usage. ResetAllClientTraffics for one inbound likewise cleared the quotas
of MTProto clients on every other inbound.

The inbound-level reset no longer touches sidecar quotas, and the
per-inbound client reset zeroes only the clients it reset.
MHSanaei 13 시간 전
부모
커밋
fb7418f7bd
3개의 변경된 파일과 45개의 추가작업 그리고 6개의 파일을 삭제
  1. 8 5
      internal/web/service/client_traffic.go
  2. 0 1
      internal/web/service/inbound_traffic.go
  3. 37 0
      internal/web/service/mtproto_quota_reset_test.go

+ 8 - 5
internal/web/service/client_traffic.go

@@ -126,22 +126,24 @@ func (s *ClientService) BulkResetTraffic(inboundSvc *InboundService, emails []st
 
 func (s *ClientService) ResetAllClientTraffics(inboundSvc *InboundService, id int) error {
 	var resetNodes []int
+	var resetEmails []string
 	err := submitTrafficWrite(func() error {
 		var inner error
-		resetNodes, inner = s.resetAllClientTrafficsLocked(id)
+		resetEmails, resetNodes, inner = s.resetAllClientTrafficsLocked(id)
 		return inner
 	})
 	if err == nil {
-		inboundSvc.resetAllMtprotoQuotas()
+		inboundSvc.resetMtprotoClientQuotas(resetEmails)
 		inboundSvc.deliverNodeResetsNow(resetNodes)
 	}
 	return err
 }
 
-func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]int, error) {
+func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]string, []int, error) {
 	db := database.GetDB()
 	now := time.Now().Unix() * 1000
 	var resetNodes []int
+	var reset []string
 
 	if err := db.Transaction(func(tx *gorm.DB) error {
 		// client_traffics.inbound_id is stale: it reflects the inbound the row was
@@ -164,6 +166,7 @@ func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]int, error) {
 		if len(resetEmails) == 0 {
 			return nil
 		}
+		reset = resetEmails
 
 		if err := adjustGroupBaselinesForRemovedTraffic(tx, resetEmails); err != nil {
 			return err
@@ -204,9 +207,9 @@ func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]int, error) {
 
 		return result.Error
 	}); err != nil {
-		return nil, err
+		return nil, nil, err
 	}
-	return resetNodes, nil
+	return reset, resetNodes, nil
 }
 
 func (s *ClientService) ResetAllTraffics() (bool, error) {

+ 0 - 1
internal/web/service/inbound_traffic.go

@@ -783,7 +783,6 @@ func (s *InboundService) ResetAllTraffics() error {
 	})
 	if err == nil {
 		s.propagateResetAllTrafficsToNodes()
-		s.resetAllMtprotoQuotas()
 	}
 	return err
 }

+ 37 - 0
internal/web/service/mtproto_quota_reset_test.go

@@ -78,6 +78,15 @@ func TestPanelResetsZeroSidecarQuota(t *testing.T) {
 			t.Fatalf("sidecar quota resets %v, want [mtga]", got)
 		}
 	})
+	t.Run("inbound clients", func(t *testing.T) {
+		ib, logPath := startQuotaSidecar(t, 46207, model.Client{Enable: true})
+		if err := (&ClientService{}).ResetAllClientTraffics(&InboundService{}, ib.Id); err != nil {
+			t.Fatalf("ResetAllClientTraffics: %v", err)
+		}
+		if got := quotaResets(t, logPath); !slices.Equal(got, []string{"mtga", "mtgb"}) {
+			t.Fatalf("sidecar quota resets %v, want [mtga mtgb]", got)
+		}
+	})
 	t.Run("reset all", func(t *testing.T) {
 		_, logPath := startQuotaSidecar(t, 46202, model.Client{Enable: true})
 		if _, err := (&ClientService{}).ResetAllTraffics(); err != nil {
@@ -98,3 +107,31 @@ func TestPanelResetsZeroSidecarQuota(t *testing.T) {
 		}
 	})
 }
+
+// Resetting inbound counters leaves every client's usage in place, so the
+// sidecar's quota counters must stay too or clients get their quota again free.
+func TestInboundResetAllKeepsSidecarQuota(t *testing.T) {
+	_, logPath := startQuotaSidecar(t, 46204, model.Client{Enable: true})
+	if err := (&InboundService{}).ResetAllTraffics(); err != nil {
+		t.Fatalf("ResetAllTraffics: %v", err)
+	}
+	if got := quotaResets(t, logPath); len(got) != 0 {
+		t.Fatalf("inbound reset zeroed sidecar quotas %v, want none", got)
+	}
+}
+
+// Resetting one inbound's clients zeroes only their sidecar quotas, not those
+// of MTProto clients whose usage the reset left in place.
+func TestInboundClientResetKeepsOtherSidecarQuotas(t *testing.T) {
+	_, logPath := startQuotaSidecar(t, 46205, model.Client{Enable: true})
+	other := mkInbound(t, 46206, model.VLESS, clientsSettings(t, []model.Client{{Email: "vless-only", ID: "11111111-1111-1111-1111-1111111111ab", Enable: true}}))
+	if err := (&ClientService{}).SyncInbound(nil, other.Id, []model.Client{{Email: "vless-only", ID: "11111111-1111-1111-1111-1111111111ab", Enable: true}}); err != nil {
+		t.Fatalf("SyncInbound: %v", err)
+	}
+	if err := (&ClientService{}).ResetAllClientTraffics(&InboundService{}, other.Id); err != nil {
+		t.Fatalf("ResetAllClientTraffics: %v", err)
+	}
+	if got := quotaResets(t, logPath); len(got) != 0 {
+		t.Fatalf("resetting another inbound zeroed sidecar quotas %v, want none", got)
+	}
+}