github.imxd.top/hashicorp/consul@v1.4.5/agent/proxyprocess/snapshot.go (about) 1 package proxyprocess 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "reflect" 10 11 "github.com/hashicorp/consul/agent/structs" 12 "github.com/hashicorp/consul/lib/file" 13 ) 14 15 // snapshot is the structure of the snapshot file. This is unexported because 16 // we don't want this being a public API. 17 // 18 // The snapshot doesn't contain any configuration for the manager. We only 19 // want to restore the proxies that we're managing, and we use the config 20 // set at runtime to sync and reconcile what proxies we should start, 21 // restart, stop, or have already running. 22 type snapshot struct { 23 // Version is the version of the snapshot format and can be used 24 // to safely update the format in the future if necessary. 25 Version int 26 27 // Proxies are the set of proxies that the manager has. 28 Proxies map[string]snapshotProxy 29 } 30 31 // snapshotProxy represents a single proxy. 32 type snapshotProxy struct { 33 // Mode corresponds to the type of proxy running. 34 Mode structs.ProxyExecMode 35 36 // Config is an opaque mapping of primitive values that the proxy 37 // implementation uses to restore state. 38 Config map[string]interface{} 39 } 40 41 // snapshotVersion is the current version to encode within the snapshot. 42 const snapshotVersion = 1 43 44 // SnapshotPath returns the default snapshot path for this manager. This 45 // will return empty if DataDir is not set. This file may not exist yet. 46 func (m *Manager) SnapshotPath() string { 47 if m.DataDir == "" { 48 return "" 49 } 50 51 return filepath.Join(m.DataDir, "snapshot.json") 52 } 53 54 // Snapshot will persist a snapshot of the proxy manager state that 55 // can be restored with Restore. 56 // 57 // If DataDir is non-empty, then the Manager will automatically snapshot 58 // whenever the set of managed proxies changes. This method generally doesn't 59 // need to be called manually. 60 func (m *Manager) Snapshot(path string) error { 61 m.lock.Lock() 62 defer m.lock.Unlock() 63 return m.snapshot(path, false) 64 } 65 66 // snapshot is the internal function analogous to Snapshot but expects 67 // a lock to already be held. 68 // 69 // checkDup when set will store the snapshot on lastSnapshot and use 70 // reflect.DeepEqual to verify that its not writing an identical snapshot. 71 func (m *Manager) snapshot(path string, checkDup bool) error { 72 // Build the snapshot 73 s := snapshot{ 74 Version: snapshotVersion, 75 Proxies: make(map[string]snapshotProxy, len(m.proxies)), 76 } 77 for id, p := range m.proxies { 78 // Get the snapshot configuration. If the configuration is nil or 79 // empty then we don't persist this proxy. 80 config := p.MarshalSnapshot() 81 if len(config) == 0 { 82 continue 83 } 84 85 s.Proxies[id] = snapshotProxy{ 86 Mode: proxyExecMode(p), 87 Config: config, 88 } 89 } 90 91 // Dup detection, if the snapshot is identical to the last, do nothing 92 if checkDup && reflect.DeepEqual(m.lastSnapshot, &s) { 93 return nil 94 } 95 96 // Encode as JSON 97 encoded, err := json.Marshal(&s) 98 if err != nil { 99 return err 100 } 101 102 // Write the file 103 err = file.WriteAtomic(path, encoded) 104 105 // If we are checking for dups and we had a successful write, store 106 // it so we don't rewrite the same value. 107 if checkDup && err == nil { 108 m.lastSnapshot = &s 109 } 110 return err 111 } 112 113 // Restore restores the manager state from a snapshot at path. If path 114 // doesn't exist, this does nothing and no error is returned. 115 // 116 // This restores proxy state but does not restore any Manager configuration 117 // such as DataDir, Logger, etc. All of those should be set _before_ Restore 118 // is called. 119 // 120 // Restore must be called before Run. Restore will immediately start 121 // supervising the restored processes but will not sync with the local 122 // state store until Run is called. 123 // 124 // If an error is returned the manager state is left untouched. 125 func (m *Manager) Restore(path string) error { 126 buf, err := ioutil.ReadFile(path) 127 if err != nil { 128 if os.IsNotExist(err) { 129 return nil 130 } 131 132 return err 133 } 134 135 var s snapshot 136 if err := json.Unmarshal(buf, &s); err != nil { 137 return err 138 } 139 140 // Verify the version matches so we can be more confident that we're 141 // decoding a structure that we expect. 142 if s.Version != snapshotVersion { 143 return fmt.Errorf("unknown snapshot version, expecting %d", snapshotVersion) 144 } 145 146 // Build the proxies from the snapshot 147 proxies := make(map[string]Proxy, len(s.Proxies)) 148 for id, sp := range s.Proxies { 149 p, err := m.newProxyFromMode(sp.Mode, id) 150 if err != nil { 151 return err 152 } 153 154 // Unmarshal the proxy. If there is an error we just continue on and 155 // ignore it. Errors restoring proxies should be exceptionally rare 156 // and only under scenarios where the proxy isn't running anymore or 157 // we won't have permission to access it. We log and continue. 158 if err := p.UnmarshalSnapshot(sp.Config); err != nil { 159 m.Logger.Printf("[WARN] agent/proxy: error restoring proxy %q: %s", id, err) 160 continue 161 } 162 163 proxies[id] = p 164 } 165 166 // Overwrite the proxies. The documentation notes that this will happen. 167 m.lock.Lock() 168 defer m.lock.Unlock() 169 m.proxies = proxies 170 return nil 171 }