Save Peer Status separately in the FileStore (#554)

Due to peer reconnects when restarting the Management service,
there are lots of SaveStore operations to update peer status.

Store.SavePeerStatus stores peer status separately and the
FileStore implementation stores it in memory.
This commit is contained in:
Misha Bragin 2022-11-08 10:46:12 +01:00 committed by GitHub
parent 7e262572a4
commit f37b43a542
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 150 additions and 29 deletions

View File

@ -62,7 +62,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
t.Fatal(err) t.Fatal(err)
} }
s := grpc.NewServer() s := grpc.NewServer()
store, err := mgmt.NewStore(config.Datadir) store, err := mgmt.NewFileStore(config.Datadir)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -935,7 +935,7 @@ func startManagement(port int, dataDir string) (*grpc.Server, error) {
return nil, err return nil, err
} }
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)) s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
store, err := server.NewStore(config.Datadir) store, err := server.NewFileStore(config.Datadir)
if err != nil { if err != nil {
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
} }

View File

@ -49,7 +49,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
t.Fatal(err) t.Fatal(err)
} }
s := grpc.NewServer() s := grpc.NewServer()
store, err := mgmt.NewStore(config.Datadir) store, err := mgmt.NewFileStore(config.Datadir)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -116,7 +116,7 @@ var (
} }
} }
store, err := server.NewStore(config.Datadir) store, err := server.NewFileStore(config.Datadir)
if err != nil { if err != nil {
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err) return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
} }
@ -250,6 +250,7 @@ var (
_ = certManager.Listener().Close() _ = certManager.Listener().Close()
} }
gRPCAPIHandler.Stop() gRPCAPIHandler.Stop()
_ = store.Close()
log.Infof("stopped Management Service") log.Infof("stopped Management Service")
return nil return nil

View File

