127 lines
5.1 KiB
Go
127 lines
5.1 KiB
Go
package management
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// SeedDemo creates only synthetic TEST-NET data. It never calls SIP, Cell Agent, MQ, OSS, or cloud APIs.
|
|
func (s *Store) SeedDemo(ctx context.Context) error {
|
|
now := time.Now().UTC()
|
|
if err := s.UpsertEgressPool(ctx, "egress-main", "北京测试出口", []string{"123.56.71.98"}, "unknown", "mock", &now); err != nil {
|
|
return err
|
|
}
|
|
if _, err := s.GetProvider(ctx, "demo-provider"); errors.Is(err, sql.ErrNoRows) {
|
|
if _, _, err := s.UpsertProvider(ctx, "demo-provider", ProviderInput{DisplayName: "演示 SIP 供应商", Notes: "synthetic mock fixture", Lifecycle: "active"}, 0, "fixture:demo", "fixture-provider"); err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
if err := s.SetEgressPoolProvider(ctx, "egress-main", "demo-provider"); err != nil {
|
|
return err
|
|
}
|
|
cells := []struct{ id, name, url string }{{"cell-a", "demo-cell-a", "https://cell-a.mock.invalid"}, {"cell-b", "demo-cell-b", "https://cell-b.mock.invalid"}}
|
|
for _, item := range cells {
|
|
if _, err := s.GetCell(ctx, item.id); errors.Is(err, sql.ErrNoRows) {
|
|
if _, _, err := s.UpsertCell(ctx, item.id, CellConfig{EgressPoolID: "egress-main", CodecCapabilities: []string{"PCMA", "PCMU"}, Status: "healthy", MaxConcurrency: 100, ManagementURL: item.url}, 0, "fixture:demo", "fixture-cell-"+item.id, item.id, item.name, "cn-beijing"); err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
if err := s.AdvanceCellBoot(ctx, item.id, "demo-boot-1"); err != nil {
|
|
return err
|
|
}
|
|
obs := ObservationInput{ObservationID: "obs-" + item.id, CellID: item.id, BootID: "demo-boot-1", Sequence: 1, ObservedAt: now, ReceivedAt: now, Source: "mock", States: map[string]any{"cell_agent": "healthy", "asterisk": "healthy", "ari": "healthy", "registration": "registered", "media": "healthy"}, Occupancy: map[string]any{"answered": 0, "cps": 0, "capacity": 100}, SampleID: "demo"}
|
|
if err := s.InsertObservation(ctx, obs); err != nil {
|
|
if appErr, ok := err.(*AppError); !ok || appErr.Code != "STALE_OBSERVATION" {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
trunk, err := s.GetTrunk(ctx, "demo-trunk", ModeMock)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
cfg := TrunkConfig{ProviderID: "demo-provider", DisplayName: "演示主线路", Enabled: true, Sip: SipConfig{Host: "198.51.100.20", Port: 5060, Transport: "udp", AuthMode: "ip"}, CodecProfile: CodecProfile{Allowed: []string{"PCMA"}, Preferred: "PCMA"}, CallerIDs: []string{"BD93205882"}, DialPrefix: "7089", EgressPoolID: "egress-main", MaxConcurrency: 100, MaxCPS: 5}
|
|
trunk, _, err = s.PutTrunk(ctx, "demo-trunk", cfg, 0, "fixture:demo", "fixture-trunk")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
if trunk.ActiveRevision == 0 {
|
|
cfg, err := s.getTrunkConfig(ctx, "demo-trunk", trunk.LatestRevision)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cells, err := s.compatibleCellIDs(ctx, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opID := "fixture-publish-demo"
|
|
if err := s.ResumeOrStagePublication(ctx, "demo-trunk", trunk.LatestRevision, cfg, cells, opID, "fixture"); err != nil {
|
|
return err
|
|
}
|
|
digest, _, _ := hashJSON(cfg)
|
|
for _, cellID := range cells {
|
|
if err := s.UpdatePublication(ctx, "demo-trunk", trunk.LatestRevision, cellID, "applied", "", trunk.LatestRevision, digest, opID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
ok, err := s.FinalizePublication(ctx, "demo-trunk", trunk.LatestRevision, cfg, opID, "fixture:demo", "fixture-publish-demo", "publish")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return errors.New("demo publication did not finalize")
|
|
}
|
|
}
|
|
return seedAttempts(ctx, s, now)
|
|
}
|
|
|
|
func seedAttempts(ctx context.Context, s *Store, now time.Time) error {
|
|
for i := 0; i < 7; i++ {
|
|
started := now.Add(-time.Duration(7-i) * time.Minute)
|
|
a := Attempt{AttemptID: "demo-attempt-" + string(rune('a'+i)), ExecutionID: "demo-execution", CallID: "demo-call-" + string(rune('a'+i)), TenantKey: "demo-tenant", ProviderID: "demo-provider", TrunkID: "demo-trunk", CellID: []string{"cell-a", "cell-b"}[i%2], EgressPoolID: "egress-main", ConfigRevision: 1, Mode: ModeMock, DialedNumber: "15003164745", AttemptStartedAt: &started, OriginStatus: "confirmed", TerminationReason: "no_answer", AIStatus: "not_started", Source: "mock", FactVersion: 1, UpdatedAt: started.Add(2 * time.Minute)}
|
|
if i == 6 {
|
|
a.OriginStatus = "rejected"
|
|
a.AttemptStartedAt = nil
|
|
a.UpdatedAt = now.Add(-30 * time.Second)
|
|
a.TerminationReason = "admission_rejected"
|
|
}
|
|
if i < 2 {
|
|
answered := started.Add(8 * time.Second)
|
|
ended := answered.Add(time.Duration(60+i*60) * time.Second)
|
|
a.AnsweredAt = &answered
|
|
a.EndedAt = &ended
|
|
a.TerminationReason = "completed"
|
|
a.AIStatus = "completed"
|
|
}
|
|
if i >= 2 && i <= 4 {
|
|
ended := started.Add(40 * time.Second)
|
|
a.EndedAt = &ended
|
|
switch i {
|
|
case 2:
|
|
a.TerminationReason = "busy"
|
|
case 3:
|
|
a.TerminationReason = "no_answer"
|
|
default:
|
|
a.EndedAt = nil
|
|
a.TerminationReason = "in_progress"
|
|
}
|
|
}
|
|
if i == 5 {
|
|
ended := started.Add(35 * time.Second)
|
|
a.EndedAt = &ended
|
|
a.TerminationReason = "network_error"
|
|
}
|
|
if err := s.UpsertAttempt(ctx, a); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|