github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/daemon/api_general_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2020 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 daemon_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "io" 27 "net/http" 28 "net/http/httptest" 29 "net/url" 30 "time" 31 32 "gopkg.in/check.v1" 33 34 "github.com/snapcore/snapd/arch" 35 "github.com/snapcore/snapd/boot" 36 "github.com/snapcore/snapd/daemon" 37 "github.com/snapcore/snapd/dirs" 38 "github.com/snapcore/snapd/interfaces/ifacetest" 39 "github.com/snapcore/snapd/overlord/auth" 40 "github.com/snapcore/snapd/overlord/configstate/config" 41 "github.com/snapcore/snapd/overlord/state" 42 "github.com/snapcore/snapd/release" 43 "github.com/snapcore/snapd/sandbox" 44 ) 45 46 var _ = check.Suite(&generalSuite{}) 47 48 type generalSuite struct { 49 apiBaseSuite 50 } 51 52 func (s *generalSuite) TestRoot(c *check.C) { 53 s.daemon(c) 54 55 req, err := http.NewRequest("GET", "/", nil) 56 c.Assert(err, check.IsNil) 57 58 // check it only does GET 59 s.checkGetOnly(c, req) 60 61 rec := httptest.NewRecorder() 62 s.req(c, req, nil).ServeHTTP(rec, nil) 63 c.Check(rec.Code, check.Equals, 200) 64 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 65 66 expected := []interface{}{"TBD"} 67 var rsp daemon.Resp 68 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) 69 c.Check(rsp.Status, check.Equals, 200) 70 c.Check(rsp.Result, check.DeepEquals, expected) 71 } 72 73 func (s *generalSuite) TestSysInfo(c *check.C) { 74 req, err := http.NewRequest("GET", "/v2/system-info", nil) 75 c.Assert(err, check.IsNil) 76 77 d := s.daemon(c) 78 d.Version = "42b1" 79 80 // check it only does GET 81 s.checkGetOnly(c, req) 82 83 // set both legacy and new refresh schedules. new one takes priority 84 st := d.Overlord().State() 85 st.Lock() 86 tr := config.NewTransaction(st) 87 tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00") 88 tr.Set("core", "refresh.timer", "8:00~9:00/2") 89 tr.Commit() 90 st.Unlock() 91 92 restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"}) 93 defer restore() 94 restore = release.MockOnClassic(true) 95 defer restore() 96 restore = sandbox.MockForceDevMode(true) 97 defer restore() 98 // reload dirs for release info to have effect 99 dirs.SetRootDir(dirs.GlobalRootDir) 100 restore = daemon.MockSystemdVirt("magic") 101 defer restore() 102 103 buildID := "this-is-my-build-id" 104 restore = daemon.MockBuildID(buildID) 105 defer restore() 106 107 rec := httptest.NewRecorder() 108 s.req(c, req, nil).ServeHTTP(rec, nil) 109 c.Check(rec.Code, check.Equals, 200) 110 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 111 112 expected := map[string]interface{}{ 113 "series": "16", 114 "version": "42b1", 115 "os-release": map[string]interface{}{ 116 "id": "distro-id", 117 "version-id": "1.2", 118 }, 119 "build-id": buildID, 120 "on-classic": true, 121 "managed": false, 122 "locations": map[string]interface{}{ 123 "snap-mount-dir": dirs.SnapMountDir, 124 "snap-bin-dir": dirs.SnapBinariesDir, 125 }, 126 "refresh": map[string]interface{}{ 127 // only the "timer" field 128 "timer": "8:00~9:00/2", 129 }, 130 "confinement": "partial", 131 "sandbox-features": map[string]interface{}{"confinement-options": []interface{}{"classic", "devmode"}}, 132 "architecture": arch.DpkgArchitecture(), 133 "virtualization": "magic", 134 "system-mode": "run", 135 } 136 var rsp daemon.Resp 137 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) 138 c.Check(rsp.Status, check.Equals, 200) 139 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 140 // Ensure that we had a kernel-verrsion but don't check the actual value. 141 const kernelVersionKey = "kernel-version" 142 c.Check(rsp.Result.(map[string]interface{})[kernelVersionKey], check.Not(check.Equals), "") 143 delete(rsp.Result.(map[string]interface{}), kernelVersionKey) 144 c.Check(rsp.Result, check.DeepEquals, expected) 145 } 146 147 func (s *generalSuite) TestSysInfoLegacyRefresh(c *check.C) { 148 req, err := http.NewRequest("GET", "/v2/system-info", nil) 149 c.Assert(err, check.IsNil) 150 151 d := s.daemon(c) 152 d.Version = "42b1" 153 154 restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"}) 155 defer restore() 156 restore = release.MockOnClassic(true) 157 defer restore() 158 restore = sandbox.MockForceDevMode(true) 159 defer restore() 160 restore = daemon.MockSystemdVirt("kvm") 161 defer restore() 162 // reload dirs for release info to have effect 163 dirs.SetRootDir(dirs.GlobalRootDir) 164 165 // set the legacy refresh schedule 166 st := d.Overlord().State() 167 st.Lock() 168 tr := config.NewTransaction(st) 169 tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00") 170 tr.Set("core", "refresh.timer", "") 171 tr.Commit() 172 st.Unlock() 173 174 // add a test security backend 175 err = d.Overlord().InterfaceManager().Repository().AddBackend(&ifacetest.TestSecurityBackend{ 176 BackendName: "apparmor", 177 SandboxFeaturesCallback: func() []string { return []string{"feature-1", "feature-2"} }, 178 }) 179 c.Assert(err, check.IsNil) 180 181 buildID := "this-is-my-build-id" 182 restore = daemon.MockBuildID(buildID) 183 defer restore() 184 185 rec := httptest.NewRecorder() 186 s.req(c, req, nil).ServeHTTP(rec, nil) 187 c.Check(rec.Code, check.Equals, 200) 188 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 189 190 expected := map[string]interface{}{ 191 "series": "16", 192 "version": "42b1", 193 "os-release": map[string]interface{}{ 194 "id": "distro-id", 195 "version-id": "1.2", 196 }, 197 "build-id": buildID, 198 "on-classic": true, 199 "managed": false, 200 "locations": map[string]interface{}{ 201 "snap-mount-dir": dirs.SnapMountDir, 202 "snap-bin-dir": dirs.SnapBinariesDir, 203 }, 204 "refresh": map[string]interface{}{ 205 // only the "schedule" field 206 "schedule": "00:00-9:00/12:00-13:00", 207 }, 208 "confinement": "partial", 209 "sandbox-features": map[string]interface{}{ 210 "apparmor": []interface{}{"feature-1", "feature-2"}, 211 "confinement-options": []interface{}{"classic", "devmode"}, // we know it's this because of the release.Mock... calls above 212 }, 213 "architecture": arch.DpkgArchitecture(), 214 "virtualization": "kvm", 215 "system-mode": "run", 216 } 217 var rsp daemon.Resp 218 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) 219 c.Check(rsp.Status, check.Equals, 200) 220 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 221 const kernelVersionKey = "kernel-version" 222 delete(rsp.Result.(map[string]interface{}), kernelVersionKey) 223 c.Check(rsp.Result, check.DeepEquals, expected) 224 } 225 226 func (s *generalSuite) testSysInfoSystemMode(c *check.C, mode string) { 227 req, err := http.NewRequest("GET", "/v2/system-info", nil) 228 c.Assert(err, check.IsNil) 229 230 c.Assert(mode != "", check.Equals, true, check.Commentf("mode is unset for the test")) 231 232 restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"}) 233 defer restore() 234 restore = release.MockOnClassic(false) 235 defer restore() 236 restore = sandbox.MockForceDevMode(false) 237 defer restore() 238 restore = daemon.MockSystemdVirt("") 239 defer restore() 240 241 // reload dirs for release info to have effect on paths 242 dirs.SetRootDir(dirs.GlobalRootDir) 243 244 // mock the modeenv file 245 m := boot.Modeenv{ 246 Mode: mode, 247 RecoverySystem: "20191127", 248 } 249 c.Assert(m.WriteTo(""), check.IsNil) 250 251 d := s.daemon(c) 252 d.Version = "42b1" 253 254 // add a test security backend 255 err = d.Overlord().InterfaceManager().Repository().AddBackend(&ifacetest.TestSecurityBackend{ 256 BackendName: "apparmor", 257 SandboxFeaturesCallback: func() []string { return []string{"feature-1", "feature-2"} }, 258 }) 259 c.Assert(err, check.IsNil) 260 261 buildID := "this-is-my-build-id" 262 restore = daemon.MockBuildID(buildID) 263 defer restore() 264 265 rec := httptest.NewRecorder() 266 s.req(c, req, nil).ServeHTTP(rec, nil) 267 c.Check(rec.Code, check.Equals, 200) 268 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") 269 270 expected := map[string]interface{}{ 271 "series": "16", 272 "version": "42b1", 273 "os-release": map[string]interface{}{ 274 "id": "distro-id", 275 "version-id": "1.2", 276 }, 277 "build-id": buildID, 278 "on-classic": false, 279 "managed": false, 280 "locations": map[string]interface{}{ 281 "snap-mount-dir": dirs.SnapMountDir, 282 "snap-bin-dir": dirs.SnapBinariesDir, 283 }, 284 "refresh": map[string]interface{}{ 285 "timer": "00:00~24:00/4", 286 }, 287 "confinement": "strict", 288 "sandbox-features": map[string]interface{}{ 289 "apparmor": []interface{}{"feature-1", "feature-2"}, 290 "confinement-options": []interface{}{"devmode", "strict"}, // we know it's this because of the release.Mock... calls above 291 }, 292 "architecture": arch.DpkgArchitecture(), 293 "system-mode": mode, 294 } 295 var rsp daemon.Resp 296 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) 297 c.Check(rsp.Status, check.Equals, 200) 298 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 299 const kernelVersionKey = "kernel-version" 300 delete(rsp.Result.(map[string]interface{}), kernelVersionKey) 301 c.Check(rsp.Result, check.DeepEquals, expected) 302 } 303 304 func (s *generalSuite) TestSysInfoSystemModeRun(c *check.C) { 305 s.testSysInfoSystemMode(c, "run") 306 } 307 308 func (s *generalSuite) TestSysInfoSystemModeRecover(c *check.C) { 309 s.testSysInfoSystemMode(c, "recover") 310 } 311 312 func (s *generalSuite) TestSysInfoSystemModeInstall(c *check.C) { 313 s.testSysInfoSystemMode(c, "install") 314 } 315 func (s *generalSuite) TestSysInfoIsManaged(c *check.C) { 316 d := s.daemon(c) 317 318 st := d.Overlord().State() 319 st.Lock() 320 _, err := auth.NewUser(st, "someuser", "mymail@test.com", "macaroon", []string{"discharge"}) 321 st.Unlock() 322 c.Assert(err, check.IsNil) 323 324 req, err := http.NewRequest("GET", "/v2/system-info", nil) 325 c.Assert(err, check.IsNil) 326 327 rsp := s.syncReq(c, req, nil) 328 c.Check(rsp.Result.(map[string]interface{})["managed"], check.Equals, true) 329 } 330 331 func (s *generalSuite) TestSysInfoWorksDegraded(c *check.C) { 332 d := s.daemon(c) 333 334 d.SetDegradedMode(fmt.Errorf("some error")) 335 336 req, err := http.NewRequest("GET", "/v2/system-info", nil) 337 c.Assert(err, check.IsNil) 338 339 rsp := s.syncReq(c, req, nil) 340 c.Check(rsp.Status, check.Equals, 200) 341 } 342 343 func setupChanges(st *state.State) []string { 344 chg1 := st.NewChange("install", "install...") 345 chg1.Set("snap-names", []string{"funky-snap-name"}) 346 t1 := st.NewTask("download", "1...") 347 t2 := st.NewTask("activate", "2...") 348 chg1.AddAll(state.NewTaskSet(t1, t2)) 349 t1.Logf("l11") 350 t1.Logf("l12") 351 chg2 := st.NewChange("remove", "remove..") 352 t3 := st.NewTask("unlink", "1...") 353 chg2.AddTask(t3) 354 t3.SetStatus(state.ErrorStatus) 355 t3.Errorf("rm failed") 356 357 return []string{chg1.ID(), chg2.ID(), t1.ID(), t2.ID(), t3.ID()} 358 } 359 360 func (s *generalSuite) TestStateChangesDefaultToInProgress(c *check.C) { 361 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 362 defer restore() 363 364 // Setup 365 d := s.daemon(c) 366 st := d.Overlord().State() 367 st.Lock() 368 setupChanges(st) 369 st.Unlock() 370 371 // Execute 372 req, err := http.NewRequest("GET", "/v2/changes", nil) 373 c.Assert(err, check.IsNil) 374 rsp := s.syncReq(c, req, nil) 375 376 // Verify 377 c.Check(rsp.Status, check.Equals, 200) 378 c.Assert(rsp.Result, check.HasLen, 1) 379 380 res, err := rsp.MarshalJSON() 381 c.Assert(err, check.IsNil) 382 383 c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*`) 384 } 385 386 func (s *generalSuite) TestStateChangesInProgress(c *check.C) { 387 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 388 defer restore() 389 390 // Setup 391 d := s.daemon(c) 392 st := d.Overlord().State() 393 st.Lock() 394 setupChanges(st) 395 st.Unlock() 396 397 // Execute 398 req, err := http.NewRequest("GET", "/v2/changes?select=in-progress", nil) 399 c.Assert(err, check.IsNil) 400 rsp := s.syncReq(c, req, nil) 401 402 // Verify 403 c.Check(rsp.Status, check.Equals, 200) 404 c.Assert(rsp.Result, check.HasLen, 1) 405 406 res, err := rsp.MarshalJSON() 407 c.Assert(err, check.IsNil) 408 409 c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*],"ready":false,"spawn-time":"2016-04-21T01:02:03Z"}.*`) 410 } 411 412 func (s *generalSuite) TestStateChangesAll(c *check.C) { 413 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 414 defer restore() 415 416 // Setup 417 d := s.daemon(c) 418 st := d.Overlord().State() 419 st.Lock() 420 setupChanges(st) 421 st.Unlock() 422 423 // Execute 424 req, err := http.NewRequest("GET", "/v2/changes?select=all", nil) 425 c.Assert(err, check.IsNil) 426 rsp := s.syncReq(c, req, nil) 427 428 // Verify 429 c.Check(rsp.Status, check.Equals, 200) 430 c.Assert(rsp.Result, check.HasLen, 2) 431 432 res, err := rsp.MarshalJSON() 433 c.Assert(err, check.IsNil) 434 435 c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*],"ready":false,"spawn-time":"2016-04-21T01:02:03Z"}.*`) 436 c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"remove","summary":"remove..","status":"Error","tasks":\[{"id":"\w+","kind":"unlink","summary":"1...","status":"Error","log":\["2016-04-21T01:02:03Z ERROR rm failed"],"progress":{"label":"","done":1,"total":1},"spawn-time":"2016-04-21T01:02:03Z","ready-time":"2016-04-21T01:02:03Z"}.*],"ready":true,"err":"[^"]+".*`) 437 } 438 439 func (s *generalSuite) TestStateChangesReady(c *check.C) { 440 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 441 defer restore() 442 443 // Setup 444 d := s.daemon(c) 445 st := d.Overlord().State() 446 st.Lock() 447 setupChanges(st) 448 st.Unlock() 449 450 // Execute 451 req, err := http.NewRequest("GET", "/v2/changes?select=ready", nil) 452 c.Assert(err, check.IsNil) 453 rsp := s.syncReq(c, req, nil) 454 455 // Verify 456 c.Check(rsp.Status, check.Equals, 200) 457 c.Assert(rsp.Result, check.HasLen, 1) 458 459 res, err := rsp.MarshalJSON() 460 c.Assert(err, check.IsNil) 461 462 c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"remove","summary":"remove..","status":"Error","tasks":\[{"id":"\w+","kind":"unlink","summary":"1...","status":"Error","log":\["2016-04-21T01:02:03Z ERROR rm failed"],"progress":{"label":"","done":1,"total":1},"spawn-time":"2016-04-21T01:02:03Z","ready-time":"2016-04-21T01:02:03Z"}.*],"ready":true,"err":"[^"]+".*`) 463 } 464 465 func (s *generalSuite) TestStateChangesForSnapName(c *check.C) { 466 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 467 defer restore() 468 469 // Setup 470 d := s.daemon(c) 471 st := d.Overlord().State() 472 st.Lock() 473 setupChanges(st) 474 st.Unlock() 475 476 // Execute 477 req, err := http.NewRequest("GET", "/v2/changes?for=funky-snap-name&select=all", nil) 478 c.Assert(err, check.IsNil) 479 rsp := s.syncReq(c, req, nil) 480 481 // Verify 482 c.Check(rsp.Status, check.Equals, 200) 483 c.Assert(rsp.Result, check.FitsTypeOf, []*daemon.ChangeInfo(nil)) 484 485 res := rsp.Result.([]*daemon.ChangeInfo) 486 c.Assert(res, check.HasLen, 1) 487 c.Check(res[0].Kind, check.Equals, `install`) 488 489 _, err = rsp.MarshalJSON() 490 c.Assert(err, check.IsNil) 491 } 492 493 func (s *generalSuite) TestStateChangesForSnapNameWithApp(c *check.C) { 494 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 495 defer restore() 496 497 // Setup 498 d := s.daemon(c) 499 st := d.Overlord().State() 500 st.Lock() 501 chg1 := st.NewChange("service-control", "install...") 502 // as triggered by snap restart lxd.daemon 503 chg1.Set("snap-names", []string{"lxd.daemon"}) 504 t1 := st.NewTask("exec-command", "1...") 505 chg1.AddAll(state.NewTaskSet(t1)) 506 t1.Logf("foobar") 507 508 st.Unlock() 509 510 // Execute 511 req, err := http.NewRequest("GET", "/v2/changes?for=lxd&select=all", nil) 512 c.Assert(err, check.IsNil) 513 rsp := s.syncReq(c, req, nil) 514 515 // Verify 516 c.Check(rsp.Status, check.Equals, 200) 517 c.Assert(rsp.Result, check.FitsTypeOf, []*daemon.ChangeInfo(nil)) 518 519 res := rsp.Result.([]*daemon.ChangeInfo) 520 c.Assert(res, check.HasLen, 1) 521 c.Check(res[0].Kind, check.Equals, `service-control`) 522 523 _, err = rsp.MarshalJSON() 524 c.Assert(err, check.IsNil) 525 } 526 527 func (s *generalSuite) TestStateChange(c *check.C) { 528 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 529 defer restore() 530 531 // Setup 532 d := s.daemon(c) 533 st := d.Overlord().State() 534 st.Lock() 535 ids := setupChanges(st) 536 chg := st.Change(ids[0]) 537 chg.Set("api-data", map[string]int{"n": 42}) 538 st.Unlock() 539 540 // Execute 541 req, err := http.NewRequest("GET", "/v2/changes/"+ids[0], nil) 542 c.Assert(err, check.IsNil) 543 rsp := s.syncReq(c, req, nil) 544 rec := httptest.NewRecorder() 545 rsp.ServeHTTP(rec, req) 546 547 // Verify 548 c.Check(rec.Code, check.Equals, 200) 549 c.Check(rsp.Status, check.Equals, 200) 550 c.Check(rsp.Result, check.NotNil) 551 552 var body map[string]interface{} 553 err = json.Unmarshal(rec.Body.Bytes(), &body) 554 c.Check(err, check.IsNil) 555 c.Check(body["result"], check.DeepEquals, map[string]interface{}{ 556 "id": ids[0], 557 "kind": "install", 558 "summary": "install...", 559 "status": "Do", 560 "ready": false, 561 "spawn-time": "2016-04-21T01:02:03Z", 562 "tasks": []interface{}{ 563 map[string]interface{}{ 564 "id": ids[2], 565 "kind": "download", 566 "summary": "1...", 567 "status": "Do", 568 "log": []interface{}{"2016-04-21T01:02:03Z INFO l11", "2016-04-21T01:02:03Z INFO l12"}, 569 "progress": map[string]interface{}{"label": "", "done": 0., "total": 1.}, 570 "spawn-time": "2016-04-21T01:02:03Z", 571 }, 572 map[string]interface{}{ 573 "id": ids[3], 574 "kind": "activate", 575 "summary": "2...", 576 "status": "Do", 577 "progress": map[string]interface{}{"label": "", "done": 0., "total": 1.}, 578 "spawn-time": "2016-04-21T01:02:03Z", 579 }, 580 }, 581 "data": map[string]interface{}{ 582 "n": float64(42), 583 }, 584 }) 585 } 586 587 func (s *generalSuite) TestStateChangeAbort(c *check.C) { 588 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 589 defer restore() 590 591 soon := 0 592 _, restore = daemon.MockEnsureStateSoon(func(st *state.State) { 593 soon++ 594 }) 595 defer restore() 596 597 // Setup 598 d := s.daemon(c) 599 st := d.Overlord().State() 600 st.Lock() 601 ids := setupChanges(st) 602 st.Unlock() 603 604 buf := bytes.NewBufferString(`{"action": "abort"}`) 605 606 // Execute 607 req, err := http.NewRequest("POST", "/v2/changes/"+ids[0], buf) 608 c.Assert(err, check.IsNil) 609 rsp := s.syncReq(c, req, nil) 610 rec := httptest.NewRecorder() 611 rsp.ServeHTTP(rec, req) 612 613 // Ensure scheduled 614 c.Check(soon, check.Equals, 1) 615 616 // Verify 617 c.Check(rec.Code, check.Equals, 200) 618 c.Check(rsp.Status, check.Equals, 200) 619 c.Check(rsp.Result, check.NotNil) 620 621 var body map[string]interface{} 622 err = json.Unmarshal(rec.Body.Bytes(), &body) 623 c.Check(err, check.IsNil) 624 c.Check(body["result"], check.DeepEquals, map[string]interface{}{ 625 "id": ids[0], 626 "kind": "install", 627 "summary": "install...", 628 "status": "Hold", 629 "ready": true, 630 "spawn-time": "2016-04-21T01:02:03Z", 631 "ready-time": "2016-04-21T01:02:03Z", 632 "tasks": []interface{}{ 633 map[string]interface{}{ 634 "id": ids[2], 635 "kind": "download", 636 "summary": "1...", 637 "status": "Hold", 638 "log": []interface{}{"2016-04-21T01:02:03Z INFO l11", "2016-04-21T01:02:03Z INFO l12"}, 639 "progress": map[string]interface{}{"label": "", "done": 1., "total": 1.}, 640 "spawn-time": "2016-04-21T01:02:03Z", 641 "ready-time": "2016-04-21T01:02:03Z", 642 }, 643 map[string]interface{}{ 644 "id": ids[3], 645 "kind": "activate", 646 "summary": "2...", 647 "status": "Hold", 648 "progress": map[string]interface{}{"label": "", "done": 1., "total": 1.}, 649 "spawn-time": "2016-04-21T01:02:03Z", 650 "ready-time": "2016-04-21T01:02:03Z", 651 }, 652 }, 653 }) 654 } 655 656 func (s *generalSuite) TestStateChangeAbortIsReady(c *check.C) { 657 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 658 defer restore() 659 660 // Setup 661 d := s.daemon(c) 662 st := d.Overlord().State() 663 st.Lock() 664 ids := setupChanges(st) 665 st.Change(ids[0]).SetStatus(state.DoneStatus) 666 st.Unlock() 667 668 buf := bytes.NewBufferString(`{"action": "abort"}`) 669 670 // Execute 671 req, err := http.NewRequest("POST", "/v2/changes/"+ids[0], buf) 672 c.Assert(err, check.IsNil) 673 rsp := s.errorReq(c, req, nil) 674 rec := httptest.NewRecorder() 675 rsp.ServeHTTP(rec, req) 676 677 // Verify 678 c.Check(rec.Code, check.Equals, 400) 679 c.Check(rsp.Status, check.Equals, 400) 680 c.Check(rsp.Result, check.NotNil) 681 682 var body map[string]interface{} 683 err = json.Unmarshal(rec.Body.Bytes(), &body) 684 c.Check(err, check.IsNil) 685 c.Check(body["result"], check.DeepEquals, map[string]interface{}{ 686 "message": fmt.Sprintf("cannot abort change %s with nothing pending", ids[0]), 687 }) 688 } 689 690 func (s *generalSuite) testWarnings(c *check.C, all bool, body io.Reader) (calls string, result interface{}) { 691 s.daemon(c) 692 693 okayWarns := func(*state.State, time.Time) int { calls += "ok"; return 0 } 694 allWarns := func(*state.State) []*state.Warning { calls += "all"; return nil } 695 pendingWarns := func(*state.State) ([]*state.Warning, time.Time) { calls += "show"; return nil, time.Time{} } 696 restore := daemon.MockWarningsAccessors(okayWarns, allWarns, pendingWarns) 697 defer restore() 698 699 method := "GET" 700 if body != nil { 701 method = "POST" 702 } 703 q := url.Values{} 704 if all { 705 q.Set("select", "all") 706 } 707 req, err := http.NewRequest(method, "/v2/warnings?"+q.Encode(), body) 708 c.Assert(err, check.IsNil) 709 710 rsp := s.syncReq(c, req, nil) 711 712 c.Check(rsp.Status, check.Equals, 200) 713 c.Assert(rsp.Result, check.NotNil) 714 return calls, rsp.Result 715 } 716 717 func (s *generalSuite) TestAllWarnings(c *check.C) { 718 calls, result := s.testWarnings(c, true, nil) 719 c.Check(calls, check.Equals, "all") 720 c.Check(result, check.DeepEquals, []state.Warning{}) 721 } 722 723 func (s *generalSuite) TestSomeWarnings(c *check.C) { 724 calls, result := s.testWarnings(c, false, nil) 725 c.Check(calls, check.Equals, "show") 726 c.Check(result, check.DeepEquals, []state.Warning{}) 727 } 728 729 func (s *generalSuite) TestAckWarnings(c *check.C) { 730 calls, result := s.testWarnings(c, false, bytes.NewReader([]byte(`{"action": "okay", "timestamp": "2006-01-02T15:04:05Z"}`))) 731 c.Check(calls, check.Equals, "ok") 732 c.Check(result, check.DeepEquals, 0) 733 }