@ -1211,7 +1211,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
func createStore(t *testing.T) (Store, error) { func createStore(t *testing.T) (Store, error) {
dataDir := t.TempDir() dataDir := t.TempDir()
store, err := NewStore(dataDir) store, err := NewFileStore(dataDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -37,8 +37,8 @@ type FileStore struct {
type StoredAccount struct{} type StoredAccount struct{}
// NewStore restores a store from the file located in the datadir // NewFileStore restores a store from the file located in the datadir
func NewStore(dataDir string) (*FileStore, error) { func NewFileStore(dataDir string) (*FileStore, error) {
return restore(filepath.Join(dataDir, storeFileName)) return restore(filepath.Join(dataDir, storeFileName))
} }
@ -198,7 +198,12 @@ func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
) )
} }
return s.getAccount(accountID) account, err := s.getAccount(accountID)
if err != nil {
return nil, err
}
return account.Copy(), nil
} }
// GetAccountBySetupKey returns account by setup key id // GetAccountBySetupKey returns account by setup key id
@ -211,7 +216,12 @@ func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
return nil, status.Errorf(codes.NotFound, "account not found: provided setup key doesn't exists") return nil, status.Errorf(codes.NotFound, "account not found: provided setup key doesn't exists")
} }
return s.getAccount(accountID) account, err := s.getAccount(accountID)
if err != nil {
return nil, err
}
return account.Copy(), nil
} }
// GetAllAccounts returns all accounts // GetAllAccounts returns all accounts
@ -225,13 +235,14 @@ func (s *FileStore) GetAllAccounts() (all []*Account) {
return all return all
} }
// getAccount returns a reference to the Account. Should not return a copy.
func (s *FileStore) getAccount(accountID string) (*Account, error) { func (s *FileStore) getAccount(accountID string) (*Account, error) {
account, accountFound := s.Accounts[accountID] account, accountFound := s.Accounts[accountID]
if !accountFound { if !accountFound {
return nil, status.Errorf(codes.NotFound, "account not found") return nil, status.Errorf(codes.NotFound, "account not found")
} }
return account.Copy(), nil return account, nil
} }
// GetAccount returns an account for ID // GetAccount returns an account for ID
@ -239,7 +250,12 @@ func (s *FileStore) GetAccount(accountID string) (*Account, error) {
s.mux.Lock() s.mux.Lock()
defer s.mux.Unlock() defer s.mux.Unlock()
return s.getAccount(accountID) account, err := s.getAccount(accountID)
if err != nil {
return nil, err
}
return account.Copy(), nil
} }
// GetAccountByUser returns a user account // GetAccountByUser returns a user account
@ -252,7 +268,12 @@ func (s *FileStore) GetAccountByUser(userID string) (*Account, error) {
return nil, status.Errorf(codes.NotFound, "account not found") return nil, status.Errorf(codes.NotFound, "account not found")
} }
return s.getAccount(accountID) account, err := s.getAccount(accountID)
if err != nil {
return nil, err
}
return account.Copy(), nil
} }
// GetAccountByPeerPubKey returns an account for a given peer WireGuard public key // GetAccountByPeerPubKey returns an account for a given peer WireGuard public key
@ -265,7 +286,12 @@ func (s *FileStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) {
return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey) return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey)
} }
return s.getAccount(accountID) account, err := s.getAccount(accountID)
if err != nil {
return nil, err
}
return account.Copy(), nil
} }
// GetInstallationID returns the installation ID from the store // GetInstallationID returns the installation ID from the store
@ -274,11 +300,42 @@ func (s *FileStore) GetInstallationID() string {
} }
// SaveInstallationID saves the installation ID // SaveInstallationID saves the installation ID
func (s *FileStore) SaveInstallationID(id string) error { func (s *FileStore) SaveInstallationID(ID string) error {
s.mux.Lock() s.mux.Lock()
defer s.mux.Unlock() defer s.mux.Unlock()
s.InstallationID = id s.InstallationID = ID
return s.persist(s.storeFile)
}
// SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things.
// PeerStatus will be saved eventually when some other changes occur.
func (s *FileStore) SavePeerStatus(accountID, peerKey string, peerStatus PeerStatus) error {
s.mux.Lock()
defer s.mux.Unlock()
account, err := s.getAccount(accountID)
if err != nil {
return err
}
peer := account.Peers[peerKey]
if peer == nil {
return status.Errorf(codes.NotFound, "peer %s not found", peerKey)
}
peer.Status = &peerStatus
return nil
}
// Close the FileStore persisting data to disk
func (s *FileStore) Close() error {
s.mux.Lock()
defer s.mux.Unlock()
log.Infof("closing FileStore")
return s.persist(s.storeFile) return s.persist(s.storeFile)
} }

View File

