feat: support verified bulk load-test setup

This commit is contained in:
richjohnrambo
2026-09-27 01:34:39 +08:00
parent 021169555a
commit b3b5dd55a6
8 changed files with 272 additions and 47 deletions
+69
View File
@@ -29,6 +29,8 @@ type Config struct {
P2PInterval string `json:"p2p_interval"`
DrainP2P string `json:"drain_p2p"`
DrainGroup string `json:"drain_group"`
PhonePrefix string `json:"registration_phone_prefix"`
VerifyCodeEnv string `json:"verification_code_env"`
}
func loadConfig(path string) (*Config, error) {
@@ -75,6 +77,14 @@ func loadConfig(path string) (*Config, error) {
return nil, fmt.Errorf("at least one endpoint is required for machine %s", machine)
}
}
if cfg.PhonePrefix != "" {
if len(cfg.PhonePrefix) != 7 || strings.Trim(cfg.PhonePrefix, "0123456789") != "" {
return nil, errors.New("registration_phone_prefix must contain exactly 7 digits")
}
if cfg.UsersPerMachine > 4999 {
return nil, errors.New("registration phone suffix supports at most 4999 users per machine")
}
}
return &cfg, nil
}
@@ -102,6 +112,18 @@ func (c *Config) password() (string, error) {
return value, nil
}
func (c *Config) verificationCode() (string, error) {
name := c.VerifyCodeEnv
if name == "" {
name = "IM_VERIFICATION_CODE"
}
value := strings.TrimSpace(os.Getenv(name))
if c.PhonePrefix != "" && value == "" {
return "", fmt.Errorf("environment variable %s is required", name)
}
return value, nil
}
func configDuration(value string) (time.Duration, error) {
d, err := time.ParseDuration(value)
if err != nil || d <= 0 {
@@ -122,6 +144,7 @@ type AccountRecord struct {
Machine string `json:"machine"`
Index int `json:"index"`
Username string `json:"username"`
Phone string `json:"phone,omitempty"`
UserID string `json:"user_id"`
Token string `json:"token"`
TokenExpires time.Time `json:"token_expires,omitempty"`
@@ -133,10 +156,12 @@ func newManifest(cfg *Config) *Manifest {
m := &Manifest{Version: manifestVersion, Tenant: cfg.Tenant, CreatedAt: time.Now().UTC()}
for _, machine := range []string{"a", "b"} {
for i := 1; i <= cfg.UsersPerMachine; i++ {
phone, _ := registrationPhone(cfg, machine, i)
m.Accounts = append(m.Accounts, AccountRecord{
Machine: machine,
Index: i,
Username: fmt.Sprintf("lt%s%04d", machine, i),
Phone: phone,
Endpoint: cfg.Endpoints[machine][(i-1)%len(cfg.Endpoints[machine])],
})
}
@@ -158,10 +183,54 @@ func applyConfigEndpoints(cfg *Config, manifest *Manifest) error {
return fmt.Errorf("invalid endpoint assignment for %s", accountKey(account.Machine, account.Index))
}
account.Endpoint = endpoints[(account.Index-1)%len(endpoints)]
phone, err := registrationPhone(cfg, account.Machine, account.Index)
if err != nil {
return err
}
account.Phone = phone
}
return nil
}
func reconcileManifest(cfg *Config, existing *Manifest) (*Manifest, error) {
if existing.Tenant != cfg.Tenant {
return nil, fmt.Errorf("manifest tenant=%s config tenant=%s", existing.Tenant, cfg.Tenant)
}
desired := newManifest(cfg)
if len(existing.Accounts) > len(desired.Accounts) {
return nil, fmt.Errorf("refusing to shrink manifest accounts from %d to %d", len(existing.Accounts), len(desired.Accounts))
}
desired.GroupTopic = existing.GroupTopic
desired.CreatedAt = existing.CreatedAt
old := manifestAccountMap(existing)
for i := range desired.Accounts {
account := &desired.Accounts[i]
if previous := old[accountKey(account.Machine, account.Index)]; previous != nil {
account.UserID = previous.UserID
account.Token = previous.Token
account.TokenExpires = previous.TokenExpires
account.P2PTopic = previous.P2PTopic
}
}
return desired, nil
}
func registrationPhone(cfg *Config, machine string, index int) (string, error) {
if cfg.PhonePrefix == "" {
return "", nil
}
ordinal := index
if machine == "b" {
ordinal += 5000
} else if machine != "a" {
return "", fmt.Errorf("invalid machine %q", machine)
}
if index <= 0 || ordinal > 9999 {
return "", fmt.Errorf("invalid phone ordinal %d", ordinal)
}
return fmt.Sprintf("%s%04d", cfg.PhonePrefix, ordinal), nil
}
func loadManifest(path string) (*Manifest, error) {
raw, err := os.ReadFile(path)
if err != nil {