github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/snapshotstate/snapshotstate.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package snapshotstate 21 22 import ( 23 "context" 24 "encoding/json" 25 "fmt" 26 "sort" 27 "time" 28 29 "github.com/snapcore/snapd/client" 30 "github.com/snapcore/snapd/logger" 31 "github.com/snapcore/snapd/overlord/configstate/config" 32 "github.com/snapcore/snapd/overlord/snapshotstate/backend" 33 "github.com/snapcore/snapd/overlord/snapstate" 34 "github.com/snapcore/snapd/overlord/state" 35 "github.com/snapcore/snapd/release" 36 "github.com/snapcore/snapd/snap" 37 "github.com/snapcore/snapd/strutil" 38 ) 39 40 var ( 41 snapstateAll = snapstate.All 42 snapstateCheckChangeConflictMany = snapstate.CheckChangeConflictMany 43 backendIter = backend.Iter 44 backendEstimateSnapshotSize = backend.EstimateSnapshotSize 45 46 // Default expiration time for automatic snapshots, if not set by the user 47 defaultAutomaticSnapshotExpiration = time.Hour * 24 * 31 48 ) 49 50 type snapshotState struct { 51 ExpiryTime time.Time `json:"expiry-time"` 52 } 53 54 func newSnapshotSetID(st *state.State) (uint64, error) { 55 var lastSetID uint64 56 57 err := st.Get("last-snapshot-set-id", &lastSetID) 58 if err != nil && err != state.ErrNoState { 59 return 0, err 60 } 61 62 lastSetID++ 63 st.Set("last-snapshot-set-id", lastSetID) 64 65 return lastSetID, nil 66 } 67 68 func allActiveSnapNames(st *state.State) ([]string, error) { 69 all, err := snapstateAll(st) 70 if err != nil { 71 return nil, err 72 } 73 names := make([]string, 0, len(all)) 74 for name, snapst := range all { 75 if snapst.Active { 76 names = append(names, name) 77 } 78 } 79 80 sort.Strings(names) 81 82 return names, nil 83 } 84 85 func EstimateSnapshotSize(st *state.State, instanceName string, users []string) (uint64, error) { 86 cur, err := snapstateCurrentInfo(st, instanceName) 87 if err != nil { 88 return 0, err 89 } 90 rawCfg, err := configGetSnapConfig(st, instanceName) 91 if err != nil { 92 return 0, err 93 } 94 sz, err := backendEstimateSnapshotSize(cur, users) 95 if err != nil { 96 return 0, err 97 } 98 if rawCfg != nil { 99 sz += uint64(len([]byte(*rawCfg))) 100 } 101 return sz, nil 102 } 103 104 func AutomaticSnapshotExpiration(st *state.State) (time.Duration, error) { 105 var expirationStr string 106 tr := config.NewTransaction(st) 107 err := tr.Get("core", "snapshots.automatic.retention", &expirationStr) 108 if err != nil && !config.IsNoOption(err) { 109 return 0, err 110 } 111 if err == nil { 112 if expirationStr == "no" { 113 return 0, nil 114 } 115 dur, err := time.ParseDuration(expirationStr) 116 if err == nil { 117 return dur, nil 118 } 119 logger.Noticef("snapshots.automatic.retention cannot be parsed: %v", err) 120 } 121 // TODO: automatic snapshots are currently disable by default 122 // on Ubuntu Core devices 123 if !release.OnClassic { 124 return 0, nil 125 } 126 return defaultAutomaticSnapshotExpiration, nil 127 } 128 129 // saveExpiration saves expiration date of the given snapshot set, in the state. 130 // The state needs to be locked by the caller. 131 func saveExpiration(st *state.State, setID uint64, expiryTime time.Time) error { 132 var snapshots map[uint64]*json.RawMessage 133 err := st.Get("snapshots", &snapshots) 134 if err != nil && err != state.ErrNoState { 135 return err 136 } 137 if snapshots == nil { 138 snapshots = make(map[uint64]*json.RawMessage) 139 } 140 data, err := json.Marshal(&snapshotState{ 141 ExpiryTime: expiryTime, 142 }) 143 if err != nil { 144 return err 145 } 146 raw := json.RawMessage(data) 147 snapshots[setID] = &raw 148 st.Set("snapshots", snapshots) 149 return nil 150 } 151 152 // removeSnapshotState removes given set IDs from the state. 153 func removeSnapshotState(st *state.State, setIDs ...uint64) error { 154 var snapshots map[uint64]*json.RawMessage 155 err := st.Get("snapshots", &snapshots) 156 if err != nil { 157 if err == state.ErrNoState { 158 return nil 159 } 160 return err 161 } 162 163 for _, setID := range setIDs { 164 delete(snapshots, setID) 165 } 166 167 st.Set("snapshots", snapshots) 168 return nil 169 } 170 171 // expiredSnapshotSets returns expired snapshot sets from the state whose expiry-time is before the given cutoffTime. 172 // The state needs to be locked by the caller. 173 func expiredSnapshotSets(st *state.State, cutoffTime time.Time) (map[uint64]bool, error) { 174 var snapshots map[uint64]*snapshotState 175 err := st.Get("snapshots", &snapshots) 176 if err != nil { 177 if err != state.ErrNoState { 178 return nil, err 179 } 180 return nil, nil 181 } 182 183 expired := make(map[uint64]bool) 184 for setID, snapshotSet := range snapshots { 185 if snapshotSet.ExpiryTime.Before(cutoffTime) { 186 expired[setID] = true 187 } 188 } 189 190 return expired, nil 191 } 192 193 // snapshotSnapSummaries are used internally to get useful data from a 194 // snapshot set when deciding whether to check/forget/restore it. 195 type snapshotSnapSummaries []*snapshotSnapSummary 196 197 func (summaries snapshotSnapSummaries) snapNames() []string { 198 names := make([]string, len(summaries)) 199 for i, summary := range summaries { 200 names[i] = summary.snap 201 } 202 return names 203 } 204 205 type snapshotSnapSummary struct { 206 snap string 207 snapID string 208 filename string 209 epoch snap.Epoch 210 } 211 212 // snapSummariesInSnapshotSet goes looking for the requested snaps in the 213 // given snap set, and returns summaries of the matching snaps in the set. 214 func snapSummariesInSnapshotSet(setID uint64, requested []string) (summaries snapshotSnapSummaries, err error) { 215 sort.Strings(requested) 216 found := false 217 err = backendIter(context.TODO(), func(r *backend.Reader) error { 218 if r.SetID == setID { 219 found = true 220 if len(requested) == 0 || strutil.SortedListContains(requested, r.Snap) { 221 summaries = append(summaries, &snapshotSnapSummary{ 222 filename: r.Name(), 223 snap: r.Snap, 224 snapID: r.SnapID, 225 epoch: r.Epoch, 226 }) 227 } 228 } 229 230 return nil 231 }) 232 if err != nil { 233 return nil, err 234 } 235 if !found { 236 return nil, client.ErrSnapshotSetNotFound 237 } 238 if len(summaries) == 0 { 239 return nil, client.ErrSnapshotSnapsNotFound 240 } 241 242 return summaries, nil 243 } 244 245 func taskGetErrMsg(task *state.Task, err error, what string) error { 246 if err == state.ErrNoState { 247 return fmt.Errorf("internal error: task %s (%s) is missing %s information", task.ID(), task.Kind(), what) 248 } 249 return fmt.Errorf("internal error: retrieving %s information from task %s (%s): %v", what, task.ID(), task.Kind(), err) 250 } 251 252 // checkSnapshotTaskConflict checks whether there's an in-progress task for snapshots with the given set id. 253 func checkSnapshotTaskConflict(st *state.State, setID uint64, conflictingKinds ...string) error { 254 for _, task := range st.Tasks() { 255 if task.Change().Status().Ready() { 256 continue 257 } 258 if !strutil.ListContains(conflictingKinds, task.Kind()) { 259 continue 260 } 261 262 var snapshot snapshotSetup 263 if err := task.Get("snapshot-setup", &snapshot); err != nil { 264 return taskGetErrMsg(task, err, "snapshot") 265 } 266 267 if snapshot.SetID == setID { 268 return fmt.Errorf("cannot operate on snapshot set #%d while change %q is in progress", setID, task.Change().ID()) 269 } 270 } 271 272 return nil 273 } 274 275 // List valid snapshots. 276 // Note that the state must be locked by the caller. 277 var List = backend.List 278 279 // Save creates a taskset for taking snapshots of snaps' data. 280 // Note that the state must be locked by the caller. 281 func Save(st *state.State, instanceNames []string, users []string) (setID uint64, snapsSaved []string, ts *state.TaskSet, err error) { 282 if len(instanceNames) == 0 { 283 instanceNames, err = allActiveSnapNames(st) 284 if err != nil { 285 return 0, nil, nil, err 286 } 287 } 288 289 // Make sure we do not snapshot if anything like install/remove/refresh is in progress 290 if err := snapstateCheckChangeConflictMany(st, instanceNames, ""); err != nil { 291 return 0, nil, nil, err 292 } 293 294 setID, err = newSnapshotSetID(st) 295 if err != nil { 296 return 0, nil, nil, err 297 } 298 299 ts = state.NewTaskSet() 300 301 for _, name := range instanceNames { 302 desc := fmt.Sprintf("Save data of snap %q in snapshot set #%d", name, setID) 303 task := st.NewTask("save-snapshot", desc) 304 snapshot := snapshotSetup{ 305 SetID: setID, 306 Snap: name, 307 Users: users, 308 } 309 task.Set("snapshot-setup", &snapshot) 310 // Here, note that a snapshot set behaves as a unit: it either 311 // succeeds, or fails, as a whole; we don't use lanes, to have 312 // some snaps' snapshot succeed and not others in a single set. 313 // In practice: either the snapshot will be automatic and only 314 // for one snap (already in a lane via refresh), or it will be 315 // done by hand and the user can remove failing snaps (or find 316 // the cause of the failure). A snapshot failure can happen if 317 // a user has dropped files they can't read in their directory, 318 // for example. 319 // Also note we aren't promising this behaviour; we can change 320 // it if we find it to be wrong. 321 ts.AddTask(task) 322 } 323 324 return setID, instanceNames, ts, nil 325 } 326 327 func AutomaticSnapshot(st *state.State, snapName string) (ts *state.TaskSet, err error) { 328 expiration, err := AutomaticSnapshotExpiration(st) 329 if err != nil { 330 return nil, err 331 } 332 if expiration == 0 { 333 return nil, snapstate.ErrNothingToDo 334 } 335 setID, err := newSnapshotSetID(st) 336 if err != nil { 337 return nil, err 338 } 339 340 ts = state.NewTaskSet() 341 desc := fmt.Sprintf("Save data of snap %q in automatic snapshot set #%d", snapName, setID) 342 task := st.NewTask("save-snapshot", desc) 343 snapshot := snapshotSetup{ 344 SetID: setID, 345 Snap: snapName, 346 Auto: true, 347 } 348 task.Set("snapshot-setup", &snapshot) 349 ts.AddTask(task) 350 351 return ts, nil 352 } 353 354 // Restore creates a taskset for restoring a snapshot's data. 355 // Note that the state must be locked by the caller. 356 func Restore(st *state.State, setID uint64, snapNames []string, users []string) (snapsFound []string, ts *state.TaskSet, err error) { 357 summaries, err := snapSummariesInSnapshotSet(setID, snapNames) 358 if err != nil { 359 return nil, nil, err 360 } 361 all, err := snapstateAll(st) 362 if err != nil { 363 return nil, nil, err 364 } 365 366 snapsFound = summaries.snapNames() 367 368 if err := snapstateCheckChangeConflictMany(st, snapsFound, ""); err != nil { 369 return nil, nil, err 370 } 371 372 // restore needs to conflict with forget of itself 373 if err := checkSnapshotTaskConflict(st, setID, "forget-snapshot"); err != nil { 374 return nil, nil, err 375 } 376 377 ts = state.NewTaskSet() 378 379 for _, summary := range summaries { 380 var current snap.Revision 381 if snapst, ok := all[summary.snap]; ok { 382 info, err := snapst.CurrentInfo() 383 if err != nil { 384 // how? 385 return nil, nil, fmt.Errorf("unexpected error while reading snap info: %v", err) 386 } 387 if !info.Epoch.CanRead(summary.epoch) { 388 const tpl = "cannot restore snapshot for %q: current snap (epoch %s) cannot read snapshot data (epoch %s)" 389 return nil, nil, fmt.Errorf(tpl, summary.snap, &info.Epoch, &summary.epoch) 390 } 391 if summary.snapID != "" && info.SnapID != "" && info.SnapID != summary.snapID { 392 const tpl = "cannot restore snapshot for %q: current snap (ID %.7s…) does not match snapshot (ID %.7s…)" 393 return nil, nil, fmt.Errorf(tpl, summary.snap, info.SnapID, summary.snapID) 394 } 395 current = snapst.Current 396 } 397 398 desc := fmt.Sprintf("Restore data of snap %q from snapshot set #%d", summary.snap, setID) 399 task := st.NewTask("restore-snapshot", desc) 400 snapshot := snapshotSetup{ 401 SetID: setID, 402 Snap: summary.snap, 403 Users: users, 404 Filename: summary.filename, 405 Current: current, 406 } 407 task.Set("snapshot-setup", &snapshot) 408 // see the note about snapshots not using lanes, above. 409 ts.AddTask(task) 410 } 411 412 return snapsFound, ts, nil 413 } 414 415 // Check creates a taskset for checking a snapshot's data. 416 // Note that the state must be locked by the caller. 417 func Check(st *state.State, setID uint64, snapNames []string, users []string) (snapsFound []string, ts *state.TaskSet, err error) { 418 // check needs to conflict with forget of itself 419 if err := checkSnapshotTaskConflict(st, setID, "forget-snapshot"); err != nil { 420 return nil, nil, err 421 } 422 423 summaries, err := snapSummariesInSnapshotSet(setID, snapNames) 424 if err != nil { 425 return nil, nil, err 426 } 427 428 ts = state.NewTaskSet() 429 430 for _, summary := range summaries { 431 desc := fmt.Sprintf("Check data of snap %q in snapshot set #%d", summary.snap, setID) 432 task := st.NewTask("check-snapshot", desc) 433 snapshot := snapshotSetup{ 434 SetID: setID, 435 Snap: summary.snap, 436 Users: users, 437 Filename: summary.filename, 438 } 439 task.Set("snapshot-setup", &snapshot) 440 ts.AddTask(task) 441 } 442 443 return summaries.snapNames(), ts, nil 444 } 445 446 // Forget creates a taskset for deletinig a snapshot. 447 // Note that the state must be locked by the caller. 448 func Forget(st *state.State, setID uint64, snapNames []string) (snapsFound []string, ts *state.TaskSet, err error) { 449 // forget needs to conflict with check and restore 450 if err := checkSnapshotTaskConflict(st, setID, "check-snapshot", "restore-snapshot"); err != nil { 451 return nil, nil, err 452 } 453 454 summaries, err := snapSummariesInSnapshotSet(setID, snapNames) 455 if err != nil { 456 return nil, nil, err 457 } 458 459 ts = state.NewTaskSet() 460 for _, summary := range summaries { 461 desc := fmt.Sprintf("Drop data of snap %q from snapshot set #%d", summary.snap, setID) 462 task := st.NewTask("forget-snapshot", desc) 463 snapshot := snapshotSetup{ 464 SetID: setID, 465 Snap: summary.snap, 466 Filename: summary.filename, 467 } 468 task.Set("snapshot-setup", &snapshot) 469 ts.AddTask(task) 470 } 471 472 return summaries.snapNames(), ts, nil 473 } 474 475 // Export exports a given snapshot ID 476 // Note that the state much be locked by the caller. 477 var Export = backend.NewSnapshotExport 478 479 // SnapshotExport provides a snapshot export that can be streamed out 480 type SnapshotExport = backend.SnapshotExport