172 lines
4.8 KiB
Go
172 lines
4.8 KiB
Go
package proxyprocess
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/lib/file"
|
|
)
|
|
|
|
// snapshot is the structure of the snapshot file. This is unexported because
|
|
// we don't want this being a public API.
|
|
//
|
|
// The snapshot doesn't contain any configuration for the manager. We only
|
|
// want to restore the proxies that we're managing, and we use the config
|
|
// set at runtime to sync and reconcile what proxies we should start,
|
|
// restart, stop, or have already running.
|
|
type snapshot struct {
|
|
// Version is the version of the snapshot format and can be used
|
|
// to safely update the format in the future if necessary.
|
|
Version int
|
|
|
|
// Proxies are the set of proxies that the manager has.
|
|
Proxies map[string]snapshotProxy
|
|
}
|
|
|
|
// snapshotProxy represents a single proxy.
|
|
type snapshotProxy struct {
|
|
// Mode corresponds to the type of proxy running.
|
|
Mode structs.ProxyExecMode
|
|
|
|
// Config is an opaque mapping of primitive values that the proxy
|
|
// implementation uses to restore state.
|
|
Config map[string]interface{}
|
|
}
|
|
|
|
// snapshotVersion is the current version to encode within the snapshot.
|
|
const snapshotVersion = 1
|
|
|
|
// SnapshotPath returns the default snapshot path for this manager. This
|
|
// will return empty if DataDir is not set. This file may not exist yet.
|
|
func (m *Manager) SnapshotPath() string {
|
|
if m.DataDir == "" {
|
|
return ""
|
|
}
|
|
|
|
return filepath.Join(m.DataDir, "snapshot.json")
|
|
}
|
|
|
|
// Snapshot will persist a snapshot of the proxy manager state that
|
|
// can be restored with Restore.
|
|
//
|
|
// If DataDir is non-empty, then the Manager will automatically snapshot
|
|
// whenever the set of managed proxies changes. This method generally doesn't
|
|
// need to be called manually.
|
|
func (m *Manager) Snapshot(path string) error {
|
|
m.lock.Lock()
|
|
defer m.lock.Unlock()
|
|
return m.snapshot(path, false)
|
|
}
|
|
|
|
// snapshot is the internal function analogous to Snapshot but expects
|
|
// a lock to already be held.
|
|
//
|
|
// checkDup when set will store the snapshot on lastSnapshot and use
|
|
// reflect.DeepEqual to verify that its not writing an identical snapshot.
|
|
func (m *Manager) snapshot(path string, checkDup bool) error {
|
|
// Build the snapshot
|
|
s := snapshot{
|
|
Version: snapshotVersion,
|
|
Proxies: make(map[string]snapshotProxy, len(m.proxies)),
|
|
}
|
|
for id, p := range m.proxies {
|
|
// Get the snapshot configuration. If the configuration is nil or
|
|
// empty then we don't persist this proxy.
|
|
config := p.MarshalSnapshot()
|
|
if len(config) == 0 {
|
|
continue
|
|
}
|
|
|
|
s.Proxies[id] = snapshotProxy{
|
|
Mode: proxyExecMode(p),
|
|
Config: config,
|
|
}
|
|
}
|
|
|
|
// Dup detection, if the snapshot is identical to the last, do nothing
|
|
if checkDup && reflect.DeepEqual(m.lastSnapshot, &s) {
|
|
return nil
|
|
}
|
|
|
|
// Encode as JSON
|
|
encoded, err := json.Marshal(&s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write the file
|
|
err = file.WriteAtomic(path, encoded)
|
|
|
|
// If we are checking for dups and we had a successful write, store
|
|
// it so we don't rewrite the same value.
|
|
if checkDup && err == nil {
|
|
m.lastSnapshot = &s
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Restore restores the manager state from a snapshot at path. If path
|
|
// doesn't exist, this does nothing and no error is returned.
|
|
//
|
|
// This restores proxy state but does not restore any Manager configuration
|
|
// such as DataDir, Logger, etc. All of those should be set _before_ Restore
|
|
// is called.
|
|
//
|
|
// Restore must be called before Run. Restore will immediately start
|
|
// supervising the restored processes but will not sync with the local
|
|
// state store until Run is called.
|
|
//
|
|
// If an error is returned the manager state is left untouched.
|
|
func (m *Manager) Restore(path string) error {
|
|
buf, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
var s snapshot
|
|
if err := json.Unmarshal(buf, &s); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Verify the version matches so we can be more confident that we're
|
|
// decoding a structure that we expect.
|
|
if s.Version != snapshotVersion {
|
|
return fmt.Errorf("unknown snapshot version, expecting %d", snapshotVersion)
|
|
}
|
|
|
|
// Build the proxies from the snapshot
|
|
proxies := make(map[string]Proxy, len(s.Proxies))
|
|
for id, sp := range s.Proxies {
|
|
p, err := m.newProxyFromMode(sp.Mode, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Unmarshal the proxy. If there is an error we just continue on and
|
|
// ignore it. Errors restoring proxies should be exceptionally rare
|
|
// and only under scenarios where the proxy isn't running anymore or
|
|
// we won't have permission to access it. We log and continue.
|
|
if err := p.UnmarshalSnapshot(sp.Config); err != nil {
|
|
m.Logger.Printf("[WARN] agent/proxy: error restoring proxy %q: %s", id, err)
|
|
continue
|
|
}
|
|
|
|
proxies[id] = p
|
|
}
|
|
|
|
// Overwrite the proxies. The documentation notes that this will happen.
|
|
m.lock.Lock()
|
|
defer m.lock.Unlock()
|
|
m.proxies = proxies
|
|
return nil
|
|
}
|