Files
rogee b6d0af1a56
management-images / build-and-publish (push) Successful in 9m15s
docs: add one-click management deployment and image workflow
2026-09-16 17:57:05 +08:00

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
}