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