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