github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/daemon/api_apps_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 "errors" 26 "fmt" 27 "io" 28 "io/ioutil" 29 "math" 30 "net/http" 31 "net/http/httptest" 32 "sort" 33 "strconv" 34 "strings" 35 36 "gopkg.in/check.v1" 37 38 "github.com/snapcore/snapd/client" 39 "github.com/snapcore/snapd/daemon" 40 "github.com/snapcore/snapd/overlord/hookstate" 41 "github.com/snapcore/snapd/overlord/servicestate" 42 "github.com/snapcore/snapd/overlord/snapstate" 43 "github.com/snapcore/snapd/overlord/state" 44 "github.com/snapcore/snapd/snap" 45 "github.com/snapcore/snapd/systemd" 46 "github.com/snapcore/snapd/testutil" 47 ) 48 49 var _ = check.Suite(&appsSuite{}) 50 51 type appsSuite struct { 52 apiBaseSuite 53 54 journalctlRestorer func() 55 jctlSvcses [][]string 56 jctlNs []int 57 jctlFollows []bool 58 jctlRCs []io.ReadCloser 59 jctlErrs []error 60 61 serviceControlError error 62 serviceControlCalls []serviceControlArgs 63 64 infoA, infoB, infoC, infoD, infoE *snap.Info 65 } 66 67 func (s *appsSuite) journalctl(svcs []string, n int, follow bool) (rc io.ReadCloser, err error) { 68 s.jctlSvcses = append(s.jctlSvcses, svcs) 69 s.jctlNs = append(s.jctlNs, n) 70 s.jctlFollows = append(s.jctlFollows, follow) 71 72 if len(s.jctlErrs) > 0 { 73 err, s.jctlErrs = s.jctlErrs[0], s.jctlErrs[1:] 74 } 75 if len(s.jctlRCs) > 0 { 76 rc, s.jctlRCs = s.jctlRCs[0], s.jctlRCs[1:] 77 } 78 79 return rc, err 80 } 81 82 type serviceControlArgs struct { 83 action string 84 options string 85 names []string 86 } 87 88 func (s *appsSuite) fakeServiceControl(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction, flags *servicestate.Flags, context *hookstate.Context) ([]*state.TaskSet, error) { 89 if flags != nil { 90 panic("flags are not expected") 91 } 92 93 if s.serviceControlError != nil { 94 return nil, s.serviceControlError 95 } 96 97 serviceCommand := serviceControlArgs{action: inst.Action} 98 if inst.RestartOptions.Reload { 99 serviceCommand.options = "reload" 100 } 101 // only one flag should ever be set (depending on Action), but appending 102 // them below acts as an extra sanity check. 103 if inst.StartOptions.Enable { 104 serviceCommand.options += "enable" 105 } 106 if inst.StopOptions.Disable { 107 serviceCommand.options += "disable" 108 } 109 for _, app := range appInfos { 110 serviceCommand.names = append(serviceCommand.names, fmt.Sprintf("%s.%s", app.Snap.InstanceName(), app.Name)) 111 } 112 s.serviceControlCalls = append(s.serviceControlCalls, serviceCommand) 113 114 t := st.NewTask("dummy", "") 115 ts := state.NewTaskSet(t) 116 return []*state.TaskSet{ts}, nil 117 } 118 119 func (s *appsSuite) SetUpSuite(c *check.C) { 120 s.apiBaseSuite.SetUpSuite(c) 121 s.journalctlRestorer = systemd.MockJournalctl(s.journalctl) 122 } 123 124 func (s *appsSuite) TearDownSuite(c *check.C) { 125 s.journalctlRestorer() 126 s.apiBaseSuite.TearDownSuite(c) 127 } 128 129 func (s *appsSuite) SetUpTest(c *check.C) { 130 s.apiBaseSuite.SetUpTest(c) 131 132 s.jctlSvcses = nil 133 s.jctlNs = nil 134 s.jctlFollows = nil 135 s.jctlRCs = nil 136 s.jctlErrs = nil 137 138 d := s.daemon(c) 139 140 s.serviceControlCalls = nil 141 s.serviceControlError = nil 142 restoreServicestateCtrl := daemon.MockServicestateControl(s.fakeServiceControl) 143 s.AddCleanup(restoreServicestateCtrl) 144 145 s.infoA = s.mkInstalledInState(c, s.d, "snap-a", "dev", "v1", snap.R(1), true, "apps: {svc1: {daemon: simple}, svc2: {daemon: simple, reload-command: x}}") 146 s.infoB = s.mkInstalledInState(c, s.d, "snap-b", "dev", "v1", snap.R(1), false, "apps: {svc3: {daemon: simple}, cmd1: {}}") 147 s.infoC = s.mkInstalledInState(c, s.d, "snap-c", "dev", "v1", snap.R(1), true, "") 148 s.infoD = s.mkInstalledInState(c, s.d, "snap-d", "dev", "v1", snap.R(1), true, "apps: {cmd2: {}, cmd3: {}}") 149 s.infoE = s.mkInstalledInState(c, s.d, "snap-e", "dev", "v1", snap.R(1), true, "apps: {svc4: {daemon: simple, daemon-scope: user}}") 150 151 d.Overlord().Loop() 152 s.AddCleanup(func() { d.Overlord().Stop() }) 153 } 154 155 func (s *appsSuite) TestSplitAppName(c *check.C) { 156 type T struct { 157 name string 158 snap string 159 app string 160 } 161 162 for _, x := range []T{ 163 {name: "foo.bar", snap: "foo", app: "bar"}, 164 {name: "foo", snap: "foo", app: ""}, 165 {name: "foo.bar.baz", snap: "foo", app: "bar.baz"}, 166 {name: ".", snap: "", app: ""}, // SISO 167 } { 168 snap, app := daemon.SplitAppName(x.name) 169 c.Check(x.snap, check.Equals, snap, check.Commentf(x.name)) 170 c.Check(x.app, check.Equals, app, check.Commentf(x.name)) 171 } 172 } 173 174 func (s *appsSuite) TestGetAppsInfo(c *check.C) { 175 // System services from active snaps 176 svcNames := []string{"snap-a.svc1", "snap-a.svc2"} 177 for _, name := range svcNames { 178 s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(` 179 Id=snap.%s.service 180 Type=simple 181 ActiveState=active 182 UnitFileState=enabled 183 `[1:], name))) 184 } 185 // System services from inactive snaps 186 svcNames = append(svcNames, "snap-b.svc3") 187 // User services from active snaps 188 svcNames = append(svcNames, "snap-e.svc4") 189 s.SysctlBufs = append(s.SysctlBufs, []byte("enabled\n")) 190 191 req, err := http.NewRequest("GET", "/v2/apps", nil) 192 c.Assert(err, check.IsNil) 193 194 rsp := s.syncReq(c, req, nil) 195 c.Assert(rsp.Status, check.Equals, 200) 196 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 197 apps := rsp.Result.([]client.AppInfo) 198 c.Assert(apps, check.HasLen, 7) 199 200 for _, name := range svcNames { 201 snapName, app := daemon.SplitAppName(name) 202 needle := client.AppInfo{ 203 Snap: snapName, 204 Name: app, 205 Daemon: "simple", 206 DaemonScope: snap.SystemDaemon, 207 } 208 if snapName != "snap-b" { 209 // snap-b is not active (all the others are) 210 needle.Active = true 211 needle.Enabled = true 212 } 213 if snapName == "snap-e" { 214 // snap-e contains user services 215 needle.DaemonScope = snap.UserDaemon 216 needle.Active = false 217 } 218 c.Check(apps, testutil.DeepContains, needle) 219 } 220 221 for _, name := range []string{"snap-b.cmd1", "snap-d.cmd2", "snap-d.cmd3"} { 222 snap, app := daemon.SplitAppName(name) 223 c.Check(apps, testutil.DeepContains, client.AppInfo{ 224 Snap: snap, 225 Name: app, 226 }) 227 } 228 229 appNames := make([]string, len(apps)) 230 for i, app := range apps { 231 appNames[i] = app.Snap + "." + app.Name 232 } 233 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 234 } 235 236 func (s *appsSuite) TestGetAppsInfoNames(c *check.C) { 237 238 req, err := http.NewRequest("GET", "/v2/apps?names=snap-d", nil) 239 c.Assert(err, check.IsNil) 240 241 rsp := s.syncReq(c, req, nil) 242 c.Assert(rsp.Status, check.Equals, 200) 243 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 244 apps := rsp.Result.([]client.AppInfo) 245 c.Assert(apps, check.HasLen, 2) 246 247 for _, name := range []string{"snap-d.cmd2", "snap-d.cmd3"} { 248 snap, app := daemon.SplitAppName(name) 249 c.Check(apps, testutil.DeepContains, client.AppInfo{ 250 Snap: snap, 251 Name: app, 252 }) 253 } 254 255 appNames := make([]string, len(apps)) 256 for i, app := range apps { 257 appNames[i] = app.Snap + "." + app.Name 258 } 259 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 260 } 261 262 func (s *appsSuite) TestGetAppsInfoServices(c *check.C) { 263 // System services from active snaps 264 svcNames := []string{"snap-a.svc1", "snap-a.svc2"} 265 for _, name := range svcNames { 266 s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(` 267 Id=snap.%s.service 268 Type=simple 269 ActiveState=active 270 UnitFileState=enabled 271 `[1:], name))) 272 } 273 // System services from inactive snaps 274 svcNames = append(svcNames, "snap-b.svc3") 275 // User services from active snaps 276 svcNames = append(svcNames, "snap-e.svc4") 277 s.SysctlBufs = append(s.SysctlBufs, []byte("enabled\n")) 278 279 req, err := http.NewRequest("GET", "/v2/apps?select=service", nil) 280 c.Assert(err, check.IsNil) 281 282 rsp := s.syncReq(c, req, nil) 283 c.Assert(rsp.Status, check.Equals, 200) 284 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 285 svcs := rsp.Result.([]client.AppInfo) 286 c.Assert(svcs, check.HasLen, 4) 287 288 for _, name := range svcNames { 289 snapName, app := daemon.SplitAppName(name) 290 needle := client.AppInfo{ 291 Snap: snapName, 292 Name: app, 293 Daemon: "simple", 294 DaemonScope: snap.SystemDaemon, 295 } 296 if snapName != "snap-b" { 297 // snap-b is not active (all the others are) 298 needle.Active = true 299 needle.Enabled = true 300 } 301 if snapName == "snap-e" { 302 // snap-e contains user services 303 needle.DaemonScope = snap.UserDaemon 304 needle.Active = false 305 } 306 c.Check(svcs, testutil.DeepContains, needle) 307 } 308 309 appNames := make([]string, len(svcs)) 310 for i, svc := range svcs { 311 appNames[i] = svc.Snap + "." + svc.Name 312 } 313 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 314 } 315 316 func (s *appsSuite) TestGetAppsInfoBadSelect(c *check.C) { 317 req, err := http.NewRequest("GET", "/v2/apps?select=potato", nil) 318 c.Assert(err, check.IsNil) 319 320 rsp := s.errorReq(c, req, nil) 321 c.Assert(rsp.Status, check.Equals, 400) 322 } 323 324 func (s *appsSuite) TestGetAppsInfoBadName(c *check.C) { 325 req, err := http.NewRequest("GET", "/v2/apps?names=potato", nil) 326 c.Assert(err, check.IsNil) 327 328 rsp := s.errorReq(c, req, nil) 329 c.Assert(rsp.Status, check.Equals, 404) 330 } 331 332 func (s *appsSuite) TestAppInfosForOne(c *check.C) { 333 st := s.d.Overlord().State() 334 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a.svc1"}, daemon.AppInfoServiceTrue) 335 c.Assert(rsp, check.IsNil) 336 c.Assert(appInfos, check.HasLen, 1) 337 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 338 c.Check(appInfos[0].Name, check.Equals, "svc1") 339 } 340 341 func (s *appsSuite) TestAppInfosForAll(c *check.C) { 342 type T struct { 343 opts daemon.AppInfoOptions 344 snaps []*snap.Info 345 names []string 346 } 347 348 for _, t := range []T{ 349 { 350 opts: daemon.AppInfoServiceTrue, 351 names: []string{"svc1", "svc2", "svc3", "svc4"}, 352 snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoE}, 353 }, 354 { 355 opts: daemon.AppInfoServiceFalse, 356 names: []string{"svc1", "svc2", "cmd1", "svc3", "cmd2", "cmd3", "svc4"}, 357 snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoB, s.infoD, s.infoD, s.infoE}, 358 }, 359 } { 360 c.Assert(len(t.names), check.Equals, len(t.snaps), check.Commentf("%s", t.opts)) 361 362 st := s.d.Overlord().State() 363 appInfos, rsp := daemon.AppInfosFor(st, nil, t.opts) 364 c.Assert(rsp, check.IsNil, check.Commentf("%s", t.opts)) 365 names := make([]string, len(appInfos)) 366 for i, appInfo := range appInfos { 367 names[i] = appInfo.Name 368 } 369 c.Assert(names, check.DeepEquals, t.names, check.Commentf("%s", t.opts)) 370 371 for i := range appInfos { 372 c.Check(appInfos[i].Snap, check.DeepEquals, t.snaps[i], check.Commentf("%s: %s", t.opts, t.names[i])) 373 } 374 } 375 } 376 377 func (s *appsSuite) TestAppInfosForOneSnap(c *check.C) { 378 st := s.d.Overlord().State() 379 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a"}, daemon.AppInfoServiceTrue) 380 c.Assert(rsp, check.IsNil) 381 c.Assert(appInfos, check.HasLen, 2) 382 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 383 384 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 385 c.Check(appInfos[0].Name, check.Equals, "svc1") 386 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 387 c.Check(appInfos[1].Name, check.Equals, "svc2") 388 } 389 390 func (s *appsSuite) TestAppInfosForMixedArgs(c *check.C) { 391 st := s.d.Overlord().State() 392 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a", "snap-a.svc1"}, daemon.AppInfoServiceTrue) 393 c.Assert(rsp, check.IsNil) 394 c.Assert(appInfos, check.HasLen, 2) 395 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 396 397 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 398 c.Check(appInfos[0].Name, check.Equals, "svc1") 399 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 400 c.Check(appInfos[1].Name, check.Equals, "svc2") 401 } 402 403 func (s *appsSuite) TestAppInfosCleanupAndSorted(c *check.C) { 404 st := s.d.Overlord().State() 405 appInfos, rsp := daemon.AppInfosFor(st, []string{ 406 "snap-b.svc3", 407 "snap-a.svc2", 408 "snap-a.svc1", 409 "snap-a.svc2", 410 "snap-b.svc3", 411 "snap-a.svc1", 412 "snap-b", 413 "snap-a", 414 }, daemon.AppInfoServiceTrue) 415 c.Assert(rsp, check.IsNil) 416 c.Assert(appInfos, check.HasLen, 3) 417 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 418 419 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 420 c.Check(appInfos[0].Name, check.Equals, "svc1") 421 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 422 c.Check(appInfos[1].Name, check.Equals, "svc2") 423 c.Check(appInfos[2].Snap, check.DeepEquals, s.infoB) 424 c.Check(appInfos[2].Name, check.Equals, "svc3") 425 } 426 427 func (s *appsSuite) TestAppInfosForAppless(c *check.C) { 428 st := s.d.Overlord().State() 429 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-c"}, daemon.AppInfoServiceTrue) 430 c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{}) 431 c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404) 432 c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindAppNotFound) 433 c.Assert(appInfos, check.IsNil) 434 } 435 436 func (s *appsSuite) TestAppInfosForMissingApp(c *check.C) { 437 st := s.d.Overlord().State() 438 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-c.whatever"}, daemon.AppInfoServiceTrue) 439 c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{}) 440 c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404) 441 c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindAppNotFound) 442 c.Assert(appInfos, check.IsNil) 443 } 444 445 func (s *appsSuite) TestAppInfosForMissingSnap(c *check.C) { 446 st := s.d.Overlord().State() 447 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-x"}, daemon.AppInfoServiceTrue) 448 c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{}) 449 c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404) 450 c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindSnapNotFound) 451 c.Assert(appInfos, check.IsNil) 452 } 453 454 func (s *appsSuite) testPostApps(c *check.C, inst servicestate.Instruction, servicecmds []serviceControlArgs) *state.Change { 455 postBody, err := json.Marshal(inst) 456 c.Assert(err, check.IsNil) 457 458 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBuffer(postBody)) 459 c.Assert(err, check.IsNil) 460 461 rsp := s.asyncReq(c, req, nil) 462 c.Assert(rsp.Status, check.Equals, 202) 463 c.Check(rsp.Change, check.Matches, `[0-9]+`) 464 465 st := s.d.Overlord().State() 466 st.Lock() 467 defer st.Unlock() 468 chg := st.Change(rsp.Change) 469 c.Assert(chg, check.NotNil) 470 c.Check(chg.Tasks(), check.HasLen, len(servicecmds)) 471 472 st.Unlock() 473 <-chg.Ready() 474 st.Lock() 475 476 c.Check(s.serviceControlCalls, check.DeepEquals, servicecmds) 477 return chg 478 } 479 480 func (s *appsSuite) TestPostAppsStartOne(c *check.C) { 481 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}} 482 expected := []serviceControlArgs{ 483 {action: "start", names: []string{"snap-a.svc2"}}, 484 } 485 chg := s.testPostApps(c, inst, expected) 486 chg.State().Lock() 487 defer chg.State().Unlock() 488 489 var names []string 490 err := chg.Get("snap-names", &names) 491 c.Assert(err, check.IsNil) 492 c.Assert(names, check.DeepEquals, []string{"snap-a"}) 493 } 494 495 func (s *appsSuite) TestPostAppsStartTwo(c *check.C) { 496 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a"}} 497 expected := []serviceControlArgs{ 498 {action: "start", names: []string{"snap-a.svc1", "snap-a.svc2"}}, 499 } 500 chg := s.testPostApps(c, inst, expected) 501 502 chg.State().Lock() 503 defer chg.State().Unlock() 504 505 var names []string 506 err := chg.Get("snap-names", &names) 507 c.Assert(err, check.IsNil) 508 c.Assert(names, check.DeepEquals, []string{"snap-a"}) 509 } 510 511 func (s *appsSuite) TestPostAppsStartThree(c *check.C) { 512 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a", "snap-b"}} 513 expected := []serviceControlArgs{ 514 {action: "start", names: []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"}}, 515 } 516 chg := s.testPostApps(c, inst, expected) 517 // check the summary expands the snap into actual apps 518 c.Check(chg.Summary(), check.Equals, "Running service command") 519 chg.State().Lock() 520 defer chg.State().Unlock() 521 522 var names []string 523 err := chg.Get("snap-names", &names) 524 c.Assert(err, check.IsNil) 525 c.Assert(names, check.DeepEquals, []string{"snap-a", "snap-b"}) 526 } 527 528 func (s *appsSuite) TestPostAppsStop(c *check.C) { 529 inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}} 530 expected := []serviceControlArgs{ 531 {action: "stop", names: []string{"snap-a.svc2"}}, 532 } 533 s.testPostApps(c, inst, expected) 534 } 535 536 func (s *appsSuite) TestPostAppsRestart(c *check.C) { 537 inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}} 538 expected := []serviceControlArgs{ 539 {action: "restart", names: []string{"snap-a.svc2"}}, 540 } 541 s.testPostApps(c, inst, expected) 542 } 543 544 func (s *appsSuite) TestPostAppsReload(c *check.C) { 545 inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}} 546 inst.Reload = true 547 expected := []serviceControlArgs{ 548 {action: "restart", options: "reload", names: []string{"snap-a.svc2"}}, 549 } 550 s.testPostApps(c, inst, expected) 551 } 552 553 func (s *appsSuite) TestPostAppsEnableNow(c *check.C) { 554 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}} 555 inst.Enable = true 556 expected := []serviceControlArgs{ 557 {action: "start", options: "enable", names: []string{"snap-a.svc2"}}, 558 } 559 s.testPostApps(c, inst, expected) 560 } 561 562 func (s *appsSuite) TestPostAppsDisableNow(c *check.C) { 563 inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}} 564 inst.Disable = true 565 expected := []serviceControlArgs{ 566 {action: "stop", options: "disable", names: []string{"snap-a.svc2"}}, 567 } 568 s.testPostApps(c, inst, expected) 569 } 570 571 func (s *appsSuite) TestPostAppsBadJSON(c *check.C) { 572 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`'junk`)) 573 c.Assert(err, check.IsNil) 574 rsp := s.errorReq(c, req, nil) 575 c.Check(rsp.Status, check.Equals, 400) 576 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, ".*cannot decode request body.*") 577 } 578 579 func (s *appsSuite) TestPostAppsBadOp(c *check.C) { 580 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"random": "json"}`)) 581 c.Assert(err, check.IsNil) 582 rsp := s.errorReq(c, req, nil) 583 c.Check(rsp.Status, check.Equals, 400) 584 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, ".*cannot perform operation on services without a list of services.*") 585 } 586 587 func (s *appsSuite) TestPostAppsBadSnap(c *check.C) { 588 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-c"]}`)) 589 c.Assert(err, check.IsNil) 590 rsp := s.errorReq(c, req, nil) 591 c.Check(rsp.Status, check.Equals, 404) 592 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-c" has no services`) 593 } 594 595 func (s *appsSuite) TestPostAppsBadApp(c *check.C) { 596 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-a.what"]}`)) 597 c.Assert(err, check.IsNil) 598 rsp := s.errorReq(c, req, nil) 599 c.Check(rsp.Status, check.Equals, 404) 600 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-a" has no service "what"`) 601 } 602 603 func (s *appsSuite) TestPostAppsServiceControlError(c *check.C) { 604 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`)) 605 c.Assert(err, check.IsNil) 606 s.serviceControlError = fmt.Errorf("total failure") 607 rsp := s.errorReq(c, req, nil) 608 c.Check(rsp.Status, check.Equals, 400) 609 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `total failure`) 610 } 611 612 func (s *appsSuite) TestPostAppsConflict(c *check.C) { 613 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`)) 614 c.Assert(err, check.IsNil) 615 s.serviceControlError = &snapstate.ChangeConflictError{Snap: "snap-a", ChangeKind: "enable"} 616 rsp := s.errorReq(c, req, nil) 617 c.Check(rsp.Status, check.Equals, 400) 618 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-a" has "enable" change in progress`) 619 } 620 621 func (s *appsSuite) TestLogs(c *check.C) { 622 s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(` 623 {"MESSAGE": "hello1", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "42"} 624 {"MESSAGE": "hello2", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "44"} 625 {"MESSAGE": "hello3", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "46"} 626 {"MESSAGE": "hello4", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "48"} 627 {"MESSAGE": "hello5", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "50"} 628 `))} 629 630 req, err := http.NewRequest("GET", "/v2/logs?names=snap-a.svc2&n=42&follow=false", nil) 631 c.Assert(err, check.IsNil) 632 633 rec := httptest.NewRecorder() 634 s.req(c, req, nil).ServeHTTP(rec, req) 635 636 c.Check(s.jctlSvcses, check.DeepEquals, [][]string{{"snap.snap-a.svc2.service"}}) 637 c.Check(s.jctlNs, check.DeepEquals, []int{42}) 638 c.Check(s.jctlFollows, check.DeepEquals, []bool{false}) 639 640 c.Check(rec.Code, check.Equals, 200) 641 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json-seq") 642 c.Check(rec.Body.String(), check.Equals, ` 643 {"timestamp":"1970-01-01T00:00:00.000042Z","message":"hello1","sid":"xyzzy","pid":"42"} 644 {"timestamp":"1970-01-01T00:00:00.000044Z","message":"hello2","sid":"xyzzy","pid":"42"} 645 {"timestamp":"1970-01-01T00:00:00.000046Z","message":"hello3","sid":"xyzzy","pid":"42"} 646 {"timestamp":"1970-01-01T00:00:00.000048Z","message":"hello4","sid":"xyzzy","pid":"42"} 647 {"timestamp":"1970-01-01T00:00:00.00005Z","message":"hello5","sid":"xyzzy","pid":"42"} 648 `[1:]) 649 } 650 651 func (s *appsSuite) TestLogsN(c *check.C) { 652 type T struct { 653 in string 654 out int 655 } 656 657 for _, t := range []T{ 658 {in: "", out: 10}, 659 {in: "0", out: 0}, 660 {in: "-1", out: -1}, 661 {in: strconv.Itoa(math.MinInt32), out: math.MinInt32}, 662 {in: strconv.Itoa(math.MaxInt32), out: math.MaxInt32}, 663 } { 664 665 s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(""))} 666 s.jctlNs = nil 667 668 req, err := http.NewRequest("GET", "/v2/logs?n="+t.in, nil) 669 c.Assert(err, check.IsNil) 670 671 rec := httptest.NewRecorder() 672 s.req(c, req, nil).ServeHTTP(rec, req) 673 674 c.Check(s.jctlNs, check.DeepEquals, []int{t.out}) 675 } 676 } 677 678 func (s *appsSuite) TestLogsBadN(c *check.C) { 679 req, err := http.NewRequest("GET", "/v2/logs?n=hello", nil) 680 c.Assert(err, check.IsNil) 681 682 rsp := s.errorReq(c, req, nil) 683 c.Assert(rsp.Status, check.Equals, 400) 684 } 685 686 func (s *appsSuite) TestLogsFollow(c *check.C) { 687 s.jctlRCs = []io.ReadCloser{ 688 ioutil.NopCloser(strings.NewReader("")), 689 ioutil.NopCloser(strings.NewReader("")), 690 ioutil.NopCloser(strings.NewReader("")), 691 } 692 693 reqT, err := http.NewRequest("GET", "/v2/logs?follow=true", nil) 694 c.Assert(err, check.IsNil) 695 reqF, err := http.NewRequest("GET", "/v2/logs?follow=false", nil) 696 c.Assert(err, check.IsNil) 697 reqN, err := http.NewRequest("GET", "/v2/logs", nil) 698 c.Assert(err, check.IsNil) 699 700 rec := httptest.NewRecorder() 701 s.req(c, reqT, nil).ServeHTTP(rec, reqT) 702 s.req(c, reqF, nil).ServeHTTP(rec, reqF) 703 s.req(c, reqN, nil).ServeHTTP(rec, reqN) 704 705 c.Check(s.jctlFollows, check.DeepEquals, []bool{true, false, false}) 706 } 707 708 func (s *appsSuite) TestLogsBadFollow(c *check.C) { 709 req, err := http.NewRequest("GET", "/v2/logs?follow=hello", nil) 710 c.Assert(err, check.IsNil) 711 712 rsp := s.errorReq(c, req, nil) 713 c.Assert(rsp.Status, check.Equals, 400) 714 } 715 716 func (s *appsSuite) TestLogsBadName(c *check.C) { 717 req, err := http.NewRequest("GET", "/v2/logs?names=hello", nil) 718 c.Assert(err, check.IsNil) 719 720 rsp := s.errorReq(c, req, nil) 721 c.Assert(rsp.Status, check.Equals, 404) 722 } 723 724 func (s *appsSuite) TestLogsSad(c *check.C) { 725 s.jctlErrs = []error{errors.New("potato")} 726 req, err := http.NewRequest("GET", "/v2/logs", nil) 727 c.Assert(err, check.IsNil) 728 729 rsp := s.errorReq(c, req, nil) 730 c.Assert(rsp.Status, check.Equals, 500) 731 } 732 733 func (s *appsSuite) TestLogsNoServices(c *check.C) { 734 // no installed snaps with services 735 st := s.d.Overlord().State() 736 st.Lock() 737 st.Set("snaps", nil) 738 st.Unlock() 739 740 req, err := http.NewRequest("GET", "/v2/logs", nil) 741 c.Assert(err, check.IsNil) 742 743 rsp := s.errorReq(c, req, nil) 744 c.Assert(rsp.Status, check.Equals, 404) 745 }