mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 09:37:53 +00:00
fix(seeder): improve key idempotency and log names
Trim whitespace in provider model lists, format provider names as `group#keyID` to match DP logs, and skip existing API keys during seeding (deleting on reset) to keep runs idempotent and summaries accurate
This commit is contained in:
@@ -198,12 +198,16 @@ type ProviderGroupResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// GetModelsSlice returns the Models field as a slice
|
||||
// GetModelsSlice returns the Models field as a slice with trimmed whitespace
|
||||
func (p *ProviderGroupResponse) GetModelsSlice() []string {
|
||||
if p.Models == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(p.Models, ",")
|
||||
parts := strings.Split(p.Models, ",")
|
||||
for i, part := range parts {
|
||||
parts[i] = strings.TrimSpace(part)
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func (c *Client) ListProviderGroups() ([]ProviderGroupResponse, error) {
|
||||
@@ -472,6 +476,22 @@ func (c *Client) CreateKey(masterID uint, req KeyRequest) (*KeyResponse, error)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListKeys(masterID uint) ([]KeyResponse, error) {
|
||||
data, err := c.get(fmt.Sprintf("/admin/masters/%d/keys", masterID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result []KeyResponse
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal keys: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteKey(masterID, keyID uint) error {
|
||||
return c.delete(fmt.Sprintf("/admin/masters/%d/keys/%d", masterID, keyID))
|
||||
}
|
||||
|
||||
// --- Log API ---
|
||||
|
||||
type LogRequest struct {
|
||||
|
||||
@@ -407,14 +407,20 @@ func (g *Generator) GenerateUsageSamples(ctx UsageSampleContext) []LogRequest {
|
||||
// For historical data, we would need direct DB access.
|
||||
// Current implementation creates logs at "now" time.
|
||||
|
||||
// Format provider_name as group#keyID to match DP log format
|
||||
providerName := group.Name
|
||||
if providerID > 0 {
|
||||
providerName = fmt.Sprintf("%s#%d", group.Name, providerID)
|
||||
}
|
||||
|
||||
result = append(result, LogRequest{
|
||||
Group: master.Group,
|
||||
MasterID: master.ID,
|
||||
KeyID: keyID,
|
||||
Model: modelName, // Fixed: use Model not ModelName
|
||||
ProviderID: providerID, // Fixed: use APIKey ID not group ID
|
||||
Model: modelName,
|
||||
ProviderID: providerID,
|
||||
ProviderType: group.Type,
|
||||
ProviderName: group.Name,
|
||||
ProviderName: providerName,
|
||||
StatusCode: statusCode,
|
||||
LatencyMs: latencyMs,
|
||||
TokensIn: tokensIn,
|
||||
|
||||
@@ -172,16 +172,62 @@ func (s *Seeder) seedProviders() error {
|
||||
gen := NewGenerator(s.rng, s.seederTag, s.cfg.Profile)
|
||||
seededProviders = make(map[uint][]APIKeyResponse)
|
||||
|
||||
// Get existing API keys for idempotency check
|
||||
existingKeys, err := s.client.ListAPIKeys()
|
||||
if err != nil {
|
||||
if s.cfg.DryRun {
|
||||
existingKeys = []APIKeyResponse{}
|
||||
fmt.Printf(" [DRY-RUN] Unable to list existing API keys, proceeding anyway\n")
|
||||
} else {
|
||||
return fmt.Errorf("list api keys: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Build map of existing keys by group_id -> api_key prefix (for matching seeder-generated keys)
|
||||
existingByGroup := make(map[uint]map[string]APIKeyResponse)
|
||||
for _, key := range existingKeys {
|
||||
if existingByGroup[key.GroupID] == nil {
|
||||
existingByGroup[key.GroupID] = make(map[string]APIKeyResponse)
|
||||
}
|
||||
// Store by full key for exact match
|
||||
existingByGroup[key.GroupID][key.APIKey] = key
|
||||
}
|
||||
|
||||
for _, group := range seededProviderGroups {
|
||||
providers := gen.GenerateProviders(group.ID, group.Name, s.profile.ProvidersPerGroup)
|
||||
groupProviders := make([]APIKeyResponse, 0, len(providers))
|
||||
|
||||
createdCount := 0
|
||||
skippedCount := 0
|
||||
|
||||
for _, provider := range providers {
|
||||
// Check if this exact key already exists
|
||||
if groupKeys, ok := existingByGroup[group.ID]; ok {
|
||||
if existingKey, exists := groupKeys[provider.APIKey]; exists {
|
||||
// Key already exists
|
||||
if s.cfg.Reset && s.matchesSeederPrefix(group.Name) {
|
||||
// Delete for reset
|
||||
if err := s.client.DeleteAPIKey(existingKey.ID); err != nil {
|
||||
return fmt.Errorf("delete api key for group %s: %w", group.Name, err)
|
||||
}
|
||||
if s.cfg.Verbose {
|
||||
fmt.Printf(" ✗ api key %d (deleted for reset)\n", existingKey.ID)
|
||||
}
|
||||
} else {
|
||||
// Skip existing
|
||||
s.summary.Providers.Skipped++
|
||||
skippedCount++
|
||||
groupProviders = append(groupProviders, existingKey)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
created, err := s.client.CreateAPIKey(provider)
|
||||
if err != nil {
|
||||
if apiErr, ok := err.(*APIError); ok && apiErr.IsConflict() {
|
||||
s.summary.Providers.Skipped++
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("create provider for group %s: %w", group.Name, err)
|
||||
@@ -196,7 +242,7 @@ func (s *Seeder) seedProviders() error {
|
||||
seededProviders[group.ID] = groupProviders
|
||||
|
||||
if s.cfg.Verbose || s.cfg.DryRun {
|
||||
fmt.Printf(" ✓ %s: %d providers created\n", group.Name, createdCount)
|
||||
fmt.Printf(" ✓ %s: %d providers created, %d skipped\n", group.Name, createdCount, skippedCount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,9 +437,49 @@ func (s *Seeder) seedKeys() error {
|
||||
seededKeys = make(map[uint][]KeyResponse)
|
||||
|
||||
for _, master := range seededMasters {
|
||||
keys := gen.GenerateKeys(s.profile.KeysPerMaster)
|
||||
// Get existing keys for this master for idempotency
|
||||
existingKeys, err := s.client.ListKeys(master.ID)
|
||||
if err != nil {
|
||||
if s.cfg.DryRun {
|
||||
existingKeys = []KeyResponse{}
|
||||
if s.cfg.Verbose {
|
||||
fmt.Printf(" [DRY-RUN] Unable to list existing keys for master %s\n", master.Name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("list keys for master %s: %w", master.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
masterKeys := make([]KeyResponse, 0, len(keys))
|
||||
// For reset mode, delete existing keys that belong to seeder masters
|
||||
if s.cfg.Reset && s.matchesSeederPrefix(master.Name) && len(existingKeys) > 0 {
|
||||
for _, key := range existingKeys {
|
||||
if err := s.client.DeleteKey(master.ID, key.ID); err != nil {
|
||||
return fmt.Errorf("delete key %d for master %s: %w", key.ID, master.Name, err)
|
||||
}
|
||||
if s.cfg.Verbose {
|
||||
fmt.Printf(" ✗ key %d (deleted for reset)\n", key.ID)
|
||||
}
|
||||
}
|
||||
existingKeys = []KeyResponse{}
|
||||
}
|
||||
|
||||
// Check if we already have enough keys (idempotency)
|
||||
targetCount := s.profile.KeysPerMaster
|
||||
if len(existingKeys) >= targetCount {
|
||||
if s.cfg.Verbose {
|
||||
fmt.Printf(" ○ %s: %d keys (exists, skipped)\n", master.Name, len(existingKeys))
|
||||
}
|
||||
s.summary.Keys.Skipped += targetCount
|
||||
seededKeys[master.ID] = existingKeys[:targetCount]
|
||||
continue
|
||||
}
|
||||
|
||||
// Create only the missing keys
|
||||
keysToCreate := targetCount - len(existingKeys)
|
||||
keys := gen.GenerateKeys(keysToCreate)
|
||||
|
||||
masterKeys := make([]KeyResponse, 0, targetCount)
|
||||
masterKeys = append(masterKeys, existingKeys...)
|
||||
createdCount := 0
|
||||
|
||||
for _, key := range keys {
|
||||
@@ -415,7 +501,7 @@ func (s *Seeder) seedKeys() error {
|
||||
seededKeys[master.ID] = masterKeys
|
||||
|
||||
if s.cfg.Verbose || s.cfg.DryRun {
|
||||
fmt.Printf(" ✓ %s: %d keys created\n", master.Name, createdCount)
|
||||
fmt.Printf(" ✓ %s: %d keys created, %d existing\n", master.Name, createdCount, len(existingKeys))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user