You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
4.8 KiB

package mock
import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"tera-clone/services/tera-api/internal/alarmdelivery"
"tera-clone/services/tera-api/internal/networkconfig"
)
// ReconcileStartupNetwork checks the owning daemon's committed configuration
// and actual interfaces. Pending user changes are never applied at startup.
func (s *Store) ReconcileStartupNetwork(ctx context.Context) error {
s.mu.Lock()
desired, err := networkconfig.Normalize(networkConfigForController(s.runningNetwork))
s.mu.Unlock()
if err != nil {
return err
}
status, err := s.networkController.Status(ctx)
if err != nil {
return err
}
if status.State == "rollback_failed" || status.State == "pending_confirmation" {
return errors.New("network recovery not complete")
}
if status.Current == nil || !reflect.DeepEqual(desired, *status.Current) || verifyStartupNetworkInterfaces(desired, status) != nil {
result, err := s.networkController.Apply(ctx, desired)
if err != nil {
return err
}
status, err = s.networkController.Status(ctx)
if err == nil {
err = verifyStartupNetworkInterfaces(desired, status)
}
if err != nil {
_, rollbackErr := s.networkController.Rollback(ctx, result.TransactionID)
return errors.Join(err, rollbackErr)
}
status, err = s.networkController.Confirm(ctx, result.TransactionID)
if err != nil {
return err
}
}
return verifyStartupNetworkInterfaces(desired, status)
}
func verifyStartupNetworkInterfaces(config networkconfig.Config, status networkconfig.Status) error {
for _, wanted := range config.Interfaces {
found := false
for _, actual := range status.Interfaces {
if actual.Device != wanted.Device {
continue
}
found = actual.Link != "unknown"
if config.Advanced.Mode == networkconfig.AdvancedModeSingle && wanted.AddressMode == networkconfig.AddressModeStatic && wanted.Address != "" {
matched := false
for _, address := range actual.Addresses {
if address == wanted.Address {
matched = true
}
}
if !matched {
return fmt.Errorf("network address not applied: %s", wanted.Name)
}
}
}
if !found {
return fmt.Errorf("network interface not observed: %s", wanted.Name)
}
}
return nil
}
// Policies are consumed directly from the restored running store per request.
// Validate enabled policies and credential references without sending alarms or
// attempting an external login as a side effect of startup.
func (s *Store) VerifyStartupPolicies(ctx context.Context) error {
s.mu.Lock()
defer s.mu.Unlock()
if err := ctx.Err(); err != nil {
return err
}
for _, err := range []error{validateAlarmDeliveryPolicy(s.runningAlarmDelivery), validateEmailNotification(s.runningEmail), validateRemoteSyslog(s.runningSyslog), validateExternalAuth(s.runningExternalAuth), validateLogRetention(s.runningRetention)} {
if err != nil {
return err
}
}
if s.runningEmail.Enabled && s.runningEmail.AuthEnabled && !s.secrets.Has(configSecretKey("running", "smtp/password")) {
return errors.New("SMTP credential unavailable")
}
for _, target := range s.runningSNMPTraps {
if !target.Enabled {
continue
}
prefix := "snmp/trap/" + strings.ToLower(target.TargetID)
if !s.secrets.Has(configSecretKey("running", prefix+"/credential")) {
return errors.New("SNMP trap credential unavailable")
}
if target.PrivacyProtocol != "" && target.PrivacyProtocol != "none" && !s.secrets.Has(configSecretKey("running", prefix+"/privacy")) {
return errors.New("SNMP trap privacy credential unavailable")
}
}
if s.runningExternalAuth.Enabled {
for kind, servers := range map[string][]externalAuthServerConfig{"radius": s.runningExternalAuth.RadiusServers, "tacacs": s.runningExternalAuth.TACACSServers} {
for _, server := range servers {
if server.Enabled && !s.secrets.Has(externalAuthSecretKey("running", kind, server.ServerID)) {
return errors.New("external authentication credential unavailable")
}
}
}
}
for _, rule := range s.runningAlarms {
if !rule.Enabled {
continue
}
channels, syslog, email, traps := s.availableDeliveryTargetsLocked(rule.Delivery, "startup validation")
// Disabled delivery targets are intentionally inactive; enabled targets
// must form valid requests before readiness can be reported.
if len(channels) == 0 {
continue
}
_, err := alarmdelivery.NormalizeRequest(alarmdelivery.Request{Message: "startup validation", Channels: channels, Syslog: syslog, Email: email, SNMPTraps: traps, RetryCount: s.runningAlarmDelivery.RetryCount, RetryIntervalSeconds: s.runningAlarmDelivery.RetryIntervalSeconds, QueueLimit: min(s.runningAlarmDelivery.PendingQueueLimit, 1000)})
if err != nil {
return err
}
}
return nil
}
func (s *Store) ReconcileStartupLogs(ctx context.Context) error {
s.mu.Lock()
defer s.mu.Unlock()
if err := ctx.Err(); err != nil {
return err
}
return s.applyJournalRetentionLocked(s.runningRetention)
}