github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/configstate/configcore/vitality.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 // +build !nomanagers 3 4 /* 5 * Copyright (C) 2020 Canonical Ltd 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 3 as 9 * published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 package configcore 22 23 import ( 24 "fmt" 25 "strings" 26 27 "github.com/snapcore/snapd/overlord/configstate/config" 28 "github.com/snapcore/snapd/overlord/servicestate" 29 "github.com/snapcore/snapd/overlord/snapstate" 30 "github.com/snapcore/snapd/overlord/state" 31 "github.com/snapcore/snapd/progress" 32 "github.com/snapcore/snapd/snap" 33 "github.com/snapcore/snapd/snap/naming" 34 "github.com/snapcore/snapd/timings" 35 "github.com/snapcore/snapd/wrappers" 36 ) 37 38 const vitalityOpt = "resilience.vitality-hint" 39 40 func init() { 41 // add supported configuration of this module 42 supportedConfigurations["core."+vitalityOpt] = true 43 } 44 45 func handleVitalityConfiguration(tr config.Conf, opts *fsOnlyContext) error { 46 var pristineVitalityStr, newVitalityStr string 47 48 if err := tr.GetPristine("core", vitalityOpt, &pristineVitalityStr); err != nil && !config.IsNoOption(err) { 49 return err 50 } 51 if err := tr.Get("core", vitalityOpt, &newVitalityStr); err != nil && !config.IsNoOption(err) { 52 return err 53 } 54 if pristineVitalityStr == newVitalityStr { 55 return nil 56 } 57 58 st := tr.State() 59 st.Lock() 60 defer st.Unlock() 61 62 // TODO: Reimplement most of this as a servicestate.UpdateVitalityRank 63 64 oldVitalityMap := map[string]int{} 65 newVitalityMap := map[string]int{} 66 // assign "0" (delete) rank to all old entries 67 for i, instanceName := range strings.Split(pristineVitalityStr, ",") { 68 oldVitalityMap[instanceName] = i + 1 69 newVitalityMap[instanceName] = 0 70 } 71 // build rank of the new entries 72 for i, instanceName := range strings.Split(newVitalityStr, ",") { 73 newVitalityMap[instanceName] = i + 1 74 } 75 76 // use a single cache of the quota groups for calculating the quota groups 77 // that services should be in 78 grps, err := servicestate.AllQuotas(st) 79 if err != nil { 80 return err 81 } 82 83 for instanceName, rank := range newVitalityMap { 84 var snapst snapstate.SnapState 85 err := snapstate.Get(st, instanceName, &snapst) 86 // not installed, vitality-score will be applied when the snap 87 // gets installed 88 if err == state.ErrNoState { 89 continue 90 } 91 if err != nil { 92 return err 93 } 94 // not active, vitality-score will be applied when the snap 95 // becomes active 96 if !snapst.Active { 97 continue 98 } 99 info, err := snapst.CurrentInfo() 100 if err != nil { 101 return err 102 } 103 104 // nothing to do if rank is unchanged 105 if oldVitalityMap[instanceName] == newVitalityMap[instanceName] { 106 continue 107 } 108 109 // rank changed, rewrite/restart services 110 111 // first get the device context to decide if we need to set 112 // RequireMountedSnapdSnap 113 // TODO: use sysconfig.Device instead 114 deviceCtx, err := snapstate.DeviceCtx(st, nil, nil) 115 if err != nil { 116 return err 117 } 118 119 ensureOpts := &wrappers.EnsureSnapServicesOptions{} 120 121 // we need the snapd snap mounted whenever in order for services to 122 // start for all services on UC18+ 123 if !deviceCtx.Classic() && deviceCtx.Model().Base() != "" { 124 ensureOpts.RequireMountedSnapdSnap = true 125 } 126 127 // get the options for this snap service 128 snapSvcOpts, err := servicestate.SnapServiceOptions(st, instanceName, grps) 129 if err != nil { 130 return err 131 } 132 133 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 134 info: snapSvcOpts, 135 } 136 137 // overwrite the VitalityRank we got from SnapServiceOptions to use the 138 // rank we calculated as part of this transaction 139 m[info].VitalityRank = rank 140 141 // ensure that the snap services are re-written with these units 142 if err := wrappers.EnsureSnapServices(m, ensureOpts, nil, progress.Null); err != nil { 143 return err 144 } 145 146 // and then restart the services 147 148 // TODO: this doesn't actually restart the services, meaning that the 149 // OOMScoreAdjust vitality-hint ranking doesn't take effect until the 150 // service is restarted by a refresh or a reboot, etc. 151 // TODO: this option also doesn't work with services that use 152 // Delegate=true, i.e. docker, greengrass, kubernetes, so we should do 153 // something about that combination because currently we jus silently 154 // apply the setting which never does anything 155 156 // XXX: copied from handlers.go:startSnapServices() 157 158 disabledSvcs, err := wrappers.QueryDisabledServices(info, progress.Null) 159 if err != nil { 160 return err 161 } 162 163 svcs := info.Services() 164 startupOrdered, err := snap.SortServices(svcs) 165 if err != nil { 166 return err 167 } 168 flags := &wrappers.StartServicesFlags{Enable: true} 169 tm := timings.New(nil) 170 if err = wrappers.StartServices(startupOrdered, disabledSvcs, flags, progress.Null, tm); err != nil { 171 return err 172 } 173 } 174 175 return nil 176 } 177 178 func validateVitalitySettings(tr config.Conf) error { 179 option, err := coreCfg(tr, vitalityOpt) 180 if err != nil { 181 return err 182 } 183 if option == "" { 184 return nil 185 } 186 vitalityHints := strings.Split(option, ",") 187 if len(vitalityHints) > 100 { 188 return fmt.Errorf("cannot set more than 100 snaps in %q: got %v", vitalityOpt, len(vitalityHints)) 189 } 190 for _, instanceName := range vitalityHints { 191 if err := naming.ValidateInstance(instanceName); err != nil { 192 return fmt.Errorf("cannot set %q: %v", vitalityOpt, err) 193 } 194 // The "snapd" snap is always at OOMScoreAdjust=-900. 195 if instanceName == "snapd" { 196 return fmt.Errorf("cannot set %q: snapd snap vitality cannot be changed", vitalityOpt) 197 } 198 } 199 200 return nil 201 }