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