github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/usersession/client/client_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-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 client_test 21 22 import ( 23 "context" 24 "fmt" 25 "net" 26 "net/http" 27 "os" 28 "path/filepath" 29 "testing" 30 "time" 31 32 . "gopkg.in/check.v1" 33 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/testutil" 36 "github.com/snapcore/snapd/usersession/client" 37 ) 38 39 var ( 40 timeout = testutil.HostScaledTimeout(80 * time.Millisecond) 41 ) 42 43 func Test(t *testing.T) { TestingT(t) } 44 45 type clientSuite struct { 46 testutil.BaseTest 47 48 cli *client.Client 49 50 server *http.Server 51 handler http.Handler 52 } 53 54 var _ = Suite(&clientSuite{}) 55 56 func (s *clientSuite) SetUpTest(c *C) { 57 s.BaseTest.SetUpTest(c) 58 dirs.SetRootDir(c.MkDir()) 59 60 s.handler = nil 61 62 s.server = &http.Server{Handler: s} 63 for _, uid := range []int{1000, 42} { 64 sock := fmt.Sprintf("%s/%d/snapd-session-agent.socket", dirs.XdgRuntimeDirBase, uid) 65 err := os.MkdirAll(filepath.Dir(sock), 0755) 66 c.Assert(err, IsNil) 67 l, err := net.Listen("unix", sock) 68 c.Assert(err, IsNil) 69 go func(l net.Listener) { 70 err := s.server.Serve(l) 71 c.Check(err, Equals, http.ErrServerClosed) 72 }(l) 73 } 74 75 s.cli = client.New() 76 } 77 78 func (s *clientSuite) TearDownTest(c *C) { 79 s.BaseTest.TearDownTest(c) 80 dirs.SetRootDir("") 81 82 err := s.server.Shutdown(context.Background()) 83 c.Check(err, IsNil) 84 } 85 86 func (s *clientSuite) ServeHTTP(w http.ResponseWriter, r *http.Request) { 87 if s.handler == nil { 88 w.WriteHeader(500) 89 return 90 } 91 s.handler.ServeHTTP(w, r) 92 } 93 94 func (s *clientSuite) TestBadJsonResponse(c *C) { 95 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 96 w.Header().Set("Content-Type", "application/json") 97 w.WriteHeader(200) 98 w.Write([]byte(`{"type":`)) 99 }) 100 si, err := s.cli.SessionInfo(context.Background()) 101 c.Check(si, DeepEquals, map[int]client.SessionInfo{}) 102 c.Check(err, ErrorMatches, `cannot decode "{\\"type\\":": unexpected EOF`) 103 } 104 105 func (s *clientSuite) TestAgentTimeout(c *C) { 106 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 107 // Delay one of the agents from responding, but don't 108 // stick around if the client disconnects. 109 if r.Host == "1000" { 110 select { 111 case <-r.Context().Done(): 112 return 113 case <-time.After(5 * time.Second): 114 c.Fatal("Request context was not cancelled") 115 } 116 } 117 w.Header().Set("Content-Type", "application/json") 118 w.WriteHeader(200) 119 w.Write([]byte(`{ 120 "type": "sync", 121 "result": { 122 "version": "42" 123 } 124 }`)) 125 }) 126 127 ctx, cancel := context.WithTimeout(context.Background(), timeout) 128 defer cancel() 129 si, err := s.cli.SessionInfo(ctx) 130 131 // An error is reported, and we receive information about the 132 // agent that replied on time. 133 c.Assert(err, ErrorMatches, `Get \"?http://1000/v1/session-info\"?: context deadline exceeded`) 134 c.Check(si, DeepEquals, map[int]client.SessionInfo{ 135 42: {Version: "42"}, 136 }) 137 } 138 139 func (s *clientSuite) TestSessionInfo(c *C) { 140 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 141 w.Header().Set("Content-Type", "application/json") 142 w.WriteHeader(200) 143 w.Write([]byte(`{ 144 "type": "sync", 145 "result": { 146 "version": "42" 147 } 148 }`)) 149 }) 150 si, err := s.cli.SessionInfo(context.Background()) 151 c.Assert(err, IsNil) 152 c.Check(si, DeepEquals, map[int]client.SessionInfo{ 153 42: {Version: "42"}, 154 1000: {Version: "42"}, 155 }) 156 } 157 158 func (s *clientSuite) TestSessionInfoError(c *C) { 159 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 160 w.Header().Set("Content-Type", "application/json") 161 w.WriteHeader(500) 162 w.Write([]byte(`{ 163 "type": "error", 164 "result": { 165 "message": "something bad happened" 166 } 167 }`)) 168 }) 169 si, err := s.cli.SessionInfo(context.Background()) 170 c.Check(si, DeepEquals, map[int]client.SessionInfo{}) 171 c.Check(err, ErrorMatches, "something bad happened") 172 c.Check(err, DeepEquals, &client.Error{ 173 Kind: "", 174 Message: "something bad happened", 175 Value: nil, 176 }) 177 } 178 179 func (s *clientSuite) TestSessionInfoWrongResultType(c *C) { 180 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 181 w.Header().Set("Content-Type", "application/json") 182 w.WriteHeader(200) 183 w.Write([]byte(`{ 184 "type": "sync", 185 "result": ["a", "list"] 186 }`)) 187 }) 188 si, err := s.cli.SessionInfo(context.Background()) 189 c.Check(si, DeepEquals, map[int]client.SessionInfo{}) 190 c.Check(err, ErrorMatches, `json: cannot unmarshal array into Go value of type client.SessionInfo`) 191 } 192 193 func (s *clientSuite) TestServicesDaemonReload(c *C) { 194 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 195 w.Header().Set("Content-Type", "application/json") 196 w.WriteHeader(200) 197 w.Write([]byte(`{ 198 "type": "sync", 199 "result": null 200 }`)) 201 }) 202 err := s.cli.ServicesDaemonReload(context.Background()) 203 c.Assert(err, IsNil) 204 } 205 206 func (s *clientSuite) TestServicesDaemonReloadError(c *C) { 207 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 208 w.Header().Set("Content-Type", "application/json") 209 w.WriteHeader(500) 210 w.Write([]byte(`{ 211 "type": "error", 212 "result": { 213 "message": "something bad happened" 214 } 215 }`)) 216 }) 217 err := s.cli.ServicesDaemonReload(context.Background()) 218 c.Check(err, ErrorMatches, "something bad happened") 219 c.Check(err, DeepEquals, &client.Error{ 220 Kind: "", 221 Message: "something bad happened", 222 Value: nil, 223 }) 224 } 225 226 func (s *clientSuite) TestServicesStart(c *C) { 227 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 228 w.Header().Set("Content-Type", "application/json") 229 w.WriteHeader(200) 230 w.Write([]byte(`{ 231 "type": "sync", 232 "result": null 233 }`)) 234 }) 235 startFailures, stopFailures, err := s.cli.ServicesStart(context.Background(), []string{"service1.service", "service2.service"}) 236 c.Assert(err, IsNil) 237 c.Check(startFailures, HasLen, 0) 238 c.Check(stopFailures, HasLen, 0) 239 } 240 241 func (s *clientSuite) TestServicesStartFailure(c *C) { 242 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 243 w.Header().Set("Content-Type", "application/json") 244 w.WriteHeader(500) 245 w.Write([]byte(`{ 246 "type": "error", 247 "result": { 248 "kind": "service-control", 249 "message": "failed to start services", 250 "value": { 251 "start-errors": { 252 "service2.service": "failed to start" 253 } 254 } 255 } 256 }`)) 257 }) 258 startFailures, stopFailures, err := s.cli.ServicesStart(context.Background(), []string{"service1.service", "service2.service"}) 259 c.Assert(err, ErrorMatches, "failed to start services") 260 c.Check(startFailures, HasLen, 2) 261 c.Check(stopFailures, HasLen, 0) 262 263 failure0 := startFailures[0] 264 failure1 := startFailures[1] 265 if failure0.Uid == 1000 { 266 failure0, failure1 = failure1, failure0 267 } 268 c.Check(failure0, DeepEquals, client.ServiceFailure{ 269 Uid: 42, 270 Service: "service2.service", 271 Error: "failed to start", 272 }) 273 c.Check(failure1, DeepEquals, client.ServiceFailure{ 274 Uid: 1000, 275 Service: "service2.service", 276 Error: "failed to start", 277 }) 278 } 279 280 func (s *clientSuite) TestServicesStartOneAgentFailure(c *C) { 281 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 282 w.Header().Set("Content-Type", "application/json") 283 284 // Only produce failure from one agent 285 if r.Host != "42" { 286 w.WriteHeader(200) 287 w.Write([]byte(`{"type": "sync","result": null}`)) 288 return 289 } 290 291 w.WriteHeader(500) 292 w.Write([]byte(`{ 293 "type": "error", 294 "result": { 295 "kind": "service-control", 296 "message": "failed to start services", 297 "value": { 298 "start-errors": { 299 "service2.service": "failed to start" 300 } 301 } 302 } 303 }`)) 304 }) 305 startFailures, stopFailures, err := s.cli.ServicesStart(context.Background(), []string{"service1.service", "service2.service"}) 306 c.Assert(err, ErrorMatches, "failed to start services") 307 c.Check(startFailures, DeepEquals, []client.ServiceFailure{ 308 { 309 Uid: 42, 310 Service: "service2.service", 311 Error: "failed to start", 312 }, 313 }) 314 c.Check(stopFailures, HasLen, 0) 315 } 316 317 func (s *clientSuite) TestServicesStartBadErrors(c *C) { 318 errorValue := "null" 319 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 320 w.Header().Set("Content-Type", "application/json") 321 322 // Only produce failure from one agent 323 if r.Host != "42" { 324 w.WriteHeader(200) 325 w.Write([]byte(`{"type": "sync","result": null}`)) 326 return 327 } 328 329 w.WriteHeader(500) 330 w.Write([]byte(fmt.Sprintf(`{ 331 "type": "error", 332 "result": { 333 "kind": "service-control", 334 "message": "failed to stop services", 335 "value": %s 336 } 337 }`, errorValue))) 338 }) 339 340 // Error value is not a map 341 errorValue = "[]" 342 startFailures, stopFailures, err := s.cli.ServicesStart(context.Background(), []string{"service1.service"}) 343 c.Check(err, ErrorMatches, "failed to stop services") 344 c.Check(startFailures, HasLen, 0) 345 c.Check(stopFailures, HasLen, 0) 346 347 // Error value is a map, but missing start-errors/stop-errors keys 348 errorValue = "{}" 349 startFailures, stopFailures, err = s.cli.ServicesStart(context.Background(), []string{"service1.service"}) 350 c.Check(err, ErrorMatches, "failed to stop services") 351 c.Check(startFailures, HasLen, 0) 352 c.Check(stopFailures, HasLen, 0) 353 354 // start-errors/stop-errors are not maps 355 errorValue = `{ 356 "start-errors": [], 357 "stop-errors": 42 358 }` 359 startFailures, stopFailures, err = s.cli.ServicesStart(context.Background(), []string{"service1.service"}) 360 c.Check(err, ErrorMatches, "failed to stop services") 361 c.Check(startFailures, HasLen, 0) 362 c.Check(stopFailures, HasLen, 0) 363 364 // start-error/stop-error values are not strings 365 errorValue = `{ 366 "start-errors": { 367 "service1.service": 42 368 }, 369 "stop-errors": { 370 "service1.service": {} 371 } 372 }` 373 startFailures, stopFailures, err = s.cli.ServicesStart(context.Background(), []string{"service1.service"}) 374 c.Check(err, ErrorMatches, "failed to stop services") 375 c.Check(startFailures, HasLen, 0) 376 c.Check(stopFailures, HasLen, 0) 377 378 // When some valid service failures are mixed in with bad 379 // ones, report the valid failure along with the error 380 // message. 381 errorValue = `{ 382 "start-errors": { 383 "service1.service": "failure one", 384 "service2.service": 42 385 } 386 }` 387 startFailures, stopFailures, err = s.cli.ServicesStart(context.Background(), []string{"service1.service"}) 388 c.Check(err, ErrorMatches, "failed to stop services") 389 c.Check(startFailures, DeepEquals, []client.ServiceFailure{ 390 { 391 Uid: 42, 392 Service: "service1.service", 393 Error: "failure one", 394 }, 395 }) 396 c.Check(stopFailures, HasLen, 0) 397 } 398 399 func (s *clientSuite) TestServicesStop(c *C) { 400 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 401 w.Header().Set("Content-Type", "application/json") 402 w.WriteHeader(200) 403 w.Write([]byte(`{ 404 "type": "sync", 405 "result": null 406 }`)) 407 }) 408 failures, err := s.cli.ServicesStop(context.Background(), []string{"service1.service", "service2.service"}) 409 c.Assert(err, IsNil) 410 c.Check(failures, HasLen, 0) 411 } 412 413 func (s *clientSuite) TestServicesStopFailure(c *C) { 414 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 415 w.Header().Set("Content-Type", "application/json") 416 w.WriteHeader(500) 417 w.Write([]byte(`{ 418 "type": "error", 419 "result": { 420 "kind": "service-control", 421 "message": "failed to stop services", 422 "value": { 423 "stop-errors": { 424 "service2.service": "failed to stop" 425 } 426 } 427 } 428 }`)) 429 }) 430 failures, err := s.cli.ServicesStop(context.Background(), []string{"service1.service", "service2.service"}) 431 c.Assert(err, ErrorMatches, "failed to stop services") 432 c.Check(failures, HasLen, 2) 433 failure0 := failures[0] 434 failure1 := failures[1] 435 if failure0.Uid == 1000 { 436 failure0, failure1 = failure1, failure0 437 } 438 c.Check(failure0, DeepEquals, client.ServiceFailure{ 439 Uid: 42, 440 Service: "service2.service", 441 Error: "failed to stop", 442 }) 443 c.Check(failure1, DeepEquals, client.ServiceFailure{ 444 Uid: 1000, 445 Service: "service2.service", 446 Error: "failed to stop", 447 }) 448 } 449 450 func (s *clientSuite) TestPendingRefreshNotification(c *C) { 451 s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 452 c.Assert(r.URL.Path, Equals, "/v1/notifications/pending-refresh") 453 w.Header().Set("Content-Type", "application/json") 454 w.WriteHeader(200) 455 w.Write([]byte(`{"type": "sync"}`)) 456 }) 457 err := s.cli.PendingRefreshNotification(context.Background(), &client.PendingSnapRefreshInfo{}) 458 c.Assert(err, IsNil) 459 }