github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/snapstate/conflict.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-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 snapstate 21 22 import ( 23 "fmt" 24 "reflect" 25 26 "github.com/snapcore/snapd/overlord/state" 27 ) 28 29 // FinalTasks are task kinds for final tasks in a change which means no further 30 // change work should be performed afterward, usually these are tasks that 31 // commit a full system transition. 32 var FinalTasks = []string{"mark-seeded", "set-model"} 33 34 // ChangeConflictError represents an error because of snap conflicts between changes. 35 type ChangeConflictError struct { 36 Snap string 37 ChangeKind string 38 // a Message is optional, otherwise one is composed from the other information 39 Message string 40 } 41 42 func (e *ChangeConflictError) Error() string { 43 if e.Message != "" { 44 return e.Message 45 } 46 if e.ChangeKind != "" { 47 return fmt.Sprintf("snap %q has %q change in progress", e.Snap, e.ChangeKind) 48 } 49 return fmt.Sprintf("snap %q has changes in progress", e.Snap) 50 } 51 52 // An AffectedSnapsFunc returns a list of affected snap names for the given supported task. 53 type AffectedSnapsFunc func(*state.Task) ([]string, error) 54 55 var ( 56 affectedSnapsByAttr = make(map[string]AffectedSnapsFunc) 57 affectedSnapsByKind = make(map[string]AffectedSnapsFunc) 58 ) 59 60 // AddAffectedSnapsByAttrs registers an AffectedSnapsFunc for returning the affected snaps for tasks sporting the given identifying attribute, to use in conflicts detection. 61 func AddAffectedSnapsByAttr(attr string, f AffectedSnapsFunc) { 62 affectedSnapsByAttr[attr] = f 63 } 64 65 // AddAffectedSnapsByKind registers an AffectedSnapsFunc for returning the affected snaps for tasks of the given kind, to use in conflicts detection. Whenever possible using AddAffectedSnapsByAttr should be preferred. 66 func AddAffectedSnapsByKind(kind string, f AffectedSnapsFunc) { 67 affectedSnapsByKind[kind] = f 68 } 69 70 func affectedSnaps(t *state.Task) ([]string, error) { 71 // snapstate's own styled tasks 72 if t.Has("snap-setup") || t.Has("snap-setup-task") { 73 snapsup, err := TaskSnapSetup(t) 74 if err != nil { 75 return nil, fmt.Errorf("internal error: cannot obtain snap setup from task: %s", t.Summary()) 76 } 77 return []string{snapsup.InstanceName()}, nil 78 } 79 80 if f := affectedSnapsByKind[t.Kind()]; f != nil { 81 return f(t) 82 } 83 84 for attrKey, f := range affectedSnapsByAttr { 85 if t.Has(attrKey) { 86 return f(t) 87 } 88 } 89 90 return nil, nil 91 } 92 93 // CheckChangeConflictMany ensures that for the given instanceNames no other 94 // changes that alters the snaps (like remove, install, refresh) are in 95 // progress. If a conflict is detected an error is returned. 96 // 97 // It's like CheckChangeConflict, but for multiple snaps, and does not 98 // check snapst. 99 func CheckChangeConflictMany(st *state.State, instanceNames []string, ignoreChangeID string) error { 100 snapMap := make(map[string]bool, len(instanceNames)) 101 for _, k := range instanceNames { 102 snapMap[k] = true 103 } 104 105 for _, chg := range st.Changes() { 106 if chg.Status().Ready() { 107 continue 108 } 109 if chg.Kind() == "transition-ubuntu-core" { 110 return &ChangeConflictError{Message: "ubuntu-core to core transition in progress, no other changes allowed until this is done", ChangeKind: "transition-ubuntu-core"} 111 } 112 if chg.Kind() == "transition-to-snapd-snap" { 113 return &ChangeConflictError{Message: "transition to snapd snap in progress, no other changes allowed until this is done", ChangeKind: "transition-to-snapd-snap"} 114 } 115 if chg.Kind() == "remodel" { 116 if ignoreChangeID != "" && chg.ID() == ignoreChangeID { 117 continue 118 } 119 return &ChangeConflictError{Message: "remodeling in progress, no other changes allowed until this is done", ChangeKind: "remodel"} 120 } 121 } 122 123 for _, task := range st.Tasks() { 124 chg := task.Change() 125 if chg == nil || chg.Status().Ready() { 126 continue 127 } 128 if ignoreChangeID != "" && chg.ID() == ignoreChangeID { 129 continue 130 } 131 if chg.Kind() == "become-operational" { 132 // become-operational will be retried until success 133 // and on its own just runs a hook on gadget: 134 // do not make it interfere with user requests 135 // TODO: consider a use vs change modeling of 136 // conflicts 137 continue 138 } 139 140 snaps, err := affectedSnaps(task) 141 if err != nil { 142 return err 143 } 144 145 for _, snap := range snaps { 146 if snapMap[snap] { 147 return &ChangeConflictError{Snap: snap, ChangeKind: chg.Kind()} 148 } 149 } 150 } 151 152 return nil 153 } 154 155 // CheckChangeConflict ensures that for the given instanceName no other 156 // changes that alters the snap (like remove, install, refresh) are in 157 // progress. It also ensures that snapst (if not nil) did not get 158 // modified. If a conflict is detected an error is returned. 159 func CheckChangeConflict(st *state.State, instanceName string, snapst *SnapState) error { 160 return checkChangeConflictIgnoringOneChange(st, instanceName, snapst, "") 161 } 162 163 func checkChangeConflictIgnoringOneChange(st *state.State, instanceName string, snapst *SnapState, ignoreChangeID string) error { 164 if err := CheckChangeConflictMany(st, []string{instanceName}, ignoreChangeID); err != nil { 165 return err 166 } 167 168 if snapst != nil { 169 // caller wants us to also make sure the SnapState in state 170 // matches the one they provided. Necessary because we need to 171 // unlock while talking to the store, during which a change can 172 // sneak in (if it's before the taskset is created) (e.g. for 173 // install, while getting the snap info; for refresh, when 174 // getting what needs refreshing). 175 var cursnapst SnapState 176 if err := Get(st, instanceName, &cursnapst); err != nil && err != state.ErrNoState { 177 return err 178 } 179 180 // TODO: implement the rather-boring-but-more-performant SnapState.Equals 181 if !reflect.DeepEqual(snapst, &cursnapst) { 182 return &ChangeConflictError{Snap: instanceName} 183 } 184 } 185 186 return nil 187 }