@ -93,7 +93,7 @@ func TestStore(t *testing.T) {
return return
} }
restored, err := NewStore(store.storeFile) restored, err := NewFileStore(store.storeFile)
if err != nil { if err != nil {
return return
} }
@ -129,7 +129,7 @@ func TestRestore(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
store, err := NewStore(storeDir) store, err := NewFileStore(storeDir)
if err != nil { if err != nil {
return return
} }
@ -161,7 +161,7 @@ func TestGetAccountByPrivateDomain(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
store, err := NewStore(storeDir) store, err := NewFileStore(storeDir)
if err != nil { if err != nil {
return return
} }
@ -190,7 +190,7 @@ func TestFileStore_GetAccount(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
store, err := NewStore(storeDir) store, err := NewFileStore(storeDir)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -218,8 +218,59 @@ func TestFileStore_GetAccount(t *testing.T) {
assert.Len(t, account.NameServerGroups, len(expected.NameServerGroups)) assert.Len(t, account.NameServerGroups, len(expected.NameServerGroups))
} }
func TestFileStore_SavePeerStatus(t *testing.T) {
storeDir := t.TempDir()
err := util.CopyFileContents("testdata/store.json", filepath.Join(storeDir, "store.json"))
if err != nil {
t.Fatal(err)
}
store, err := NewFileStore(storeDir)
if err != nil {
return
}
account, err := store.getAccount("bf1c8084-ba50-4ce7-9439-34653001fc3b")
if err != nil {
t.Fatal(err)
}
// save status of non-existing peer
newStatus := PeerStatus{Connected: true, LastSeen: time.Now()}
err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus)
assert.Error(t, err)
// save new status of existing peer
account.Peers["testpeer"] = &Peer{
Key: "peerkey",
SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{},
Name: "peer name",
Status: &PeerStatus{Connected: false, LastSeen: time.Now()},
}
err = store.SaveAccount(account)
if err != nil {
t.Fatal(err)
}
err = store.SavePeerStatus(account.Id, "testpeer", newStatus)
if err != nil {
t.Fatal(err)
}
account, err = store.getAccount(account.Id)
if err != nil {
t.Fatal(err)
}
actual := account.Peers["testpeer"].Status
assert.Equal(t, newStatus, *actual)
}
func newStore(t *testing.T) *FileStore { func newStore(t *testing.T) *FileStore {
store, err := NewStore(t.TempDir()) store, err := NewFileStore(t.TempDir())
if err != nil { if err != nil {
t.Errorf("failed creating a new store") t.Errorf("failed creating a new store")
} }

View File

@ -398,7 +398,7 @@ func startManagement(t *testing.T, port int, config *Config) (*grpc.Server, erro
return nil, err return nil, err
} }
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)) s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
store, err := NewStore(config.Datadir) store, err := NewFileStore(config.Datadir)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -488,7 +488,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
s := grpc.NewServer() s := grpc.NewServer()
store, err := server.NewStore(config.Datadir) store, err := server.NewFileStore(config.Datadir)
if err != nil { if err != nil {
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
} }

View File

@ -1061,7 +1061,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
func createNSStore(t *testing.T) (Store, error) { func createNSStore(t *testing.T) (Store, error) {
dataDir := t.TempDir() dataDir := t.TempDir()
store, err := NewStore(dataDir) store, err := NewFileStore(dataDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -74,6 +74,14 @@ func (p *Peer) Copy() *Peer {
} }
} }
// Copy PeerStatus
func (p *PeerStatus) Copy() *PeerStatus {
return &PeerStatus{
LastSeen: p.LastSeen,
Connected: p.Connected,
}
}
// GetPeer looks up peer by its public WireGuard key // GetPeer looks up peer by its public WireGuard key
func (am *DefaultAccountManager) GetPeer(peerPubKey string) (*Peer, error) { func (am *DefaultAccountManager) GetPeer(peerPubKey string) (*Peer, error) {
@ -133,12 +141,13 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected
return err return err
} }
peer.Status.LastSeen = time.Now() newStatus := peer.Status.Copy()
peer.Status.Connected = connected newStatus.LastSeen = time.Now()
newStatus.Connected = connected
peer.Status = newStatus
account.UpdatePeer(peer) account.UpdatePeer(peer)
err = am.Store.SaveAccount(account) err = am.Store.SavePeerStatus(account.Id, peerPubKey, *newStatus)
if err != nil { if err != nil {
return err return err
} }

View File

@ -788,7 +788,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
func createRouterStore(t *testing.T) (Store, error) { func createRouterStore(t *testing.T) (Store, error) {
dataDir := t.TempDir() dataDir := t.TempDir()
store, err := NewStore(dataDir) store, err := NewFileStore(dataDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,9 +9,12 @@ type Store interface {
GetAccountByPrivateDomain(domain string) (*Account, error) GetAccountByPrivateDomain(domain string) (*Account, error)
SaveAccount(account *Account) error SaveAccount(account *Account) error
GetInstallationID() string GetInstallationID() string
SaveInstallationID(id string) error SaveInstallationID(ID string) error
// AcquireAccountLock should attempt to acquire account lock and return a function that releases the lock // AcquireAccountLock should attempt to acquire account lock and return a function that releases the lock
AcquireAccountLock(accountID string) func() AcquireAccountLock(accountID string) func()
// AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock // AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock
AcquireGlobalLock() func() AcquireGlobalLock() func()
SavePeerStatus(accountID, peerKey string, status PeerStatus) error
// Close should close the store persisting all unsaved data.
Close() error
} }