82 lines
3.3 KiB
Go
82 lines
3.3 KiB
Go
package dispatcher
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"git.ipao.vip/rogee/go-sip/internal/configread"
|
|
)
|
|
|
|
// refreshSIP processes only a durable sip.config notification. A pending
|
|
// revision stays fenced until existing calls drain and Agent confirms loading.
|
|
func (r *Runtime) refreshSIP(ctx context.Context, approvedSIP *configread.SIP) error {
|
|
if r == nil || approvedSIP == nil || r.Bootstrap.Client == nil || r.Bootstrap.Store == nil || r.Bootstrap.VerifySIP == nil || r.Logger == nil {
|
|
return errors.New("SIP refresh requires durable state and applied-revision verifier")
|
|
}
|
|
_, pending, err := r.Bootstrap.Store.SIPState(r.Bootstrap.DispatcherID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if pending == 0 {
|
|
return nil
|
|
}
|
|
occupied, err := r.Bootstrap.Store.OccupiedCalls(r.Bootstrap.DispatcherID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if occupied != 0 {
|
|
r.Logger.Debug("SIP reload waits for confirmed old-call drain", "dispatcher_id", r.Bootstrap.DispatcherID, "pending_revision", pending, "occupied_calls", occupied)
|
|
return nil
|
|
}
|
|
current, err := r.Bootstrap.Client.ReadSIP(ctx)
|
|
if err != nil {
|
|
return r.closeSIPAdmission(fmt.Errorf("read approved SIP full snapshot: %w", err))
|
|
}
|
|
current, err = r.Bootstrap.Store.PrepareSIP(current)
|
|
if err != nil {
|
|
return r.closeSIPAdmission(fmt.Errorf("persist local SIP load generation: %w", err))
|
|
}
|
|
if r.Bootstrap.ApplySIP != nil {
|
|
r.Logger.Info("applying pending SIP snapshot after calls drained", "dispatcher_id", r.Bootstrap.DispatcherID, "revision", current.Revision)
|
|
if err := r.Bootstrap.ApplySIP(ctx, current); err != nil {
|
|
r.Logger.Warn("approved SIP update remains fenced until native reload confirms", "dispatcher_id", r.Bootstrap.DispatcherID, "revision", current.Revision, "error", err)
|
|
return nil
|
|
}
|
|
}
|
|
if err := r.Bootstrap.VerifySIP(ctx, current); err != nil {
|
|
r.Logger.Debug("SIP reload waits for actual Agent/Asterisk revision", "dispatcher_id", r.Bootstrap.DispatcherID, "pending_revision", pending, "error", err)
|
|
return nil
|
|
}
|
|
r.gate.Lock()
|
|
defer r.gate.Unlock()
|
|
_, latestPending, err := r.Bootstrap.Store.SIPState(r.Bootstrap.DispatcherID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if latestPending > current.Revision {
|
|
r.Logger.Info("newer SIP notification arrived during reload; admission remains closed", "dispatcher_id", r.Bootstrap.DispatcherID, "pending_revision", latestPending)
|
|
return nil
|
|
}
|
|
if err := r.Bootstrap.Store.RebindSIP(r.Bootstrap.DispatcherID, current); err != nil {
|
|
return r.closeSIPAdmission(fmt.Errorf("bind approved SIP to existing task snapshots: %w", err))
|
|
}
|
|
// This transaction rechecks drain, pending revision and every binding.
|
|
if err := r.Bootstrap.Store.MarkReadyForSIP(r.Bootstrap.DispatcherID, current.Revision); err != nil {
|
|
return r.closeSIPAdmission(fmt.Errorf("verify and commit reloaded SIP revision: %w", err))
|
|
}
|
|
*approvedSIP = current
|
|
if r.Bootstrap.SIP != nil {
|
|
*r.Bootstrap.SIP = current
|
|
}
|
|
r.Logger.Info("approved SIP revision is applied after confirmed drain", "dispatcher_id", r.Bootstrap.DispatcherID, "revision", current.Revision)
|
|
return nil
|
|
}
|
|
|
|
func (r *Runtime) closeSIPAdmission(cause error) error {
|
|
if err := r.Bootstrap.Store.CloseAdmission(r.Bootstrap.DispatcherID); err != nil {
|
|
return errors.Join(cause, fmt.Errorf("close admission after SIP refresh failure: %w", err))
|
|
}
|
|
return cause
|
|
}
|