github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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.RespJSON 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.RespJSON 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.RespJSON 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.RespJSON 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 rec := httptest.NewRecorder() 381 rsp.ServeHTTP(rec, nil) 382 c.Assert(rec.Code, check.Equals, 200) 383 res := rec.Body.Bytes() 384 385 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"}.*`) 386 } 387 388 func (s *generalSuite) TestStateChangesInProgress(c *check.C) { 389 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 390 defer restore() 391 392 // Setup 393 d := s.daemon(c) 394 st := d.Overlord().State() 395 st.Lock() 396 setupChanges(st) 397 st.Unlock() 398 399 // Execute 400 req, err := http.NewRequest("GET", "/v2/changes?select=in-progress", nil) 401 c.Assert(err, check.IsNil) 402 rsp := s.syncReq(c, req, nil) 403 404 // Verify 405 c.Check(rsp.Status, check.Equals, 200) 406 c.Assert(rsp.Result, check.HasLen, 1) 407 408 rec := httptest.NewRecorder() 409 rsp.ServeHTTP(rec, nil) 410 c.Assert(rec.Code, check.Equals, 200) 411 res := rec.Body.Bytes() 412 413 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"}.*`) 414 } 415 416 func (s *generalSuite) TestStateChangesAll(c *check.C) { 417 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 418 defer restore() 419 420 // Setup 421 d := s.daemon(c) 422 st := d.Overlord().State() 423 st.Lock() 424 setupChanges(st) 425 st.Unlock() 426 427 // Execute 428 req, err := http.NewRequest("GET", "/v2/changes?select=all", nil) 429 c.Assert(err, check.IsNil) 430 rsp := s.syncReq(c, req, nil) 431 432 // Verify 433 c.Check(rsp.Status, check.Equals, 200) 434 c.Assert(rsp.Result, check.HasLen, 2) 435 436 rec := httptest.NewRecorder() 437 rsp.ServeHTTP(rec, nil) 438 c.Assert(rec.Code, check.Equals, 200) 439 res := rec.Body.Bytes() 440 441 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"}.*`) 442 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":"[^"]+".*`) 443 } 444 445 func (s *generalSuite) TestStateChangesReady(c *check.C) { 446 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 447 defer restore() 448 449 // Setup 450 d := s.daemon(c) 451 st := d.Overlord().State() 452 st.Lock() 453 setupChanges(st) 454 st.Unlock() 455 456 // Execute 457 req, err := http.NewRequest("GET", "/v2/changes?select=ready", nil) 458 c.Assert(err, check.IsNil) 459 rsp := s.syncReq(c, req, nil) 460 461 // Verify 462 c.Check(rsp.Status, check.Equals, 200) 463 c.Assert(rsp.Result, check.HasLen, 1) 464 465 rec := httptest.NewRecorder() 466 rsp.ServeHTTP(rec, nil) 467 c.Assert(rec.Code, check.Equals, 200) 468 res := rec.Body.Bytes() 469 470 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":"[^"]+".*`) 471 } 472 473 func (s *generalSuite) TestStateChangesForSnapName(c *check.C) { 474 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 475 defer restore() 476 477 // Setup 478 d := s.daemon(c) 479 st := d.Overlord().State() 480 st.Lock() 481 setupChanges(st) 482 st.Unlock() 483 484 // Execute 485 req, err := http.NewRequest("GET", "/v2/changes?for=funky-snap-name&select=all", nil) 486 c.Assert(err, check.IsNil) 487 rsp := s.syncReq(c, req, nil) 488 489 // Verify 490 c.Check(rsp.Status, check.Equals, 200) 491 c.Assert(rsp.Result, check.FitsTypeOf, []*daemon.ChangeInfo(nil)) 492 493 res := rsp.Result.([]*daemon.ChangeInfo) 494 c.Assert(res, check.HasLen, 1) 495 c.Check(res[0].Kind, check.Equals, `install`) 496 497 rec := httptest.NewRecorder() 498 rsp.ServeHTTP(rec, nil) 499 c.Assert(rec.Code, check.Equals, 200) 500 } 501 502 func (s *generalSuite) TestStateChangesForSnapNameWithApp(c *check.C) { 503 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 504 defer restore() 505 506 // Setup 507 d := s.daemon(c) 508 st := d.Overlord().State() 509 st.Lock() 510 chg1 := st.NewChange("service-control", "install...") 511 // as triggered by snap restart lxd.daemon 512 chg1.Set("snap-names", []string{"lxd.daemon"}) 513 t1 := st.NewTask("exec-command", "1...") 514 chg1.AddAll(state.NewTaskSet(t1)) 515 t1.Logf("foobar") 516 517 st.Unlock() 518 519 // Execute 520 req, err := http.NewRequest("GET", "/v2/changes?for=lxd&select=all", nil) 521 c.Assert(err, check.IsNil) 522 rsp := s.syncReq(c, req, nil) 523 524 // Verify 525 c.Check(rsp.Status, check.Equals, 200) 526 c.Assert(rsp.Result, check.FitsTypeOf, []*daemon.ChangeInfo(nil)) 527 528 res := rsp.Result.([]*daemon.ChangeInfo) 529 c.Assert(res, check.HasLen, 1) 530 c.Check(res[0].Kind, check.Equals, `service-control`) 531 532 rec := httptest.NewRecorder() 533 rsp.ServeHTTP(rec, nil) 534 c.Assert(rec.Code, check.Equals, 200) 535 } 536 537 func (s *generalSuite) TestStateChange(c *check.C) { 538 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 539 defer restore() 540 541 // Setup 542 d := s.daemon(c) 543 st := d.Overlord().State() 544 st.Lock() 545 ids := setupChanges(st) 546 chg := st.Change(ids[0]) 547 chg.Set("api-data", map[string]int{"n": 42}) 548 st.Unlock() 549 550 // Execute 551 req, err := http.NewRequest("GET", "/v2/changes/"+ids[0], nil) 552 c.Assert(err, check.IsNil) 553 rsp := s.syncReq(c, req, nil) 554 rec := httptest.NewRecorder() 555 rsp.ServeHTTP(rec, req) 556 557 // Verify 558 c.Check(rec.Code, check.Equals, 200) 559 c.Check(rsp.Status, check.Equals, 200) 560 c.Check(rsp.Result, check.NotNil) 561 562 var body map[string]interface{} 563 err = json.Unmarshal(rec.Body.Bytes(), &body) 564 c.Check(err, check.IsNil) 565 c.Check(body["result"], check.DeepEquals, map[string]interface{}{ 566 "id": ids[0], 567 "kind": "install", 568 "summary": "install...", 569 "status": "Do", 570 "ready": false, 571 "spawn-time": "2016-04-21T01:02:03Z", 572 "tasks": []interface{}{ 573 map[string]interface{}{ 574 "id": ids[2], 575 "kind": "download", 576 "summary": "1...", 577 "status": "Do", 578 "log": []interface{}{"2016-04-21T01:02:03Z INFO l11", "2016-04-21T01:02:03Z INFO l12"}, 579 "progress": map[string]interface{}{"label": "", "done": 0., "total": 1.}, 580 "spawn-time": "2016-04-21T01:02:03Z", 581 }, 582 map[string]interface{}{ 583 "id": ids[3], 584 "kind": "activate", 585 "summary": "2...", 586 "status": "Do", 587 "progress": map[string]interface{}{"label": "", "done": 0., "total": 1.}, 588 "spawn-time": "2016-04-21T01:02:03Z", 589 }, 590 }, 591 "data": map[string]interface{}{ 592 "n": float64(42), 593 }, 594 }) 595 } 596 597 func (s *generalSuite) expectManageAccess() { 598 s.expectWriteAccess(daemon.AuthenticatedAccess{Polkit: "io.snapcraft.snapd.manage"}) 599 } 600 601 func (s *generalSuite) TestStateChangeAbort(c *check.C) { 602 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 603 defer restore() 604 605 soon := 0 606 _, restore = daemon.MockEnsureStateSoon(func(st *state.State) { 607 soon++ 608 }) 609 defer restore() 610 611 // Setup 612 d := s.daemon(c) 613 st := d.Overlord().State() 614 st.Lock() 615 ids := setupChanges(st) 616 st.Unlock() 617 618 s.expectManageAccess() 619 620 buf := bytes.NewBufferString(`{"action": "abort"}`) 621 622 // Execute 623 req, err := http.NewRequest("POST", "/v2/changes/"+ids[0], buf) 624 c.Assert(err, check.IsNil) 625 rsp := s.syncReq(c, req, nil) 626 rec := httptest.NewRecorder() 627 rsp.ServeHTTP(rec, req) 628 629 // Ensure scheduled 630 c.Check(soon, check.Equals, 1) 631 632 // Verify 633 c.Check(rec.Code, check.Equals, 200) 634 c.Check(rsp.Status, check.Equals, 200) 635 c.Check(rsp.Result, check.NotNil) 636 637 var body map[string]interface{} 638 err = json.Unmarshal(rec.Body.Bytes(), &body) 639 c.Check(err, check.IsNil) 640 c.Check(body["result"], check.DeepEquals, map[string]interface{}{ 641 "id": ids[0], 642 "kind": "install", 643 "summary": "install...", 644 "status": "Hold", 645 "ready": true, 646 "spawn-time": "2016-04-21T01:02:03Z", 647 "ready-time": "2016-04-21T01:02:03Z", 648 "tasks": []interface{}{ 649 map[string]interface{}{ 650 "id": ids[2], 651 "kind": "download", 652 "summary": "1...", 653 "status": "Hold", 654 "log": []interface{}{"2016-04-21T01:02:03Z INFO l11", "2016-04-21T01:02:03Z INFO l12"}, 655 "progress": map[string]interface{}{"label": "", "done": 1., "total": 1.}, 656 "spawn-time": "2016-04-21T01:02:03Z", 657 "ready-time": "2016-04-21T01:02:03Z", 658 }, 659 map[string]interface{}{ 660 "id": ids[3], 661 "kind": "activate", 662 "summary": "2...", 663 "status": "Hold", 664 "progress": map[string]interface{}{"label": "", "done": 1., "total": 1.}, 665 "spawn-time": "2016-04-21T01:02:03Z", 666 "ready-time": "2016-04-21T01:02:03Z", 667 }, 668 }, 669 }) 670 } 671 672 func (s *generalSuite) TestStateChangeAbortIsReady(c *check.C) { 673 restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) 674 defer restore() 675 676 // Setup 677 d := s.daemon(c) 678 st := d.Overlord().State() 679 st.Lock() 680 ids := setupChanges(st) 681 st.Change(ids[0]).SetStatus(state.DoneStatus) 682 st.Unlock() 683 684 s.expectManageAccess() 685 686 buf := bytes.NewBufferString(`{"action": "abort"}`) 687 688 // Execute 689 req, err := http.NewRequest("POST", "/v2/changes/"+ids[0], buf) 690 c.Assert(err, check.IsNil) 691 rspe := s.errorReq(c, req, nil) 692 rec := httptest.NewRecorder() 693 rspe.ServeHTTP(rec, req) 694 695 // Verify 696 c.Check(rec.Code, check.Equals, 400) 697 c.Check(rspe.Status, check.Equals, 400) 698 699 var body map[string]interface{} 700 err = json.Unmarshal(rec.Body.Bytes(), &body) 701 c.Check(err, check.IsNil) 702 c.Check(body["result"], check.DeepEquals, map[string]interface{}{ 703 "message": fmt.Sprintf("cannot abort change %s with nothing pending", ids[0]), 704 }) 705 } 706 707 func (s *generalSuite) testWarnings(c *check.C, all bool, body io.Reader) (calls string, result interface{}) { 708 s.daemon(c) 709 710 s.expectManageAccess() 711 712 okayWarns := func(*state.State, time.Time) int { calls += "ok"; return 0 } 713 allWarns := func(*state.State) []*state.Warning { calls += "all"; return nil } 714 pendingWarns := func(*state.State) ([]*state.Warning, time.Time) { calls += "show"; return nil, time.Time{} } 715 restore := daemon.MockWarningsAccessors(okayWarns, allWarns, pendingWarns) 716 defer restore() 717 718 method := "GET" 719 if body != nil { 720 method = "POST" 721 } 722 q := url.Values{} 723 if all { 724 q.Set("select", "all") 725 } 726 req, err := http.NewRequest(method, "/v2/warnings?"+q.Encode(), body) 727 c.Assert(err, check.IsNil) 728 729 rsp := s.syncReq(c, req, nil) 730 731 c.Check(rsp.Status, check.Equals, 200) 732 c.Assert(rsp.Result, check.NotNil) 733 return calls, rsp.Result 734 } 735 736 func (s *generalSuite) TestAllWarnings(c *check.C) { 737 calls, result := s.testWarnings(c, true, nil) 738 c.Check(calls, check.Equals, "all") 739 c.Check(result, check.DeepEquals, []state.Warning{}) 740 } 741 742 func (s *generalSuite) TestSomeWarnings(c *check.C) { 743 calls, result := s.testWarnings(c, false, nil) 744 c.Check(calls, check.Equals, "show") 745 c.Check(result, check.DeepEquals, []state.Warning{}) 746 } 747 748 func (s *generalSuite) TestAckWarnings(c *check.C) { 749 calls, result := s.testWarnings(c, false, bytes.NewReader([]byte(`{"action": "okay", "timestamp": "2006-01-02T15:04:05Z"}`))) 750 c.Check(calls, check.Equals, "ok") 751 c.Check(result, check.DeepEquals, 0) 752 }