github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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 *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 150 d.Overlord().Loop() 151 s.AddCleanup(func() { d.Overlord().Stop() }) 152 } 153 154 func (s *appsSuite) TestSplitAppName(c *check.C) { 155 type T struct { 156 name string 157 snap string 158 app string 159 } 160 161 for _, x := range []T{ 162 {name: "foo.bar", snap: "foo", app: "bar"}, 163 {name: "foo", snap: "foo", app: ""}, 164 {name: "foo.bar.baz", snap: "foo", app: "bar.baz"}, 165 {name: ".", snap: "", app: ""}, // SISO 166 } { 167 snap, app := daemon.SplitAppName(x.name) 168 c.Check(x.snap, check.Equals, snap, check.Commentf(x.name)) 169 c.Check(x.app, check.Equals, app, check.Commentf(x.name)) 170 } 171 } 172 173 func (s *appsSuite) TestGetAppsInfo(c *check.C) { 174 svcNames := []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"} 175 for _, name := range svcNames { 176 s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(` 177 Id=snap.%s.service 178 Type=simple 179 ActiveState=active 180 UnitFileState=enabled 181 `[1:], name))) 182 } 183 184 req, err := http.NewRequest("GET", "/v2/apps", nil) 185 c.Assert(err, check.IsNil) 186 187 rsp := s.req(c, req, nil).(*daemon.Resp) 188 c.Assert(rsp.Status, check.Equals, 200) 189 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeSync) 190 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 191 apps := rsp.Result.([]client.AppInfo) 192 c.Assert(apps, check.HasLen, 6) 193 194 for _, name := range svcNames { 195 snap, app := daemon.SplitAppName(name) 196 needle := client.AppInfo{ 197 Snap: snap, 198 Name: app, 199 Daemon: "simple", 200 } 201 if snap != "snap-b" { 202 // snap-b is not active (all the others are) 203 needle.Active = true 204 needle.Enabled = true 205 } 206 c.Check(apps, testutil.DeepContains, needle) 207 } 208 209 for _, name := range []string{"snap-b.cmd1", "snap-d.cmd2", "snap-d.cmd3"} { 210 snap, app := daemon.SplitAppName(name) 211 c.Check(apps, testutil.DeepContains, client.AppInfo{ 212 Snap: snap, 213 Name: app, 214 }) 215 } 216 217 appNames := make([]string, len(apps)) 218 for i, app := range apps { 219 appNames[i] = app.Snap + "." + app.Name 220 } 221 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 222 } 223 224 func (s *appsSuite) TestGetAppsInfoNames(c *check.C) { 225 226 req, err := http.NewRequest("GET", "/v2/apps?names=snap-d", nil) 227 c.Assert(err, check.IsNil) 228 229 rsp := s.req(c, req, nil).(*daemon.Resp) 230 c.Assert(rsp.Status, check.Equals, 200) 231 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeSync) 232 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 233 apps := rsp.Result.([]client.AppInfo) 234 c.Assert(apps, check.HasLen, 2) 235 236 for _, name := range []string{"snap-d.cmd2", "snap-d.cmd3"} { 237 snap, app := daemon.SplitAppName(name) 238 c.Check(apps, testutil.DeepContains, client.AppInfo{ 239 Snap: snap, 240 Name: app, 241 }) 242 } 243 244 appNames := make([]string, len(apps)) 245 for i, app := range apps { 246 appNames[i] = app.Snap + "." + app.Name 247 } 248 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 249 } 250 251 func (s *appsSuite) TestGetAppsInfoServices(c *check.C) { 252 svcNames := []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"} 253 for _, name := range svcNames { 254 s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(` 255 Id=snap.%s.service 256 Type=simple 257 ActiveState=active 258 UnitFileState=enabled 259 `[1:], name))) 260 } 261 262 req, err := http.NewRequest("GET", "/v2/apps?select=service", nil) 263 c.Assert(err, check.IsNil) 264 265 rsp := s.req(c, req, nil).(*daemon.Resp) 266 c.Assert(rsp.Status, check.Equals, 200) 267 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeSync) 268 c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{}) 269 svcs := rsp.Result.([]client.AppInfo) 270 c.Assert(svcs, check.HasLen, 3) 271 272 for _, name := range svcNames { 273 snap, app := daemon.SplitAppName(name) 274 needle := client.AppInfo{ 275 Snap: snap, 276 Name: app, 277 Daemon: "simple", 278 } 279 if snap != "snap-b" { 280 // snap-b is not active (all the others are) 281 needle.Active = true 282 needle.Enabled = true 283 } 284 c.Check(svcs, testutil.DeepContains, needle) 285 } 286 287 appNames := make([]string, len(svcs)) 288 for i, svc := range svcs { 289 appNames[i] = svc.Snap + "." + svc.Name 290 } 291 c.Check(sort.StringsAreSorted(appNames), check.Equals, true) 292 } 293 294 func (s *appsSuite) TestGetAppsInfoBadSelect(c *check.C) { 295 req, err := http.NewRequest("GET", "/v2/apps?select=potato", nil) 296 c.Assert(err, check.IsNil) 297 298 rsp := s.req(c, req, nil).(*daemon.Resp) 299 c.Assert(rsp.Status, check.Equals, 400) 300 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 301 } 302 303 func (s *appsSuite) TestGetAppsInfoBadName(c *check.C) { 304 req, err := http.NewRequest("GET", "/v2/apps?names=potato", nil) 305 c.Assert(err, check.IsNil) 306 307 rsp := s.req(c, req, nil).(*daemon.Resp) 308 c.Assert(rsp.Status, check.Equals, 404) 309 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 310 } 311 312 func (s *appsSuite) TestAppInfosForOne(c *check.C) { 313 st := s.d.Overlord().State() 314 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a.svc1"}, daemon.AppInfoServiceTrue) 315 c.Assert(rsp, check.IsNil) 316 c.Assert(appInfos, check.HasLen, 1) 317 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 318 c.Check(appInfos[0].Name, check.Equals, "svc1") 319 } 320 321 func (s *appsSuite) TestAppInfosForAll(c *check.C) { 322 type T struct { 323 opts daemon.AppInfoOptions 324 snaps []*snap.Info 325 names []string 326 } 327 328 for _, t := range []T{ 329 { 330 opts: daemon.AppInfoServiceTrue, 331 names: []string{"svc1", "svc2", "svc3"}, 332 snaps: []*snap.Info{s.infoA, s.infoA, s.infoB}, 333 }, 334 { 335 opts: daemon.AppInfoServiceFalse, 336 names: []string{"svc1", "svc2", "cmd1", "svc3", "cmd2", "cmd3"}, 337 snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoB, s.infoD, s.infoD}, 338 }, 339 } { 340 c.Assert(len(t.names), check.Equals, len(t.snaps), check.Commentf("%s", t.opts)) 341 342 st := s.d.Overlord().State() 343 appInfos, rsp := daemon.AppInfosFor(st, nil, t.opts) 344 c.Assert(rsp, check.IsNil, check.Commentf("%s", t.opts)) 345 names := make([]string, len(appInfos)) 346 for i, appInfo := range appInfos { 347 names[i] = appInfo.Name 348 } 349 c.Assert(names, check.DeepEquals, t.names, check.Commentf("%s", t.opts)) 350 351 for i := range appInfos { 352 c.Check(appInfos[i].Snap, check.DeepEquals, t.snaps[i], check.Commentf("%s: %s", t.opts, t.names[i])) 353 } 354 } 355 } 356 357 func (s *appsSuite) TestAppInfosForOneSnap(c *check.C) { 358 st := s.d.Overlord().State() 359 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a"}, daemon.AppInfoServiceTrue) 360 c.Assert(rsp, check.IsNil) 361 c.Assert(appInfos, check.HasLen, 2) 362 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 363 364 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 365 c.Check(appInfos[0].Name, check.Equals, "svc1") 366 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 367 c.Check(appInfos[1].Name, check.Equals, "svc2") 368 } 369 370 func (s *appsSuite) TestAppInfosForMixedArgs(c *check.C) { 371 st := s.d.Overlord().State() 372 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a", "snap-a.svc1"}, daemon.AppInfoServiceTrue) 373 c.Assert(rsp, check.IsNil) 374 c.Assert(appInfos, check.HasLen, 2) 375 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 376 377 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 378 c.Check(appInfos[0].Name, check.Equals, "svc1") 379 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 380 c.Check(appInfos[1].Name, check.Equals, "svc2") 381 } 382 383 func (s *appsSuite) TestAppInfosCleanupAndSorted(c *check.C) { 384 st := s.d.Overlord().State() 385 appInfos, rsp := daemon.AppInfosFor(st, []string{ 386 "snap-b.svc3", 387 "snap-a.svc2", 388 "snap-a.svc1", 389 "snap-a.svc2", 390 "snap-b.svc3", 391 "snap-a.svc1", 392 "snap-b", 393 "snap-a", 394 }, daemon.AppInfoServiceTrue) 395 c.Assert(rsp, check.IsNil) 396 c.Assert(appInfos, check.HasLen, 3) 397 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 398 399 c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) 400 c.Check(appInfos[0].Name, check.Equals, "svc1") 401 c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) 402 c.Check(appInfos[1].Name, check.Equals, "svc2") 403 c.Check(appInfos[2].Snap, check.DeepEquals, s.infoB) 404 c.Check(appInfos[2].Name, check.Equals, "svc3") 405 } 406 407 func (s *appsSuite) TestAppInfosForAppless(c *check.C) { 408 st := s.d.Overlord().State() 409 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-c"}, daemon.AppInfoServiceTrue) 410 c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{}) 411 c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404) 412 c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindAppNotFound) 413 c.Assert(appInfos, check.IsNil) 414 } 415 416 func (s *appsSuite) TestAppInfosForMissingApp(c *check.C) { 417 st := s.d.Overlord().State() 418 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-c.whatever"}, daemon.AppInfoServiceTrue) 419 c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{}) 420 c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404) 421 c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindAppNotFound) 422 c.Assert(appInfos, check.IsNil) 423 } 424 425 func (s *appsSuite) TestAppInfosForMissingSnap(c *check.C) { 426 st := s.d.Overlord().State() 427 appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-x"}, daemon.AppInfoServiceTrue) 428 c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{}) 429 c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404) 430 c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindSnapNotFound) 431 c.Assert(appInfos, check.IsNil) 432 } 433 434 func (s *appsSuite) testPostApps(c *check.C, inst servicestate.Instruction, servicecmds []serviceControlArgs) *state.Change { 435 postBody, err := json.Marshal(inst) 436 c.Assert(err, check.IsNil) 437 438 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBuffer(postBody)) 439 c.Assert(err, check.IsNil) 440 441 rsp := s.req(c, req, nil).(*daemon.Resp) 442 c.Assert(rsp.Status, check.Equals, 202) 443 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync) 444 c.Check(rsp.Change, check.Matches, `[0-9]+`) 445 446 st := s.d.Overlord().State() 447 st.Lock() 448 defer st.Unlock() 449 chg := st.Change(rsp.Change) 450 c.Assert(chg, check.NotNil) 451 c.Check(chg.Tasks(), check.HasLen, len(servicecmds)) 452 453 st.Unlock() 454 <-chg.Ready() 455 st.Lock() 456 457 c.Check(s.serviceControlCalls, check.DeepEquals, servicecmds) 458 return chg 459 } 460 461 func (s *appsSuite) TestPostAppsStartOne(c *check.C) { 462 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}} 463 expected := []serviceControlArgs{ 464 {action: "start", names: []string{"snap-a.svc2"}}, 465 } 466 chg := s.testPostApps(c, inst, expected) 467 chg.State().Lock() 468 defer chg.State().Unlock() 469 470 var names []string 471 err := chg.Get("snap-names", &names) 472 c.Assert(err, check.IsNil) 473 c.Assert(names, check.DeepEquals, []string{"snap-a"}) 474 } 475 476 func (s *appsSuite) TestPostAppsStartTwo(c *check.C) { 477 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a"}} 478 expected := []serviceControlArgs{ 479 {action: "start", names: []string{"snap-a.svc1", "snap-a.svc2"}}, 480 } 481 chg := s.testPostApps(c, inst, expected) 482 483 chg.State().Lock() 484 defer chg.State().Unlock() 485 486 var names []string 487 err := chg.Get("snap-names", &names) 488 c.Assert(err, check.IsNil) 489 c.Assert(names, check.DeepEquals, []string{"snap-a"}) 490 } 491 492 func (s *appsSuite) TestPostAppsStartThree(c *check.C) { 493 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a", "snap-b"}} 494 expected := []serviceControlArgs{ 495 {action: "start", names: []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"}}, 496 } 497 chg := s.testPostApps(c, inst, expected) 498 // check the summary expands the snap into actual apps 499 c.Check(chg.Summary(), check.Equals, "Running service command") 500 chg.State().Lock() 501 defer chg.State().Unlock() 502 503 var names []string 504 err := chg.Get("snap-names", &names) 505 c.Assert(err, check.IsNil) 506 c.Assert(names, check.DeepEquals, []string{"snap-a", "snap-b"}) 507 } 508 509 func (s *appsSuite) TestPostAppsStop(c *check.C) { 510 inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}} 511 expected := []serviceControlArgs{ 512 {action: "stop", names: []string{"snap-a.svc2"}}, 513 } 514 s.testPostApps(c, inst, expected) 515 } 516 517 func (s *appsSuite) TestPostAppsRestart(c *check.C) { 518 inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}} 519 expected := []serviceControlArgs{ 520 {action: "restart", names: []string{"snap-a.svc2"}}, 521 } 522 s.testPostApps(c, inst, expected) 523 } 524 525 func (s *appsSuite) TestPostAppsReload(c *check.C) { 526 inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}} 527 inst.Reload = true 528 expected := []serviceControlArgs{ 529 {action: "restart", options: "reload", names: []string{"snap-a.svc2"}}, 530 } 531 s.testPostApps(c, inst, expected) 532 } 533 534 func (s *appsSuite) TestPostAppsEnableNow(c *check.C) { 535 inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}} 536 inst.Enable = true 537 expected := []serviceControlArgs{ 538 {action: "start", options: "enable", names: []string{"snap-a.svc2"}}, 539 } 540 s.testPostApps(c, inst, expected) 541 } 542 543 func (s *appsSuite) TestPostAppsDisableNow(c *check.C) { 544 inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}} 545 inst.Disable = true 546 expected := []serviceControlArgs{ 547 {action: "stop", options: "disable", names: []string{"snap-a.svc2"}}, 548 } 549 s.testPostApps(c, inst, expected) 550 } 551 552 func (s *appsSuite) TestPostAppsBadJSON(c *check.C) { 553 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`'junk`)) 554 c.Assert(err, check.IsNil) 555 rsp := s.req(c, req, nil).(*daemon.Resp) 556 c.Check(rsp.Status, check.Equals, 400) 557 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 558 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, ".*cannot decode request body.*") 559 } 560 561 func (s *appsSuite) TestPostAppsBadOp(c *check.C) { 562 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"random": "json"}`)) 563 c.Assert(err, check.IsNil) 564 rsp := s.req(c, req, nil).(*daemon.Resp) 565 c.Check(rsp.Status, check.Equals, 400) 566 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 567 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, ".*cannot perform operation on services without a list of services.*") 568 } 569 570 func (s *appsSuite) TestPostAppsBadSnap(c *check.C) { 571 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-c"]}`)) 572 c.Assert(err, check.IsNil) 573 rsp := s.req(c, req, nil).(*daemon.Resp) 574 c.Check(rsp.Status, check.Equals, 404) 575 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 576 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-c" has no services`) 577 } 578 579 func (s *appsSuite) TestPostAppsBadApp(c *check.C) { 580 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-a.what"]}`)) 581 c.Assert(err, check.IsNil) 582 rsp := s.req(c, req, nil).(*daemon.Resp) 583 c.Check(rsp.Status, check.Equals, 404) 584 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 585 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-a" has no service "what"`) 586 } 587 588 func (s *appsSuite) TestPostAppsServiceControlError(c *check.C) { 589 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`)) 590 c.Assert(err, check.IsNil) 591 s.serviceControlError = fmt.Errorf("total failure") 592 rsp := s.req(c, req, nil).(*daemon.Resp) 593 c.Check(rsp.Status, check.Equals, 400) 594 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 595 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `total failure`) 596 } 597 598 func (s *appsSuite) TestPostAppsConflict(c *check.C) { 599 req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`)) 600 c.Assert(err, check.IsNil) 601 s.serviceControlError = &snapstate.ChangeConflictError{Snap: "snap-a", ChangeKind: "enable"} 602 rsp := s.req(c, req, nil).(*daemon.Resp) 603 c.Check(rsp.Status, check.Equals, 400) 604 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 605 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-a" has "enable" change in progress`) 606 } 607 608 func (s *appsSuite) TestLogs(c *check.C) { 609 s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(` 610 {"MESSAGE": "hello1", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "42"} 611 {"MESSAGE": "hello2", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "44"} 612 {"MESSAGE": "hello3", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "46"} 613 {"MESSAGE": "hello4", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "48"} 614 {"MESSAGE": "hello5", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "50"} 615 `))} 616 617 req, err := http.NewRequest("GET", "/v2/logs?names=snap-a.svc2&n=42&follow=false", nil) 618 c.Assert(err, check.IsNil) 619 620 rec := httptest.NewRecorder() 621 s.req(c, req, nil).ServeHTTP(rec, req) 622 623 c.Check(s.jctlSvcses, check.DeepEquals, [][]string{{"snap.snap-a.svc2.service"}}) 624 c.Check(s.jctlNs, check.DeepEquals, []int{42}) 625 c.Check(s.jctlFollows, check.DeepEquals, []bool{false}) 626 627 c.Check(rec.Code, check.Equals, 200) 628 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json-seq") 629 c.Check(rec.Body.String(), check.Equals, ` 630 {"timestamp":"1970-01-01T00:00:00.000042Z","message":"hello1","sid":"xyzzy","pid":"42"} 631 {"timestamp":"1970-01-01T00:00:00.000044Z","message":"hello2","sid":"xyzzy","pid":"42"} 632 {"timestamp":"1970-01-01T00:00:00.000046Z","message":"hello3","sid":"xyzzy","pid":"42"} 633 {"timestamp":"1970-01-01T00:00:00.000048Z","message":"hello4","sid":"xyzzy","pid":"42"} 634 {"timestamp":"1970-01-01T00:00:00.00005Z","message":"hello5","sid":"xyzzy","pid":"42"} 635 `[1:]) 636 } 637 638 func (s *appsSuite) TestLogsN(c *check.C) { 639 type T struct { 640 in string 641 out int 642 } 643 644 for _, t := range []T{ 645 {in: "", out: 10}, 646 {in: "0", out: 0}, 647 {in: "-1", out: -1}, 648 {in: strconv.Itoa(math.MinInt32), out: math.MinInt32}, 649 {in: strconv.Itoa(math.MaxInt32), out: math.MaxInt32}, 650 } { 651 652 s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(""))} 653 s.jctlNs = nil 654 655 req, err := http.NewRequest("GET", "/v2/logs?n="+t.in, nil) 656 c.Assert(err, check.IsNil) 657 658 rec := httptest.NewRecorder() 659 s.req(c, req, nil).ServeHTTP(rec, req) 660 661 c.Check(s.jctlNs, check.DeepEquals, []int{t.out}) 662 } 663 } 664 665 func (s *appsSuite) TestLogsBadN(c *check.C) { 666 req, err := http.NewRequest("GET", "/v2/logs?n=hello", nil) 667 c.Assert(err, check.IsNil) 668 669 rsp := s.req(c, req, nil).(*daemon.Resp) 670 c.Assert(rsp.Status, check.Equals, 400) 671 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 672 } 673 674 func (s *appsSuite) TestLogsFollow(c *check.C) { 675 s.jctlRCs = []io.ReadCloser{ 676 ioutil.NopCloser(strings.NewReader("")), 677 ioutil.NopCloser(strings.NewReader("")), 678 ioutil.NopCloser(strings.NewReader("")), 679 } 680 681 reqT, err := http.NewRequest("GET", "/v2/logs?follow=true", nil) 682 c.Assert(err, check.IsNil) 683 reqF, err := http.NewRequest("GET", "/v2/logs?follow=false", nil) 684 c.Assert(err, check.IsNil) 685 reqN, err := http.NewRequest("GET", "/v2/logs", nil) 686 c.Assert(err, check.IsNil) 687 688 rec := httptest.NewRecorder() 689 s.req(c, reqT, nil).ServeHTTP(rec, reqT) 690 s.req(c, reqF, nil).ServeHTTP(rec, reqF) 691 s.req(c, reqN, nil).ServeHTTP(rec, reqN) 692 693 c.Check(s.jctlFollows, check.DeepEquals, []bool{true, false, false}) 694 } 695 696 func (s *appsSuite) TestLogsBadFollow(c *check.C) { 697 req, err := http.NewRequest("GET", "/v2/logs?follow=hello", nil) 698 c.Assert(err, check.IsNil) 699 700 rsp := s.req(c, req, nil).(*daemon.Resp) 701 c.Assert(rsp.Status, check.Equals, 400) 702 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 703 } 704 705 func (s *appsSuite) TestLogsBadName(c *check.C) { 706 req, err := http.NewRequest("GET", "/v2/logs?names=hello", nil) 707 c.Assert(err, check.IsNil) 708 709 rsp := s.req(c, req, nil).(*daemon.Resp) 710 c.Assert(rsp.Status, check.Equals, 404) 711 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 712 } 713 714 func (s *appsSuite) TestLogsSad(c *check.C) { 715 s.jctlErrs = []error{errors.New("potato")} 716 req, err := http.NewRequest("GET", "/v2/logs", nil) 717 c.Assert(err, check.IsNil) 718 719 rsp := s.req(c, req, nil).(*daemon.Resp) 720 c.Assert(rsp.Status, check.Equals, 500) 721 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 722 }