// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/hashicorp/consul/agent/structs" ) const tableFederationStates = "federation-states" func federationStateTableSchema() *memdb.TableSchema { return &memdb.TableSchema{ Name: tableFederationStates, Indexes: map[string]*memdb.IndexSchema{ indexID: { Name: indexID, AllowMissing: false, Unique: true, Indexer: &memdb.StringFieldIndex{ Field: "Datacenter", Lowercase: true, }, }, }, } } // FederationStates is used to pull all the federation states for the snapshot. func (s *Snapshot) FederationStates() ([]*structs.FederationState, error) { configs, err := s.tx.Get(tableFederationStates, "id") if err != nil { return nil, err } var ret []*structs.FederationState for wrapped := configs.Next(); wrapped != nil; wrapped = configs.Next() { ret = append(ret, wrapped.(*structs.FederationState)) } return ret, nil } // FederationState is used when restoring from a snapshot. func (s *Restore) FederationState(g *structs.FederationState) error { // Insert if err := s.tx.Insert(tableFederationStates, g); err != nil { return fmt.Errorf("failed restoring federation state object: %s", err) } if err := indexUpdateMaxTxn(s.tx, g.ModifyIndex, tableFederationStates); err != nil { return fmt.Errorf("failed updating index: %s", err) } return nil } func (s *Store) FederationStateBatchSet(idx uint64, configs structs.FederationStates) error { tx := s.db.WriteTxn(idx) defer tx.Abort() for _, config := range configs { if err := federationStateSetTxn(tx, idx, config); err != nil { return err } } return tx.Commit() } // FederationStateSet is called to do an upsert of a given federation state. func (s *Store) FederationStateSet(idx uint64, config *structs.FederationState) error { tx := s.db.WriteTxn(idx) defer tx.Abort() if err := federationStateSetTxn(tx, idx, config); err != nil { return err } return tx.Commit() } // federationStateSetTxn upserts a federation state inside of a transaction. func federationStateSetTxn(tx WriteTxn, idx uint64, config *structs.FederationState) error { if config.Datacenter == "" { return fmt.Errorf("missing datacenter on federation state") } // Check for existing. var existing *structs.FederationState existingRaw, err := tx.First(tableFederationStates, "id", config.Datacenter) if err != nil { return fmt.Errorf("failed federation state lookup: %s", err) } if existingRaw != nil { existing = existingRaw.(*structs.FederationState) } // Set the indexes if existing != nil { config.CreateIndex = existing.CreateIndex config.ModifyIndex = idx } else { config.CreateIndex = idx config.ModifyIndex = idx } if config.PrimaryModifyIndex == 0 { // Since replication ordinarily would set this value for us, we can // assume this is a write to the primary datacenter's federation state // so we can just duplicate the new modify index. config.PrimaryModifyIndex = idx } // Insert the federation state and update the index if err := tx.Insert(tableFederationStates, config); err != nil { return fmt.Errorf("failed inserting federation state: %s", err) } if err := tx.Insert(tableIndex, &IndexEntry{tableFederationStates, idx}); err != nil { return fmt.Errorf("failed updating index: %v", err) } return nil } // FederationStateGet is called to get a federation state. func (s *Store) FederationStateGet(ws memdb.WatchSet, datacenter string) (uint64, *structs.FederationState, error) { tx := s.db.Txn(false) defer tx.Abort() return federationStateGetTxn(tx, ws, datacenter) } func federationStateGetTxn(tx ReadTxn, ws memdb.WatchSet, datacenter string) (uint64, *structs.FederationState, error) { // Get the index idx := maxIndexTxn(tx, tableFederationStates) // Get the existing contents. watchCh, existing, err := tx.FirstWatch(tableFederationStates, "id", datacenter) if err != nil { return 0, nil, fmt.Errorf("failed federation state lookup: %s", err) } ws.Add(watchCh) if existing == nil { return idx, nil, nil } config, ok := existing.(*structs.FederationState) if !ok { return 0, nil, fmt.Errorf("federation state %q is an invalid type: %T", datacenter, config) } return idx, config, nil } // FederationStateList is called to get all federation state objects. func (s *Store) FederationStateList(ws memdb.WatchSet) (uint64, []*structs.FederationState, error) { tx := s.db.Txn(false) defer tx.Abort() return federationStateListTxn(tx, ws) } func federationStateListTxn(tx ReadTxn, ws memdb.WatchSet) (uint64, []*structs.FederationState, error) { // Get the index idx := maxIndexTxn(tx, tableFederationStates) iter, err := tx.Get(tableFederationStates, "id") if err != nil { return 0, nil, fmt.Errorf("failed federation state lookup: %s", err) } ws.Add(iter.WatchCh()) var results []*structs.FederationState for v := iter.Next(); v != nil; v = iter.Next() { results = append(results, v.(*structs.FederationState)) } return idx, results, nil } func (s *Store) FederationStateDelete(idx uint64, datacenter string) error { tx := s.db.WriteTxn(idx) defer tx.Abort() if err := federationStateDeleteTxn(tx, idx, datacenter); err != nil { return err } return tx.Commit() } func (s *Store) FederationStateBatchDelete(idx uint64, datacenters []string) error { tx := s.db.WriteTxn(idx) defer tx.Abort() for _, datacenter := range datacenters { if err := federationStateDeleteTxn(tx, idx, datacenter); err != nil { return err } } return tx.Commit() } func federationStateDeleteTxn(tx WriteTxn, idx uint64, datacenter string) error { // Try to retrieve the existing federation state. existing, err := tx.First(tableFederationStates, "id", datacenter) if err != nil { return fmt.Errorf("failed federation state lookup: %s", err) } if existing == nil { return nil } // Delete the federation state from the DB and update the index. if err := tx.Delete(tableFederationStates, existing); err != nil { return fmt.Errorf("failed removing federation state: %s", err) } if err := tx.Insert(tableIndex, &IndexEntry{tableFederationStates, idx}); err != nil { return fmt.Errorf("failed updating index: %s", err) } return nil }