github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/overlord/configstate/configstate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 configstate_test 21 22 import ( 23 "fmt" 24 "time" 25 26 . "gopkg.in/check.v1" 27 28 "github.com/snapcore/snapd/dirs" 29 "github.com/snapcore/snapd/features" 30 "github.com/snapcore/snapd/gadget" 31 "github.com/snapcore/snapd/overlord" 32 "github.com/snapcore/snapd/overlord/configstate" 33 "github.com/snapcore/snapd/overlord/configstate/config" 34 "github.com/snapcore/snapd/overlord/hookstate" 35 "github.com/snapcore/snapd/overlord/snapstate" 36 "github.com/snapcore/snapd/overlord/state" 37 "github.com/snapcore/snapd/snap" 38 "github.com/snapcore/snapd/testutil" 39 ) 40 41 type tasksetsSuite struct { 42 state *state.State 43 } 44 45 var ( 46 _ = Suite(&tasksetsSuite{}) 47 _ = Suite(&configcoreHijackSuite{}) 48 _ = Suite(&miscSuite{}) 49 _ = Suite(&earlyConfigSuite{}) 50 ) 51 52 func (s *tasksetsSuite) SetUpTest(c *C) { 53 s.state = state.New(nil) 54 } 55 56 var configureTests = []struct { 57 patch map[string]interface{} 58 optional bool 59 ignoreError bool 60 useDefaults bool 61 }{{ 62 patch: nil, 63 optional: true, 64 ignoreError: false, 65 }, { 66 patch: map[string]interface{}{}, 67 optional: true, 68 ignoreError: false, 69 }, { 70 patch: map[string]interface{}{"foo": "bar"}, 71 optional: false, 72 ignoreError: false, 73 }, { 74 patch: nil, 75 optional: true, 76 ignoreError: true, 77 }, { 78 patch: nil, 79 optional: true, 80 ignoreError: true, 81 useDefaults: true, 82 }} 83 84 func (s *tasksetsSuite) TestConfigureInstalled(c *C) { 85 s.state.Lock() 86 snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ 87 Sequence: []*snap.SideInfo{ 88 {RealName: "test-snap", Revision: snap.R(1)}, 89 }, 90 Current: snap.R(1), 91 Active: true, 92 SnapType: "app", 93 }) 94 s.state.Unlock() 95 96 for _, test := range configureTests { 97 var flags int 98 if test.ignoreError { 99 flags |= snapstate.IgnoreHookError 100 } 101 if test.useDefaults { 102 flags |= snapstate.UseConfigDefaults 103 } 104 105 s.state.Lock() 106 taskset := configstate.Configure(s.state, "test-snap", test.patch, flags) 107 s.state.Unlock() 108 109 tasks := taskset.Tasks() 110 c.Assert(tasks, HasLen, 1) 111 task := tasks[0] 112 113 c.Assert(task.Kind(), Equals, "run-hook") 114 115 summary := `Run configure hook of "test-snap" snap` 116 if test.optional { 117 summary += " if present" 118 } 119 c.Assert(task.Summary(), Equals, summary) 120 121 var hooksup hookstate.HookSetup 122 s.state.Lock() 123 err := task.Get("hook-setup", &hooksup) 124 s.state.Unlock() 125 c.Check(err, IsNil) 126 127 c.Assert(hooksup.Snap, Equals, "test-snap") 128 c.Assert(hooksup.Hook, Equals, "configure") 129 c.Assert(hooksup.Optional, Equals, test.optional) 130 c.Assert(hooksup.IgnoreError, Equals, test.ignoreError) 131 c.Assert(hooksup.Timeout, Equals, 5*time.Minute) 132 133 context, err := hookstate.NewContext(task, task.State(), &hooksup, nil, "") 134 c.Check(err, IsNil) 135 c.Check(context.InstanceName(), Equals, "test-snap") 136 c.Check(context.SnapRevision(), Equals, snap.Revision{}) 137 c.Check(context.HookName(), Equals, "configure") 138 139 var patch map[string]interface{} 140 var useDefaults bool 141 context.Lock() 142 context.Get("use-defaults", &useDefaults) 143 err = context.Get("patch", &patch) 144 context.Unlock() 145 if len(test.patch) > 0 { 146 c.Check(err, IsNil) 147 c.Check(patch, DeepEquals, test.patch) 148 } else { 149 c.Check(err, Equals, state.ErrNoState) 150 c.Check(patch, IsNil) 151 } 152 c.Check(useDefaults, Equals, test.useDefaults) 153 } 154 } 155 156 func (s *tasksetsSuite) TestConfigureInstalledConflict(c *C) { 157 s.state.Lock() 158 defer s.state.Unlock() 159 snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ 160 Sequence: []*snap.SideInfo{ 161 {RealName: "test-snap", Revision: snap.R(1)}, 162 }, 163 Current: snap.R(1), 164 Active: true, 165 SnapType: "app", 166 }) 167 168 ts, err := snapstate.Disable(s.state, "test-snap") 169 c.Assert(err, IsNil) 170 chg := s.state.NewChange("other-change", "...") 171 chg.AddAll(ts) 172 173 patch := map[string]interface{}{"foo": "bar"} 174 _, err = configstate.ConfigureInstalled(s.state, "test-snap", patch, 0) 175 c.Check(err, ErrorMatches, `snap "test-snap" has "other-change" change in progress`) 176 } 177 178 func (s *tasksetsSuite) TestConfigureNotInstalled(c *C) { 179 patch := map[string]interface{}{"foo": "bar"} 180 s.state.Lock() 181 defer s.state.Unlock() 182 183 _, err := configstate.ConfigureInstalled(s.state, "test-snap", patch, 0) 184 c.Check(err, ErrorMatches, `snap "test-snap" is not installed`) 185 186 // core can be configure before being installed 187 _, err = configstate.ConfigureInstalled(s.state, "core", patch, 0) 188 c.Check(err, IsNil) 189 } 190 191 func (s *tasksetsSuite) TestConfigureDenyBases(c *C) { 192 patch := map[string]interface{}{"foo": "bar"} 193 s.state.Lock() 194 defer s.state.Unlock() 195 snapstate.Set(s.state, "test-base", &snapstate.SnapState{ 196 Sequence: []*snap.SideInfo{ 197 {RealName: "test-base", Revision: snap.R(1)}, 198 }, 199 Current: snap.R(1), 200 Active: true, 201 SnapType: "base", 202 }) 203 204 _, err := configstate.ConfigureInstalled(s.state, "test-base", patch, 0) 205 c.Check(err, ErrorMatches, `cannot configure snap "test-base" because it is of type 'base'`) 206 } 207 208 func (s *tasksetsSuite) TestConfigureDenySnapd(c *C) { 209 patch := map[string]interface{}{"foo": "bar"} 210 s.state.Lock() 211 defer s.state.Unlock() 212 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 213 Sequence: []*snap.SideInfo{ 214 {RealName: "snapd", Revision: snap.R(1)}, 215 }, 216 Current: snap.R(1), 217 Active: true, 218 SnapType: "snapd", 219 }) 220 221 _, err := configstate.ConfigureInstalled(s.state, "snapd", patch, 0) 222 c.Check(err, ErrorMatches, `cannot configure the "snapd" snap, please use "system" instead`) 223 } 224 225 type configcoreHijackSuite struct { 226 testutil.BaseTest 227 228 o *overlord.Overlord 229 state *state.State 230 } 231 232 func (s *configcoreHijackSuite) SetUpTest(c *C) { 233 s.BaseTest.SetUpTest(c) 234 s.o = overlord.Mock() 235 s.state = s.o.State() 236 hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner()) 237 c.Assert(err, IsNil) 238 s.o.AddManager(hookMgr) 239 r := configstate.MockConfigcoreExportExperimentalFlags(func(_ config.ConfGetter) error { 240 return nil 241 }) 242 s.AddCleanup(r) 243 244 err = configstate.Init(s.state, hookMgr) 245 c.Assert(err, IsNil) 246 s.o.AddManager(s.o.TaskRunner()) 247 } 248 249 type witnessManager struct { 250 state *state.State 251 committed bool 252 } 253 254 func (wm *witnessManager) Ensure() error { 255 wm.state.Lock() 256 defer wm.state.Unlock() 257 t := config.NewTransaction(wm.state) 258 var witnessCfg bool 259 t.GetMaybe("core", "witness", &witnessCfg) 260 if witnessCfg { 261 wm.committed = true 262 } 263 return nil 264 } 265 266 func (s *configcoreHijackSuite) TestHijack(c *C) { 267 configcoreRan := false 268 witnessCfg := false 269 witnessConfigcoreRun := func(conf config.Conf) error { 270 // called with no state lock! 271 conf.State().Lock() 272 defer conf.State().Unlock() 273 err := conf.Get("core", "witness", &witnessCfg) 274 c.Assert(err, IsNil) 275 configcoreRan = true 276 return nil 277 } 278 r := configstate.MockConfigcoreRun(witnessConfigcoreRun) 279 defer r() 280 281 witnessMgr := &witnessManager{ 282 state: s.state, 283 } 284 s.o.AddManager(witnessMgr) 285 286 s.state.Lock() 287 defer s.state.Unlock() 288 289 ts := configstate.Configure(s.state, "core", map[string]interface{}{ 290 "witness": true, 291 }, 0) 292 293 chg := s.state.NewChange("configure-core", "configure core") 294 chg.AddAll(ts) 295 296 // this will be run by settle helper once no more Ensure are 297 // scheduled, the witnessMgr Ensure would not see the 298 // committed config unless an additional Ensure Loop is 299 // scheduled when committing the configuration 300 observe := func() { 301 c.Check(witnessCfg, Equals, true) 302 c.Check(witnessMgr.committed, Equals, true) 303 } 304 305 s.state.Unlock() 306 err := s.o.SettleObserveBeforeCleanups(5*time.Second, observe) 307 s.state.Lock() 308 c.Assert(err, IsNil) 309 310 c.Check(chg.Err(), IsNil) 311 c.Check(configcoreRan, Equals, true) 312 } 313 314 type miscSuite struct{} 315 316 func (s *miscSuite) TestRemappingFuncs(c *C) { 317 // We don't change those. 318 c.Assert(configstate.RemapSnapFromRequest("foo"), Equals, "foo") 319 c.Assert(configstate.RemapSnapFromRequest("snapd"), Equals, "snapd") 320 c.Assert(configstate.RemapSnapFromRequest("core"), Equals, "core") 321 c.Assert(configstate.RemapSnapToResponse("foo"), Equals, "foo") 322 c.Assert(configstate.RemapSnapToResponse("snapd"), Equals, "snapd") 323 324 // This is the one we alter. 325 c.Assert(configstate.RemapSnapFromRequest("system"), Equals, "core") 326 c.Assert(configstate.RemapSnapToResponse("core"), Equals, "system") 327 } 328 329 type earlyConfigSuite struct { 330 testutil.BaseTest 331 332 state *state.State 333 rootdir string 334 } 335 336 func (s *earlyConfigSuite) SetUpTest(c *C) { 337 s.BaseTest.SetUpTest(c) 338 339 s.state = state.New(nil) 340 341 s.rootdir = c.MkDir() 342 343 dirs.SetRootDir(s.rootdir) 344 s.AddCleanup(func() { dirs.SetRootDir("") }) 345 } 346 347 func (s *earlyConfigSuite) sysConfig(c *C) { 348 t := config.NewTransaction(s.state) 349 err := t.Set("core", "experimental.parallel-instances", true) 350 c.Assert(err, IsNil) 351 err = t.Set("core", "experimental.user-daemons", true) 352 c.Assert(err, IsNil) 353 t.Commit() 354 } 355 356 func (s *earlyConfigSuite) TestEarlyConfigSeeded(c *C) { 357 s.state.Lock() 358 defer s.state.Unlock() 359 s.sysConfig(c) 360 s.state.Set("seeded", true) 361 362 err := configstate.EarlyConfig(s.state, nil) 363 c.Assert(err, IsNil) 364 // parallel-instances was exported 365 c.Check(features.ParallelInstances.IsEnabled(), Equals, true) 366 } 367 368 func (s *earlyConfigSuite) TestEarlyConfigSeededErr(c *C) { 369 r := configstate.MockConfigcoreExportExperimentalFlags(func(conf config.ConfGetter) error { 370 return fmt.Errorf("bad bad") 371 }) 372 defer r() 373 374 s.state.Lock() 375 defer s.state.Unlock() 376 s.state.Set("seeded", true) 377 378 err := configstate.EarlyConfig(s.state, nil) 379 c.Assert(err, ErrorMatches, "cannot export experimental config flags: bad bad") 380 } 381 382 func (s *earlyConfigSuite) TestEarlyConfigSysConfigured(c *C) { 383 s.state.Lock() 384 defer s.state.Unlock() 385 s.sysConfig(c) 386 387 preloadGadget := func() (*gadget.Info, error) { 388 panic("unexpected") 389 } 390 391 err := configstate.EarlyConfig(s.state, preloadGadget) 392 c.Assert(err, IsNil) 393 // parallel-instances was exported 394 c.Check(features.ParallelInstances.IsEnabled(), Equals, true) 395 } 396 397 const preloadedGadgetYaml = ` 398 defaults: 399 system: 400 experimental: 401 parallel-instances: true 402 user-daemons: true 403 services: 404 ssh.disable 405 ` 406 407 func (s *earlyConfigSuite) TestEarlyConfigFromGadget(c *C) { 408 s.state.Lock() 409 defer s.state.Unlock() 410 411 preloadGadget := func() (*gadget.Info, error) { 412 return gadget.InfoFromGadgetYaml([]byte(preloadedGadgetYaml), nil) 413 } 414 415 err := configstate.EarlyConfig(s.state, preloadGadget) 416 c.Assert(err, IsNil) 417 418 // parallel-instances was exported 419 c.Check(features.ParallelInstances.IsEnabled(), Equals, true) 420 tr := config.NewTransaction(s.state) 421 ok, err := features.Flag(tr, features.ParallelInstances) 422 c.Assert(err, IsNil) 423 c.Check(ok, Equals, true) 424 ok, err = features.Flag(tr, features.UserDaemons) 425 c.Assert(err, IsNil) 426 c.Check(ok, Equals, true) 427 var serviceCfg map[string]interface{} 428 err = tr.Get("core", "services", &serviceCfg) 429 // nothing of this was set 430 c.Assert(config.IsNoOption(err), Equals, true) 431 } 432 433 func (s *earlyConfigSuite) TestEarlyConfigFromGadgetErr(c *C) { 434 defer configstate.MockConfigcoreEarly(func(config.Conf, map[string]interface{}) error { 435 return fmt.Errorf("boom") 436 })() 437 438 s.state.Lock() 439 defer s.state.Unlock() 440 441 preloadGadget := func() (*gadget.Info, error) { 442 return gadget.InfoFromGadgetYaml([]byte(preloadedGadgetYaml), nil) 443 } 444 445 err := configstate.EarlyConfig(s.state, preloadGadget) 446 c.Assert(err, ErrorMatches, "boom") 447 } 448 449 func (s *earlyConfigSuite) TestEarlyConfigPreloadGadgetErr(c *C) { 450 s.state.Lock() 451 defer s.state.Unlock() 452 453 preloadGadget := func() (*gadget.Info, error) { 454 return nil, fmt.Errorf("cannot load gadget") 455 } 456 457 err := configstate.EarlyConfig(s.state, preloadGadget) 458 c.Assert(err, ErrorMatches, "cannot load gadget") 459 } 460 461 func (s *earlyConfigSuite) TestEarlyConfigNoGadget(c *C) { 462 s.state.Lock() 463 defer s.state.Unlock() 464 465 preloadGadget := func() (*gadget.Info, error) { 466 return nil, state.ErrNoState 467 } 468 469 err := configstate.EarlyConfig(s.state, preloadGadget) 470 c.Assert(err, IsNil) 471 472 sysCfg, err := config.GetSnapConfig(s.state, "core") 473 c.Assert(err, IsNil) 474 c.Check(sysCfg, IsNil) 475 }