package topology import ( "encoding/json" "fmt" "os" "sync" ) // Store provides thread-safe access to the mesh topology state. // The DaemonSet writes topology updates, and the CNI plugin reads them. // State is persisted to a JSON file on disk. type Store struct { mu sync.RWMutex filePath string topo *MeshTopology } // NewStore creates a topology store backed by the given file path. func NewStore(filePath string) *Store { return &Store{ filePath: filePath, topo: &MeshTopology{}, } } // LoadFromFile reads a MeshTopology from a JSON file. // Used by the CNI plugin to read the DaemonSet-written state. func LoadFromFile(path string) (*MeshTopology, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read topology file %s: %w", path, err) } var topo MeshTopology if err := json.Unmarshal(data, &topo); err != nil { return nil, fmt.Errorf("failed to parse topology file: %w", err) } return &topo, nil } // Get returns a copy of the current topology. func (s *Store) Get() MeshTopology { s.mu.RLock() defer s.mu.RUnlock() return *s.topo } // Update atomically updates the topology and persists to disk. func (s *Store) Update(fn func(*MeshTopology)) error { s.mu.Lock() defer s.mu.Unlock() fn(s.topo) return s.persist() } func (s *Store) persist() error { data, err := json.MarshalIndent(s.topo, "", " ") if err != nil { return fmt.Errorf("failed to marshal topology: %w", err) } // Atomic write: write to temp file, then rename. tmpPath := s.filePath + ".tmp" if err := os.WriteFile(tmpPath, data, 0644); err != nil { return fmt.Errorf("failed to write topology file: %w", err) } if err := os.Rename(tmpPath, s.filePath); err != nil { return fmt.Errorf("failed to rename topology file: %w", err) } return nil }