github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/usersession/agent/rest_api_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 agent_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "net/http" 27 "net/http/httptest" 28 "os" 29 "time" 30 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/systemd" 35 "github.com/snapcore/snapd/testutil" 36 "github.com/snapcore/snapd/usersession/agent" 37 ) 38 39 type restSuite struct { 40 testutil.BaseTest 41 sysdLog [][]string 42 } 43 44 var _ = Suite(&restSuite{}) 45 46 func (s *restSuite) SetUpTest(c *C) { 47 s.BaseTest.SetUpTest(c) 48 dirs.SetRootDir(c.MkDir()) 49 xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, os.Getuid()) 50 c.Assert(os.MkdirAll(xdgRuntimeDir, 0700), IsNil) 51 52 s.sysdLog = nil 53 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 54 s.sysdLog = append(s.sysdLog, cmd) 55 return []byte("ActiveState=inactive\n"), nil 56 }) 57 s.AddCleanup(restore) 58 restore = systemd.MockStopDelays(time.Millisecond, 25*time.Second) 59 s.AddCleanup(restore) 60 restore = agent.MockStopTimeouts(20*time.Millisecond, time.Millisecond) 61 s.AddCleanup(restore) 62 } 63 64 func (s *restSuite) TearDownTest(c *C) { 65 dirs.SetRootDir("") 66 s.BaseTest.TearDownTest(c) 67 } 68 69 type resp struct { 70 Type agent.ResponseType `json:"type"` 71 Result interface{} `json:"result"` 72 } 73 74 func (s *restSuite) TestSessionInfo(c *C) { 75 // the agent.SessionInfo end point only supports GET requests 76 c.Check(agent.SessionInfoCmd.PUT, IsNil) 77 c.Check(agent.SessionInfoCmd.POST, IsNil) 78 c.Check(agent.SessionInfoCmd.DELETE, IsNil) 79 c.Assert(agent.SessionInfoCmd.GET, NotNil) 80 81 c.Check(agent.SessionInfoCmd.Path, Equals, "/v1/session-info") 82 83 a, err := agent.New() 84 c.Assert(err, IsNil) 85 a.Version = "42b1" 86 rec := httptest.NewRecorder() 87 agent.SessionInfoCmd.GET(agent.SessionInfoCmd, nil).ServeHTTP(rec, nil) 88 c.Check(rec.Code, Equals, 200) 89 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 90 91 var rsp resp 92 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 93 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 94 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 95 "version": "42b1", 96 }) 97 } 98 99 func (s *restSuite) TestServiceControl(c *C) { 100 // the agent.Services end point only supports POST requests 101 c.Assert(agent.ServiceControlCmd.GET, IsNil) 102 c.Check(agent.ServiceControlCmd.PUT, IsNil) 103 c.Check(agent.ServiceControlCmd.POST, NotNil) 104 c.Check(agent.ServiceControlCmd.DELETE, IsNil) 105 106 c.Check(agent.ServiceControlCmd.Path, Equals, "/v1/service-control") 107 } 108 109 func (s *restSuite) TestServiceControlDaemonReload(c *C) { 110 s.testServiceControlDaemonReload(c, "application/json") 111 } 112 113 func (s *restSuite) TestServiceControlDaemonReloadComplexerContentType(c *C) { 114 s.testServiceControlDaemonReload(c, "application/json; charset=utf-8") 115 } 116 117 func (s *restSuite) TestServiceControlDaemonReloadInvalidCharset(c *C) { 118 _, err := agent.New() 119 c.Assert(err, IsNil) 120 121 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"daemon-reload"}`)) 122 req.Header.Set("Content-Type", "application/json; charset=iso-8859-1") 123 c.Assert(err, IsNil) 124 rec := httptest.NewRecorder() 125 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 126 c.Check(rec.Code, Equals, 400) 127 c.Check(rec.Body.String(), testutil.Contains, 128 "unknown charset in content type") 129 } 130 131 func (s *restSuite) testServiceControlDaemonReload(c *C, contentType string) { 132 _, err := agent.New() 133 c.Assert(err, IsNil) 134 135 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"daemon-reload"}`)) 136 req.Header.Set("Content-Type", contentType) 137 c.Assert(err, IsNil) 138 rec := httptest.NewRecorder() 139 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 140 c.Check(rec.Code, Equals, 200) 141 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 142 143 var rsp resp 144 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 145 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 146 c.Check(rsp.Result, IsNil) 147 148 c.Check(s.sysdLog, DeepEquals, [][]string{ 149 {"--user", "daemon-reload"}, 150 }) 151 } 152 153 func (s *restSuite) TestServiceControlStart(c *C) { 154 _, err := agent.New() 155 c.Assert(err, IsNil) 156 157 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"start","services":["snap.foo.service", "snap.bar.service"]}`)) 158 req.Header.Set("Content-Type", "application/json") 159 c.Assert(err, IsNil) 160 rec := httptest.NewRecorder() 161 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 162 c.Check(rec.Code, Equals, 200) 163 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 164 165 var rsp resp 166 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 167 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 168 c.Check(rsp.Result, Equals, nil) 169 170 c.Check(s.sysdLog, DeepEquals, [][]string{ 171 {"--user", "start", "snap.foo.service"}, 172 {"--user", "start", "snap.bar.service"}, 173 }) 174 } 175 176 func (s *restSuite) TestServicesStartNonSnap(c *C) { 177 _, err := agent.New() 178 c.Assert(err, IsNil) 179 180 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"start","services":["snap.foo.service", "not-snap.bar.service"]}`)) 181 req.Header.Set("Content-Type", "application/json") 182 c.Assert(err, IsNil) 183 rec := httptest.NewRecorder() 184 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 185 c.Check(rec.Code, Equals, 500) 186 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 187 188 var rsp resp 189 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 190 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 191 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 192 "message": "cannot start non-snap service not-snap.bar.service", 193 }) 194 195 // No services were started on the error. 196 c.Check(s.sysdLog, HasLen, 0) 197 } 198 199 func (s *restSuite) TestServicesStartFailureStopsServices(c *C) { 200 var sysdLog [][]string 201 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 202 sysdLog = append(sysdLog, cmd) 203 if cmd[0] == "--user" && cmd[1] == "start" && cmd[2] == "snap.bar.service" { 204 return nil, fmt.Errorf("start failure") 205 } 206 return []byte("ActiveState=inactive\n"), nil 207 }) 208 defer restore() 209 210 _, err := agent.New() 211 c.Assert(err, IsNil) 212 213 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"start","services":["snap.foo.service", "snap.bar.service"]}`)) 214 req.Header.Set("Content-Type", "application/json") 215 c.Assert(err, IsNil) 216 rec := httptest.NewRecorder() 217 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 218 c.Check(rec.Code, Equals, 500) 219 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 220 221 var rsp resp 222 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 223 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 224 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 225 "message": "some user services failed to start", 226 "kind": "service-control", 227 "value": map[string]interface{}{ 228 "start-errors": map[string]interface{}{ 229 "snap.bar.service": "start failure", 230 }, 231 "stop-errors": map[string]interface{}{}, 232 }, 233 }) 234 235 c.Check(sysdLog, DeepEquals, [][]string{ 236 {"--user", "start", "snap.foo.service"}, 237 {"--user", "start", "snap.bar.service"}, 238 {"--user", "stop", "snap.foo.service"}, 239 {"--user", "show", "--property=ActiveState", "snap.foo.service"}, 240 }) 241 } 242 243 func (s *restSuite) TestServicesStartFailureReportsStopFailures(c *C) { 244 var sysdLog [][]string 245 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 246 sysdLog = append(sysdLog, cmd) 247 if cmd[0] == "--user" && cmd[1] == "start" && cmd[2] == "snap.bar.service" { 248 return nil, fmt.Errorf("start failure") 249 } 250 if cmd[0] == "--user" && cmd[1] == "stop" && cmd[2] == "snap.foo.service" { 251 return nil, fmt.Errorf("stop failure") 252 } 253 return []byte("ActiveState=inactive\n"), nil 254 }) 255 defer restore() 256 257 _, err := agent.New() 258 c.Assert(err, IsNil) 259 260 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"start","services":["snap.foo.service", "snap.bar.service"]}`)) 261 req.Header.Set("Content-Type", "application/json") 262 c.Assert(err, IsNil) 263 rec := httptest.NewRecorder() 264 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 265 c.Check(rec.Code, Equals, 500) 266 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 267 268 var rsp resp 269 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 270 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 271 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 272 "message": "some user services failed to start", 273 "kind": "service-control", 274 "value": map[string]interface{}{ 275 "start-errors": map[string]interface{}{ 276 "snap.bar.service": "start failure", 277 }, 278 "stop-errors": map[string]interface{}{ 279 "snap.foo.service": "stop failure", 280 }, 281 }, 282 }) 283 284 c.Check(sysdLog, DeepEquals, [][]string{ 285 {"--user", "start", "snap.foo.service"}, 286 {"--user", "start", "snap.bar.service"}, 287 {"--user", "stop", "snap.foo.service"}, 288 }) 289 } 290 291 func (s *restSuite) TestServicesStop(c *C) { 292 _, err := agent.New() 293 c.Assert(err, IsNil) 294 295 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"stop","services":["snap.foo.service", "snap.bar.service"]}`)) 296 req.Header.Set("Content-Type", "application/json") 297 c.Assert(err, IsNil) 298 rec := httptest.NewRecorder() 299 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 300 c.Check(rec.Code, Equals, 200) 301 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 302 303 var rsp resp 304 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 305 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 306 c.Check(rsp.Result, Equals, nil) 307 308 c.Check(s.sysdLog, DeepEquals, [][]string{ 309 {"--user", "stop", "snap.foo.service"}, 310 {"--user", "show", "--property=ActiveState", "snap.foo.service"}, 311 {"--user", "stop", "snap.bar.service"}, 312 {"--user", "show", "--property=ActiveState", "snap.bar.service"}, 313 }) 314 } 315 316 func (s *restSuite) TestServicesStopNonSnap(c *C) { 317 _, err := agent.New() 318 c.Assert(err, IsNil) 319 320 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"stop","services":["snap.foo.service", "not-snap.bar.service"]}`)) 321 req.Header.Set("Content-Type", "application/json") 322 c.Assert(err, IsNil) 323 rec := httptest.NewRecorder() 324 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 325 c.Check(rec.Code, Equals, 500) 326 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 327 328 var rsp resp 329 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 330 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 331 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 332 "message": "cannot stop non-snap service not-snap.bar.service", 333 }) 334 335 // No services were started on the error. 336 c.Check(s.sysdLog, HasLen, 0) 337 } 338 339 func (s *restSuite) TestServicesStopReportsTimeout(c *C) { 340 var sysdLog [][]string 341 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 342 // Ignore "show" spam 343 if cmd[1] != "show" { 344 sysdLog = append(sysdLog, cmd) 345 } 346 if cmd[len(cmd)-1] == "snap.bar.service" { 347 return []byte("ActiveState=active\n"), nil 348 } 349 return []byte("ActiveState=inactive\n"), nil 350 }) 351 defer restore() 352 353 _, err := agent.New() 354 c.Assert(err, IsNil) 355 356 req, err := http.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"stop","services":["snap.foo.service", "snap.bar.service"]}`)) 357 req.Header.Set("Content-Type", "application/json") 358 c.Assert(err, IsNil) 359 rec := httptest.NewRecorder() 360 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 361 c.Check(rec.Code, Equals, 500) 362 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 363 364 var rsp resp 365 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 366 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 367 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 368 "message": "some user services failed to stop", 369 "kind": "service-control", 370 "value": map[string]interface{}{ 371 "stop-errors": map[string]interface{}{ 372 "snap.bar.service": "snap.bar.service failed to stop: timeout", 373 }, 374 }, 375 }) 376 377 c.Check(sysdLog, DeepEquals, [][]string{ 378 {"--user", "stop", "snap.foo.service"}, 379 {"--user", "stop", "snap.bar.service"}, 380 }) 381 }