github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/devicestate/devicestate.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2019 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 devicestate implements the manager and state aspects responsible 21 // for the device identity and policies. 22 package devicestate 23 24 import ( 25 "context" 26 "fmt" 27 "sync" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/gadget" 31 "github.com/snapcore/snapd/i18n" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/netutil" 34 "github.com/snapcore/snapd/overlord/assertstate" 35 "github.com/snapcore/snapd/overlord/auth" 36 "github.com/snapcore/snapd/overlord/configstate/config" 37 "github.com/snapcore/snapd/overlord/devicestate/internal" 38 "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" 39 "github.com/snapcore/snapd/overlord/snapstate" 40 "github.com/snapcore/snapd/overlord/state" 41 "github.com/snapcore/snapd/release" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/naming" 44 ) 45 46 var ( 47 snapstateInstallWithDeviceContext = snapstate.InstallWithDeviceContext 48 snapstateUpdateWithDeviceContext = snapstate.UpdateWithDeviceContext 49 ) 50 51 // findModel returns the device model assertion. 52 func findModel(st *state.State) (*asserts.Model, error) { 53 device, err := internal.Device(st) 54 if err != nil { 55 return nil, err 56 } 57 58 if device.Brand == "" || device.Model == "" { 59 return nil, state.ErrNoState 60 } 61 62 a, err := assertstate.DB(st).Find(asserts.ModelType, map[string]string{ 63 "series": release.Series, 64 "brand-id": device.Brand, 65 "model": device.Model, 66 }) 67 if asserts.IsNotFound(err) { 68 return nil, state.ErrNoState 69 } 70 if err != nil { 71 return nil, err 72 } 73 74 return a.(*asserts.Model), nil 75 } 76 77 // findSerial returns the device serial assertion. device is optional and used instead of the global state if provided. 78 func findSerial(st *state.State, device *auth.DeviceState) (*asserts.Serial, error) { 79 if device == nil { 80 var err error 81 device, err = internal.Device(st) 82 if err != nil { 83 return nil, err 84 } 85 } 86 87 if device.Serial == "" { 88 return nil, state.ErrNoState 89 } 90 91 a, err := assertstate.DB(st).Find(asserts.SerialType, map[string]string{ 92 "brand-id": device.Brand, 93 "model": device.Model, 94 "serial": device.Serial, 95 }) 96 if asserts.IsNotFound(err) { 97 return nil, state.ErrNoState 98 } 99 if err != nil { 100 return nil, err 101 } 102 103 return a.(*asserts.Serial), nil 104 } 105 106 // auto-refresh 107 func canAutoRefresh(st *state.State) (bool, error) { 108 // we need to be seeded first 109 var seeded bool 110 st.Get("seeded", &seeded) 111 if !seeded { 112 return false, nil 113 } 114 115 // Either we have a serial or we try anyway if we attempted 116 // for a while to get a serial, this would allow us to at 117 // least upgrade core if that can help. 118 if ensureOperationalAttempts(st) >= 3 { 119 return true, nil 120 } 121 122 // Check model exists, for sanity. We always have a model, either 123 // seeded or a generic one that ships with snapd. 124 _, err := findModel(st) 125 if err == state.ErrNoState { 126 return false, nil 127 } 128 if err != nil { 129 return false, err 130 } 131 132 _, err = findSerial(st, nil) 133 if err == state.ErrNoState { 134 return false, nil 135 } 136 if err != nil { 137 return false, err 138 } 139 140 return true, nil 141 } 142 143 func checkGadgetOrKernel(st *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error { 144 kind := "" 145 var snapType snap.Type 146 var getName func(*asserts.Model) string 147 switch snapInfo.Type() { 148 case snap.TypeGadget: 149 kind = "gadget" 150 snapType = snap.TypeGadget 151 getName = (*asserts.Model).Gadget 152 case snap.TypeKernel: 153 if release.OnClassic { 154 return fmt.Errorf("cannot install a kernel snap on classic") 155 } 156 157 kind = "kernel" 158 snapType = snap.TypeKernel 159 getName = (*asserts.Model).Kernel 160 default: 161 // not a relevant check 162 return nil 163 } 164 165 model := deviceCtx.Model() 166 167 if snapInfo.SnapID != "" { 168 snapDecl, err := assertstate.SnapDeclaration(st, snapInfo.SnapID) 169 if err != nil { 170 return fmt.Errorf("internal error: cannot find snap declaration for %q: %v", snapInfo.InstanceName(), err) 171 } 172 publisher := snapDecl.PublisherID() 173 if publisher != "canonical" && publisher != model.BrandID() { 174 return fmt.Errorf("cannot install %s %q published by %q for model by %q", kind, snapInfo.InstanceName(), publisher, model.BrandID()) 175 } 176 } else { 177 logger.Noticef("installing unasserted %s %q", kind, snapInfo.InstanceName()) 178 } 179 180 found, err := snapstate.HasSnapOfType(st, snapType) 181 if err != nil { 182 return fmt.Errorf("cannot detect original %s snap: %v", kind, err) 183 } 184 if found { 185 // already installed, snapstate takes care 186 return nil 187 } 188 // first installation of a gadget/kernel 189 190 expectedName := getName(model) 191 if expectedName == "" { // can happen only on classic 192 return fmt.Errorf("cannot install %s snap on classic if not requested by the model", kind) 193 } 194 195 if snapInfo.InstanceName() != snapInfo.SnapName() { 196 return fmt.Errorf("cannot install %q, parallel installation of kernel or gadget snaps is not supported", snapInfo.InstanceName()) 197 } 198 199 if snapInfo.InstanceName() != expectedName { 200 return fmt.Errorf("cannot install %s %q, model assertion requests %q", kind, snapInfo.InstanceName(), expectedName) 201 } 202 203 return nil 204 } 205 206 func checkGadgetValid(st *state.State, snapInfo, _ *snap.Info, snapf snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error { 207 if snapInfo.Type() != snap.TypeGadget { 208 // not a gadget, nothing to do 209 return nil 210 } 211 if deviceCtx.ForRemodeling() { 212 // in this case the gadget is checked by 213 // checkGadgetRemodelCompatible 214 return nil 215 } 216 217 // do basic validity checks on the gadget against its model constraints 218 _, err := gadget.ReadInfoFromSnapFile(snapf, deviceCtx.Model()) 219 return err 220 } 221 222 var once sync.Once 223 224 func delayedCrossMgrInit() { 225 once.Do(func() { 226 snapstate.AddCheckSnapCallback(checkGadgetOrKernel) 227 snapstate.AddCheckSnapCallback(checkGadgetValid) 228 snapstate.AddCheckSnapCallback(checkGadgetRemodelCompatible) 229 }) 230 snapstate.CanAutoRefresh = canAutoRefresh 231 snapstate.CanManageRefreshes = CanManageRefreshes 232 snapstate.IsOnMeteredConnection = netutil.IsOnMeteredConnection 233 snapstate.DeviceCtx = DeviceCtx 234 snapstate.Remodeling = Remodeling 235 } 236 237 // proxyStore returns the store assertion for the proxy store if one is set. 238 func proxyStore(st *state.State, tr *config.Transaction) (*asserts.Store, error) { 239 var proxyStore string 240 err := tr.GetMaybe("core", "proxy.store", &proxyStore) 241 if err != nil { 242 return nil, err 243 } 244 if proxyStore == "" { 245 return nil, state.ErrNoState 246 } 247 248 a, err := assertstate.DB(st).Find(asserts.StoreType, map[string]string{ 249 "store": proxyStore, 250 }) 251 if asserts.IsNotFound(err) { 252 return nil, state.ErrNoState 253 } 254 if err != nil { 255 return nil, err 256 } 257 258 return a.(*asserts.Store), nil 259 } 260 261 // interfaceConnected returns true if the given snap/interface names 262 // are connected 263 func interfaceConnected(st *state.State, snapName, ifName string) bool { 264 conns, err := ifacerepo.Get(st).Connected(snapName, ifName) 265 return err == nil && len(conns) > 0 266 } 267 268 // CanManageRefreshes returns true if the device can be 269 // switched to the "core.refresh.schedule=managed" mode. 270 // 271 // TODO: 272 // - Move the CanManageRefreshes code into the ifstate 273 // - Look at the connections and find the connection for snapd-control 274 // with the managed attribute 275 // - Take the snap from this connection and look at the snapstate to see 276 // if that snap has a snap declaration (to ensure it comes from the store) 277 func CanManageRefreshes(st *state.State) bool { 278 snapStates, err := snapstate.All(st) 279 if err != nil { 280 return false 281 } 282 for _, snapst := range snapStates { 283 // Always get the current info even if the snap is currently 284 // being operated on or if its disabled. 285 info, err := snapst.CurrentInfo() 286 if err != nil { 287 continue 288 } 289 if info.Broken != "" { 290 continue 291 } 292 // The snap must have a snap declaration (implies that 293 // its from the store) 294 if _, err := assertstate.SnapDeclaration(st, info.SideInfo.SnapID); err != nil { 295 continue 296 } 297 298 for _, plugInfo := range info.Plugs { 299 if plugInfo.Interface == "snapd-control" && plugInfo.Attrs["refresh-schedule"] == "managed" { 300 snapName := info.InstanceName() 301 plugName := plugInfo.Name 302 if interfaceConnected(st, snapName, plugName) { 303 return true 304 } 305 } 306 } 307 } 308 309 return false 310 } 311 312 func getAllRequiredSnapsForModel(model *asserts.Model) *naming.SnapSet { 313 reqSnaps := model.RequiredWithEssentialSnaps() 314 return naming.NewSnapSet(reqSnaps) 315 } 316 317 // extractDownloadInstallEdgesFromTs extracts the first, last download 318 // phase and install phase tasks from a TaskSet 319 func extractDownloadInstallEdgesFromTs(ts *state.TaskSet) (firstDl, lastDl, firstInst, lastInst *state.Task, err error) { 320 edgeTask, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge) 321 if err != nil { 322 return nil, nil, nil, nil, err 323 } 324 tasks := ts.Tasks() 325 // we know we always start with downloads 326 firstDl = tasks[0] 327 // and always end with installs 328 lastInst = tasks[len(tasks)-1] 329 330 var edgeTaskIndex int 331 for i, task := range tasks { 332 if task == edgeTask { 333 edgeTaskIndex = i 334 break 335 } 336 } 337 return firstDl, tasks[edgeTaskIndex], tasks[edgeTaskIndex+1], lastInst, nil 338 } 339 340 func notInstalled(st *state.State, name string) (bool, error) { 341 _, err := snapstate.CurrentInfo(st, name) 342 _, isNotInstalled := err.(*snap.NotInstalledError) 343 if isNotInstalled { 344 return true, nil 345 } 346 return false, err 347 } 348 349 func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Model, deviceCtx snapstate.DeviceContext, fromChange string) ([]*state.TaskSet, error) { 350 userID := 0 351 var tss []*state.TaskSet 352 353 // kernel 354 if current.Kernel() == new.Kernel() && current.KernelTrack() != new.KernelTrack() { 355 ts, err := snapstateUpdateWithDeviceContext(st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange) 356 if err != nil { 357 return nil, err 358 } 359 tss = append(tss, ts) 360 } 361 362 var ts *state.TaskSet 363 if current.Kernel() != new.Kernel() { 364 needsInstall, err := notInstalled(st, new.Kernel()) 365 if err != nil { 366 return nil, err 367 } 368 if needsInstall { 369 ts, err = snapstateInstallWithDeviceContext(ctx, st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{}, deviceCtx, fromChange) 370 } else { 371 ts, err = snapstate.LinkNewBaseOrKernel(st, new.Base()) 372 } 373 if err != nil { 374 return nil, err 375 } 376 tss = append(tss, ts) 377 } 378 if current.Base() != new.Base() { 379 needsInstall, err := notInstalled(st, new.Base()) 380 if err != nil { 381 return nil, err 382 } 383 if needsInstall { 384 ts, err = snapstateInstallWithDeviceContext(ctx, st, new.Base(), nil, userID, snapstate.Flags{}, deviceCtx, fromChange) 385 } else { 386 ts, err = snapstate.LinkNewBaseOrKernel(st, new.Base()) 387 } 388 if err != nil { 389 return nil, err 390 } 391 tss = append(tss, ts) 392 } 393 // gadget 394 if current.Gadget() == new.Gadget() && current.GadgetTrack() != new.GadgetTrack() { 395 ts, err := snapstateUpdateWithDeviceContext(st, new.Gadget(), &snapstate.RevisionOptions{Channel: new.GadgetTrack()}, userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange) 396 if err != nil { 397 return nil, err 398 } 399 tss = append(tss, ts) 400 } 401 if current.Gadget() != new.Gadget() { 402 ts, err := snapstateInstallWithDeviceContext(ctx, st, new.Gadget(), &snapstate.RevisionOptions{Channel: new.GadgetTrack()}, userID, snapstate.Flags{}, deviceCtx, fromChange) 403 if err != nil { 404 return nil, err 405 } 406 tss = append(tss, ts) 407 } 408 409 // add new required-snaps, no longer required snaps will be cleaned 410 // in "set-model" 411 for _, snapRef := range new.RequiredNoEssentialSnaps() { 412 // TODO|XXX: have methods that take refs directly 413 // to respect the snap ids 414 needsInstall, err := notInstalled(st, snapRef.SnapName()) 415 if err != nil { 416 return nil, err 417 } 418 if needsInstall { 419 // If the snap is not installed we need to install it now. 420 ts, err := snapstateInstallWithDeviceContext(ctx, st, snapRef.SnapName(), nil, userID, snapstate.Flags{Required: true}, deviceCtx, fromChange) 421 if err != nil { 422 return nil, err 423 } 424 tss = append(tss, ts) 425 } 426 } 427 // TODO: Validate that all bases and default-providers are part 428 // of the install tasksets and error if not. If the 429 // prereq task handler check starts adding installs into 430 // our remodel change our carefully constructed wait chain 431 // breaks down. 432 433 // Ensure all download/check tasks are run *before* the install 434 // tasks. During a remodel the network may not be available so 435 // we need to ensure we have everything local. 436 var lastDownloadInChain, firstInstallInChain *state.Task 437 var prevDownload, prevInstall *state.Task 438 for _, ts := range tss { 439 // make sure all things happen sequentially 440 // Terminology 441 // A <- B means B waits for A 442 // "download,verify" are part of the "Download" phase 443 // "link,start" is part of "Install" phase 444 // 445 // - all tasks inside ts{Download,Install} already wait for 446 // each other so the chains look something like this: 447 // download1 <- verify1 <- install1 448 // download2 <- verify2 <- install2 449 // download3 <- verify3 <- install3 450 // - add wait of each first ts{Download,Install} task for 451 // the last previous ts{Download,Install} task 452 // Our chains now looks like: 453 // download1 <- verify1 <- install1 (as before) 454 // download2 <- verify2 <- install2 (as before) 455 // download3 <- verify3 <- install3 (as before) 456 // verify1 <- download2 (added) 457 // verify2 <- download3 (added) 458 // install1 <- install2 (added) 459 // install2 <- install3 (added) 460 downloadStart, downloadLast, installFirst, installLast, err := extractDownloadInstallEdgesFromTs(ts) 461 if err != nil { 462 return nil, fmt.Errorf("cannot remodel: %v", err) 463 } 464 if prevDownload != nil { 465 // XXX: we don't strictly need to serialize the download 466 downloadStart.WaitFor(prevDownload) 467 } 468 if prevInstall != nil { 469 installFirst.WaitFor(prevInstall) 470 } 471 prevDownload = downloadLast 472 prevInstall = installLast 473 // update global state 474 lastDownloadInChain = downloadLast 475 if firstInstallInChain == nil { 476 firstInstallInChain = installFirst 477 } 478 } 479 // Make sure the first install waits for the last download. With this 480 // our (simplified) wait chain looks like: 481 // download1 <- verify1 <- download2 <- verify2 <- download3 <- verify3 <- install1 <- install2 <- install3 482 if firstInstallInChain != nil && lastDownloadInChain != nil { 483 firstInstallInChain.WaitFor(lastDownloadInChain) 484 } 485 486 // Set the new model assertion - this *must* be the last thing done 487 // by the change. 488 setModel := st.NewTask("set-model", i18n.G("Set new model assertion")) 489 for _, tsPrev := range tss { 490 setModel.WaitAll(tsPrev) 491 } 492 tss = append(tss, state.NewTaskSet(setModel)) 493 494 return tss, nil 495 } 496 497 // Remodel takes a new model assertion and generates a change that 498 // takes the device from the old to the new model or an error if the 499 // transition is not possible. 500 // 501 // TODO: 502 // - Check estimated disk size delta 503 // - Check all relevant snaps exist in new store 504 // (need to check that even unchanged snaps are accessible) 505 // - Make sure this works with Core 20 as well, in the Core 20 case 506 // we must enforce the default-channels from the model as well 507 func Remodel(st *state.State, new *asserts.Model) (*state.Change, error) { 508 var seeded bool 509 err := st.Get("seeded", &seeded) 510 if err != nil && err != state.ErrNoState { 511 return nil, err 512 } 513 if !seeded { 514 return nil, fmt.Errorf("cannot remodel until fully seeded") 515 } 516 517 current, err := findModel(st) 518 if err != nil { 519 return nil, err 520 } 521 if current.Series() != new.Series() { 522 return nil, fmt.Errorf("cannot remodel to different series yet") 523 } 524 525 // TODO:UC20: support remodel, also ensure we never remodel to a lower 526 // grade 527 if current.Grade() != asserts.ModelGradeUnset { 528 return nil, fmt.Errorf("cannot remodel Ubuntu Core 20 models yet") 529 } 530 if new.Grade() != asserts.ModelGradeUnset { 531 return nil, fmt.Errorf("cannot remodel to Ubuntu Core 20 models yet") 532 } 533 534 // TODO: we need dedicated assertion language to permit for 535 // model transitions before we allow cross vault 536 // transitions. 537 538 remodelKind := ClassifyRemodel(current, new) 539 540 // TODO: should we restrict remodel from one arch to another? 541 // There are valid use-cases here though, i.e. amd64 machine that 542 // remodels itself to/from i386 (if the HW can do both 32/64 bit) 543 if current.Architecture() != new.Architecture() { 544 return nil, fmt.Errorf("cannot remodel to different architectures yet") 545 } 546 547 // calculate snap differences between the two models 548 // FIXME: this needs work to switch from core->bases 549 if current.Base() == "" && new.Base() != "" { 550 return nil, fmt.Errorf("cannot remodel from core to bases yet") 551 } 552 553 // TODO: should we run a remodel only while no other change is 554 // running? do we add a task upfront that waits for that to be 555 // true? Do we do this only for the more complicated cases 556 // (anything more than adding required-snaps really)? 557 558 remodCtx, err := remodelCtx(st, current, new) 559 if err != nil { 560 return nil, err 561 } 562 563 var tss []*state.TaskSet 564 switch remodelKind { 565 case ReregRemodel: 566 // nothing else can be in-flight 567 for _, chg := range st.Changes() { 568 if !chg.IsReady() { 569 return nil, &snapstate.ChangeConflictError{Message: "cannot start complete remodel, other changes are in progress"} 570 } 571 } 572 573 requestSerial := st.NewTask("request-serial", i18n.G("Request new device serial")) 574 575 prepare := st.NewTask("prepare-remodeling", i18n.G("Prepare remodeling")) 576 prepare.WaitFor(requestSerial) 577 ts := state.NewTaskSet(requestSerial, prepare) 578 tss = []*state.TaskSet{ts} 579 case StoreSwitchRemodel: 580 sto := remodCtx.Store() 581 if sto == nil { 582 return nil, fmt.Errorf("internal error: a store switch remodeling should have built a store") 583 } 584 // ensure a new session accounting for the new brand store 585 st.Unlock() 586 _, err := sto.EnsureDeviceSession() 587 st.Lock() 588 if err != nil { 589 return nil, fmt.Errorf("cannot get a store session based on the new model assertion: %v", err) 590 } 591 fallthrough 592 case UpdateRemodel: 593 var err error 594 tss, err = remodelTasks(context.TODO(), st, current, new, remodCtx, "") 595 if err != nil { 596 return nil, err 597 } 598 } 599 600 // we potentially released the lock a couple of times here: 601 // make sure the current model is essentially the same as when 602 // we started 603 current1, err := findModel(st) 604 if err != nil { 605 return nil, err 606 } 607 if current.BrandID() != current1.BrandID() || current.Model() != current1.Model() || current.Revision() != current1.Revision() { 608 return nil, &snapstate.ChangeConflictError{Message: fmt.Sprintf("cannot start remodel, clashing with concurrent remodel to %v/%v (%v)", current1.BrandID(), current1.Model(), current1.Revision())} 609 } 610 // make sure another unfinished remodel wasn't already setup either 611 if Remodeling(st) { 612 return nil, &snapstate.ChangeConflictError{Message: "cannot start remodel, clashing with concurrent one"} 613 } 614 615 var msg string 616 if current.BrandID() == new.BrandID() && current.Model() == new.Model() { 617 msg = fmt.Sprintf(i18n.G("Refresh model assertion from revision %v to %v"), current.Revision(), new.Revision()) 618 } else { 619 msg = fmt.Sprintf(i18n.G("Remodel device to %v/%v (%v)"), new.BrandID(), new.Model(), new.Revision()) 620 } 621 622 chg := st.NewChange("remodel", msg) 623 remodCtx.Init(chg) 624 for _, ts := range tss { 625 chg.AddAll(ts) 626 } 627 628 return chg, nil 629 } 630 631 // Remodeling returns true whether there's a remodeling in progress 632 func Remodeling(st *state.State) bool { 633 for _, chg := range st.Changes() { 634 if !chg.IsReady() && chg.Kind() == "remodel" { 635 return true 636 } 637 } 638 return false 639 }