github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/daemon_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2015 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 21 22 import ( 23 "fmt" 24 25 "bytes" 26 "encoding/json" 27 "errors" 28 "io/ioutil" 29 "net" 30 "net/http" 31 "net/http/httptest" 32 "os" 33 "path/filepath" 34 "sync" 35 "syscall" 36 "testing" 37 "time" 38 39 "github.com/gorilla/mux" 40 "gopkg.in/check.v1" 41 42 "github.com/snapcore/snapd/client" 43 "github.com/snapcore/snapd/dirs" 44 "github.com/snapcore/snapd/logger" 45 "github.com/snapcore/snapd/osutil" 46 "github.com/snapcore/snapd/overlord/auth" 47 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 48 "github.com/snapcore/snapd/overlord/ifacestate" 49 "github.com/snapcore/snapd/overlord/patch" 50 "github.com/snapcore/snapd/overlord/snapstate" 51 "github.com/snapcore/snapd/overlord/standby" 52 "github.com/snapcore/snapd/overlord/state" 53 "github.com/snapcore/snapd/polkit" 54 "github.com/snapcore/snapd/snap" 55 "github.com/snapcore/snapd/store" 56 "github.com/snapcore/snapd/systemd" 57 "github.com/snapcore/snapd/testutil" 58 ) 59 60 // Hook up check.v1 into the "go test" runner 61 func Test(t *testing.T) { check.TestingT(t) } 62 63 type daemonSuite struct { 64 testutil.BaseTest 65 66 authorized bool 67 err error 68 lastPolkitFlags polkit.CheckFlags 69 notified []string 70 } 71 72 var _ = check.Suite(&daemonSuite{}) 73 74 func (s *daemonSuite) checkAuthorization(pid int32, uid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { 75 s.lastPolkitFlags = flags 76 return s.authorized, s.err 77 } 78 79 func (s *daemonSuite) SetUpTest(c *check.C) { 80 s.BaseTest.SetUpTest(c) 81 82 dirs.SetRootDir(c.MkDir()) 83 s.AddCleanup(osutil.MockMountInfo("")) 84 85 err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755) 86 c.Assert(err, check.IsNil) 87 systemdSdNotify = func(notif string) error { 88 s.notified = append(s.notified, notif) 89 return nil 90 } 91 s.notified = nil 92 polkitCheckAuthorization = s.checkAuthorization 93 s.AddCleanup(ifacestate.MockSecurityBackends(nil)) 94 } 95 96 func (s *daemonSuite) TearDownTest(c *check.C) { 97 systemdSdNotify = systemd.SdNotify 98 dirs.SetRootDir("") 99 s.authorized = false 100 s.err = nil 101 logger.SetLogger(logger.NullLogger) 102 103 s.BaseTest.TearDownTest(c) 104 } 105 106 func (s *daemonSuite) TearDownSuite(c *check.C) { 107 polkitCheckAuthorization = polkit.CheckAuthorization 108 } 109 110 // build a new daemon, with only a little of Init(), suitable for the tests 111 func newTestDaemon(c *check.C) *Daemon { 112 d, err := New() 113 c.Assert(err, check.IsNil) 114 d.addRoutes() 115 116 // don't actually try to talk to the store on snapstate.Ensure 117 // needs doing after the call to devicestate.Manager (which 118 // happens in daemon.New via overlord.New) 119 snapstate.CanAutoRefresh = nil 120 121 return d 122 } 123 124 // a Response suitable for testing 125 type mockHandler struct { 126 cmd *Command 127 lastMethod string 128 } 129 130 func (mck *mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 131 mck.lastMethod = r.Method 132 } 133 134 func (s *daemonSuite) TestCommandMethodDispatch(c *check.C) { 135 fakeUserAgent := "some-agent-talking-to-snapd/1.0" 136 137 cmd := &Command{d: newTestDaemon(c)} 138 mck := &mockHandler{cmd: cmd} 139 rf := func(innerCmd *Command, req *http.Request, user *auth.UserState) Response { 140 c.Assert(cmd, check.Equals, innerCmd) 141 c.Check(store.ClientUserAgent(req.Context()), check.Equals, fakeUserAgent) 142 return mck 143 } 144 cmd.GET = rf 145 cmd.PUT = rf 146 cmd.POST = rf 147 148 for _, method := range []string{"GET", "POST", "PUT"} { 149 req, err := http.NewRequest(method, "", nil) 150 req.Header.Add("User-Agent", fakeUserAgent) 151 c.Assert(err, check.IsNil) 152 153 rec := httptest.NewRecorder() 154 cmd.ServeHTTP(rec, req) 155 c.Check(rec.Code, check.Equals, 401, check.Commentf(method)) 156 157 rec = httptest.NewRecorder() 158 req.RemoteAddr = "pid=100;uid=0;socket=;" 159 160 cmd.ServeHTTP(rec, req) 161 c.Check(mck.lastMethod, check.Equals, method) 162 c.Check(rec.Code, check.Equals, 200) 163 } 164 165 req, err := http.NewRequest("POTATO", "", nil) 166 c.Assert(err, check.IsNil) 167 req.RemoteAddr = "pid=100;uid=0;socket=;" 168 169 rec := httptest.NewRecorder() 170 cmd.ServeHTTP(rec, req) 171 c.Check(rec.Code, check.Equals, 405) 172 } 173 174 func (s *daemonSuite) TestCommandRestartingState(c *check.C) { 175 d := newTestDaemon(c) 176 177 cmd := &Command{d: d} 178 cmd.GET = func(*Command, *http.Request, *auth.UserState) Response { 179 return SyncResponse(nil, nil) 180 } 181 req, err := http.NewRequest("GET", "", nil) 182 c.Assert(err, check.IsNil) 183 req.RemoteAddr = "pid=100;uid=0;socket=;" 184 185 rec := httptest.NewRecorder() 186 cmd.ServeHTTP(rec, req) 187 c.Check(rec.Code, check.Equals, 200) 188 var rst struct { 189 Maintenance *errorResult `json:"maintenance"` 190 } 191 err = json.Unmarshal(rec.Body.Bytes(), &rst) 192 c.Assert(err, check.IsNil) 193 c.Check(rst.Maintenance, check.IsNil) 194 195 state.MockRestarting(d.overlord.State(), state.RestartSystem) 196 rec = httptest.NewRecorder() 197 cmd.ServeHTTP(rec, req) 198 c.Check(rec.Code, check.Equals, 200) 199 err = json.Unmarshal(rec.Body.Bytes(), &rst) 200 c.Assert(err, check.IsNil) 201 c.Check(rst.Maintenance, check.DeepEquals, &errorResult{ 202 Kind: client.ErrorKindSystemRestart, 203 Message: "system is restarting", 204 }) 205 206 state.MockRestarting(d.overlord.State(), state.RestartDaemon) 207 rec = httptest.NewRecorder() 208 cmd.ServeHTTP(rec, req) 209 c.Check(rec.Code, check.Equals, 200) 210 err = json.Unmarshal(rec.Body.Bytes(), &rst) 211 c.Assert(err, check.IsNil) 212 c.Check(rst.Maintenance, check.DeepEquals, &errorResult{ 213 Kind: client.ErrorKindDaemonRestart, 214 Message: "daemon is restarting", 215 }) 216 } 217 218 func (s *daemonSuite) TestMaintenanceJsonDeletedOnStart(c *check.C) { 219 // write a maintenance.json file that has that the system is restarting 220 maintErr := &errorResult{ 221 Kind: client.ErrorKindDaemonRestart, 222 Message: systemRestartMsg, 223 } 224 225 b, err := json.Marshal(maintErr) 226 c.Assert(err, check.IsNil) 227 c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapdMaintenanceFile), 0755), check.IsNil) 228 c.Assert(ioutil.WriteFile(dirs.SnapdMaintenanceFile, b, 0644), check.IsNil) 229 230 d := newTestDaemon(c) 231 makeDaemonListeners(c, d) 232 233 // after starting, maintenance.json should be removed 234 d.Start() 235 c.Assert(dirs.SnapdMaintenanceFile, testutil.FileAbsent) 236 d.Stop(nil) 237 } 238 239 func (s *daemonSuite) TestFillsWarnings(c *check.C) { 240 d := newTestDaemon(c) 241 242 cmd := &Command{d: d} 243 cmd.GET = func(*Command, *http.Request, *auth.UserState) Response { 244 return SyncResponse(nil, nil) 245 } 246 req, err := http.NewRequest("GET", "", nil) 247 c.Assert(err, check.IsNil) 248 req.RemoteAddr = "pid=100;uid=0;socket=;" 249 250 rec := httptest.NewRecorder() 251 cmd.ServeHTTP(rec, req) 252 c.Check(rec.Code, check.Equals, 200) 253 var rst struct { 254 WarningTimestamp *time.Time `json:"warning-timestamp,omitempty"` 255 WarningCount int `json:"warning-count,omitempty"` 256 } 257 err = json.Unmarshal(rec.Body.Bytes(), &rst) 258 c.Assert(err, check.IsNil) 259 c.Check(rst.WarningCount, check.Equals, 0) 260 c.Check(rst.WarningTimestamp, check.IsNil) 261 262 st := d.overlord.State() 263 st.Lock() 264 st.Warnf("hello world") 265 st.Unlock() 266 267 rec = httptest.NewRecorder() 268 cmd.ServeHTTP(rec, req) 269 c.Check(rec.Code, check.Equals, 200) 270 err = json.Unmarshal(rec.Body.Bytes(), &rst) 271 c.Assert(err, check.IsNil) 272 c.Check(rst.WarningCount, check.Equals, 1) 273 c.Check(rst.WarningTimestamp, check.NotNil) 274 } 275 276 func (s *daemonSuite) TestGuestAccess(c *check.C) { 277 get := &http.Request{Method: "GET"} 278 put := &http.Request{Method: "PUT"} 279 pst := &http.Request{Method: "POST"} 280 del := &http.Request{Method: "DELETE"} 281 282 cmd := &Command{d: newTestDaemon(c)} 283 c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized) 284 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 285 c.Check(cmd.canAccess(pst, nil), check.Equals, accessUnauthorized) 286 c.Check(cmd.canAccess(del, nil), check.Equals, accessUnauthorized) 287 288 cmd = &Command{d: newTestDaemon(c), RootOnly: true} 289 c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized) 290 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 291 c.Check(cmd.canAccess(pst, nil), check.Equals, accessUnauthorized) 292 c.Check(cmd.canAccess(del, nil), check.Equals, accessUnauthorized) 293 294 cmd = &Command{d: newTestDaemon(c), UserOK: true} 295 c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized) 296 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 297 c.Check(cmd.canAccess(pst, nil), check.Equals, accessUnauthorized) 298 c.Check(cmd.canAccess(del, nil), check.Equals, accessUnauthorized) 299 300 cmd = &Command{d: newTestDaemon(c), GuestOK: true} 301 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 302 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 303 c.Check(cmd.canAccess(pst, nil), check.Equals, accessUnauthorized) 304 c.Check(cmd.canAccess(del, nil), check.Equals, accessUnauthorized) 305 } 306 307 func (s *daemonSuite) TestSnapctlAccessSnapOKWithUser(c *check.C) { 308 remoteAddr := "pid=100;uid=1000;socket=" + dirs.SnapSocket + ";" 309 get := &http.Request{Method: "GET", RemoteAddr: remoteAddr} 310 put := &http.Request{Method: "PUT", RemoteAddr: remoteAddr} 311 pst := &http.Request{Method: "POST", RemoteAddr: remoteAddr} 312 del := &http.Request{Method: "DELETE", RemoteAddr: remoteAddr} 313 314 cmd := &Command{d: newTestDaemon(c), SnapOK: true} 315 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 316 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 317 c.Check(cmd.canAccess(pst, nil), check.Equals, accessOK) 318 c.Check(cmd.canAccess(del, nil), check.Equals, accessOK) 319 } 320 321 func (s *daemonSuite) TestSnapctlAccessSnapOKWithRoot(c *check.C) { 322 remoteAddr := "pid=100;uid=0;socket=" + dirs.SnapSocket + ";" 323 get := &http.Request{Method: "GET", RemoteAddr: remoteAddr} 324 put := &http.Request{Method: "PUT", RemoteAddr: remoteAddr} 325 pst := &http.Request{Method: "POST", RemoteAddr: remoteAddr} 326 del := &http.Request{Method: "DELETE", RemoteAddr: remoteAddr} 327 328 cmd := &Command{d: newTestDaemon(c), SnapOK: true} 329 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 330 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 331 c.Check(cmd.canAccess(pst, nil), check.Equals, accessOK) 332 c.Check(cmd.canAccess(del, nil), check.Equals, accessOK) 333 } 334 335 func (s *daemonSuite) TestUserAccess(c *check.C) { 336 get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=42;socket=;"} 337 put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;socket=;"} 338 339 cmd := &Command{d: newTestDaemon(c)} 340 c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized) 341 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 342 343 cmd = &Command{d: newTestDaemon(c), RootOnly: true} 344 c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized) 345 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 346 347 cmd = &Command{d: newTestDaemon(c), UserOK: true} 348 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 349 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 350 351 cmd = &Command{d: newTestDaemon(c), GuestOK: true} 352 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 353 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 354 355 // Since this request has a RemoteAddr, it must be coming from the snapd 356 // socket instead of the snap one. In that case, SnapOK should have no 357 // bearing on the default behavior, which is to deny access. 358 cmd = &Command{d: newTestDaemon(c), SnapOK: true} 359 c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized) 360 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 361 } 362 363 func (s *daemonSuite) TestLoggedInUserAccess(c *check.C) { 364 user := &auth.UserState{} 365 get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=42;socket=;"} 366 put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;socket=;"} 367 368 cmd := &Command{d: newTestDaemon(c)} 369 c.Check(cmd.canAccess(get, user), check.Equals, accessOK) 370 c.Check(cmd.canAccess(put, user), check.Equals, accessOK) 371 372 cmd = &Command{d: newTestDaemon(c), RootOnly: true} 373 c.Check(cmd.canAccess(get, user), check.Equals, accessUnauthorized) 374 c.Check(cmd.canAccess(put, user), check.Equals, accessUnauthorized) 375 376 cmd = &Command{d: newTestDaemon(c), UserOK: true} 377 c.Check(cmd.canAccess(get, user), check.Equals, accessOK) 378 c.Check(cmd.canAccess(put, user), check.Equals, accessOK) 379 380 cmd = &Command{d: newTestDaemon(c), GuestOK: true} 381 c.Check(cmd.canAccess(get, user), check.Equals, accessOK) 382 c.Check(cmd.canAccess(put, user), check.Equals, accessOK) 383 384 cmd = &Command{d: newTestDaemon(c), SnapOK: true} 385 c.Check(cmd.canAccess(get, user), check.Equals, accessOK) 386 c.Check(cmd.canAccess(put, user), check.Equals, accessOK) 387 } 388 389 func (s *daemonSuite) TestSuperAccess(c *check.C) { 390 get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=0;socket=;"} 391 put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=0;socket=;"} 392 393 cmd := &Command{d: newTestDaemon(c)} 394 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 395 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 396 397 cmd = &Command{d: newTestDaemon(c), RootOnly: true} 398 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 399 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 400 401 cmd = &Command{d: newTestDaemon(c), UserOK: true} 402 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 403 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 404 405 cmd = &Command{d: newTestDaemon(c), GuestOK: true} 406 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 407 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 408 409 cmd = &Command{d: newTestDaemon(c), SnapOK: true} 410 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 411 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 412 } 413 414 func (s *daemonSuite) TestPolkitAccess(c *check.C) { 415 put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;socket=;"} 416 cmd := &Command{d: newTestDaemon(c), PolkitOK: "polkit.action"} 417 418 // polkit says user is not authorised 419 s.authorized = false 420 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 421 422 // polkit grants authorisation 423 s.authorized = true 424 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 425 426 // an error occurs communicating with polkit 427 s.err = errors.New("error") 428 c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) 429 430 // if the user dismisses the auth request, forbid access 431 s.err = polkit.ErrDismissed 432 c.Check(cmd.canAccess(put, nil), check.Equals, accessCancelled) 433 } 434 435 func (s *daemonSuite) TestPolkitAccessForGet(c *check.C) { 436 get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=42;socket=;"} 437 cmd := &Command{d: newTestDaemon(c), PolkitOK: "polkit.action"} 438 439 // polkit can grant authorisation for GET requests 440 s.authorized = true 441 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 442 443 // for UserOK commands, polkit is not consulted 444 cmd.UserOK = true 445 polkitCheckAuthorization = func(pid int32, uid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { 446 panic("polkit.CheckAuthorization called") 447 } 448 c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) 449 } 450 451 func (s *daemonSuite) TestPolkitInteractivity(c *check.C) { 452 put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;socket=;", Header: make(http.Header)} 453 cmd := &Command{d: newTestDaemon(c), PolkitOK: "polkit.action"} 454 s.authorized = true 455 456 var logbuf bytes.Buffer 457 log, err := logger.New(&logbuf, logger.DefaultFlags) 458 c.Assert(err, check.IsNil) 459 logger.SetLogger(log) 460 461 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 462 c.Check(s.lastPolkitFlags, check.Equals, polkit.CheckNone) 463 c.Check(logbuf.String(), check.Equals, "") 464 465 put.Header.Set(client.AllowInteractionHeader, "true") 466 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 467 c.Check(s.lastPolkitFlags, check.Equals, polkit.CheckAllowInteraction) 468 c.Check(logbuf.String(), check.Equals, "") 469 470 // bad values are logged and treated as false 471 put.Header.Set(client.AllowInteractionHeader, "garbage") 472 c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) 473 c.Check(s.lastPolkitFlags, check.Equals, polkit.CheckNone) 474 c.Check(logbuf.String(), testutil.Contains, "error parsing X-Allow-Interaction header:") 475 } 476 477 func (s *daemonSuite) TestAddRoutes(c *check.C) { 478 d := newTestDaemon(c) 479 480 expected := make([]string, len(api)) 481 for i, v := range api { 482 if v.PathPrefix != "" { 483 expected[i] = v.PathPrefix 484 continue 485 } 486 expected[i] = v.Path 487 } 488 489 got := make([]string, 0, len(api)) 490 c.Assert(d.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { 491 got = append(got, route.GetName()) 492 return nil 493 }), check.IsNil) 494 495 c.Check(got, check.DeepEquals, expected) // this'll stop being true if routes are added that aren't commands (e.g. for the favicon) 496 497 // XXX: still waiting to know how to check d.router.NotFoundHandler has been set to NotFound 498 // the old test relied on undefined behaviour: 499 // c.Check(fmt.Sprintf("%p", d.router.NotFoundHandler), check.Equals, fmt.Sprintf("%p", NotFound)) 500 } 501 502 type witnessAcceptListener struct { 503 net.Listener 504 505 accept chan struct{} 506 accept1 bool 507 508 idempotClose sync.Once 509 closeErr error 510 closed chan struct{} 511 } 512 513 func (l *witnessAcceptListener) Accept() (net.Conn, error) { 514 if !l.accept1 { 515 l.accept1 = true 516 close(l.accept) 517 } 518 return l.Listener.Accept() 519 } 520 521 func (l *witnessAcceptListener) Close() error { 522 l.idempotClose.Do(func() { 523 l.closeErr = l.Listener.Close() 524 if l.closed != nil { 525 close(l.closed) 526 } 527 }) 528 return l.closeErr 529 } 530 531 func (s *daemonSuite) markSeeded(d *Daemon) { 532 st := d.overlord.State() 533 st.Lock() 534 st.Set("seeded", true) 535 devicestatetest.SetDevice(st, &auth.DeviceState{ 536 Brand: "canonical", 537 Model: "pc", 538 Serial: "serialserial", 539 }) 540 st.Unlock() 541 } 542 543 func (s *daemonSuite) TestStartStop(c *check.C) { 544 d := newTestDaemon(c) 545 // mark as already seeded 546 s.markSeeded(d) 547 // and pretend we have snaps 548 st := d.overlord.State() 549 st.Lock() 550 snapstate.Set(st, "core", &snapstate.SnapState{ 551 Active: true, 552 Sequence: []*snap.SideInfo{ 553 {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, 554 }, 555 Current: snap.R(1), 556 }) 557 st.Unlock() 558 // 1 snap => extended timeout 30s + 5s 559 const extendedTimeoutUSec = "EXTEND_TIMEOUT_USEC=35000000" 560 561 l1, err := net.Listen("tcp", "127.0.0.1:0") 562 c.Assert(err, check.IsNil) 563 l2, err := net.Listen("tcp", "127.0.0.1:0") 564 c.Assert(err, check.IsNil) 565 566 snapdAccept := make(chan struct{}) 567 d.snapdListener = &witnessAcceptListener{Listener: l1, accept: snapdAccept} 568 569 snapAccept := make(chan struct{}) 570 d.snapListener = &witnessAcceptListener{Listener: l2, accept: snapAccept} 571 572 c.Assert(d.Start(), check.IsNil) 573 574 c.Check(s.notified, check.DeepEquals, []string{extendedTimeoutUSec, "READY=1"}) 575 576 snapdDone := make(chan struct{}) 577 go func() { 578 select { 579 case <-snapdAccept: 580 case <-time.After(2 * time.Second): 581 c.Fatal("snapd accept was not called") 582 } 583 close(snapdDone) 584 }() 585 586 snapDone := make(chan struct{}) 587 go func() { 588 select { 589 case <-snapAccept: 590 case <-time.After(2 * time.Second): 591 c.Fatal("snapd accept was not called") 592 } 593 close(snapDone) 594 }() 595 596 <-snapdDone 597 <-snapDone 598 599 err = d.Stop(nil) 600 c.Check(err, check.IsNil) 601 602 c.Check(s.notified, check.DeepEquals, []string{extendedTimeoutUSec, "READY=1", "STOPPING=1"}) 603 } 604 605 func (s *daemonSuite) TestRestartWiring(c *check.C) { 606 d := newTestDaemon(c) 607 // mark as already seeded 608 s.markSeeded(d) 609 610 l, err := net.Listen("tcp", "127.0.0.1:0") 611 c.Assert(err, check.IsNil) 612 613 snapdAccept := make(chan struct{}) 614 d.snapdListener = &witnessAcceptListener{Listener: l, accept: snapdAccept} 615 616 snapAccept := make(chan struct{}) 617 d.snapListener = &witnessAcceptListener{Listener: l, accept: snapAccept} 618 619 c.Assert(d.Start(), check.IsNil) 620 stoppedYet := false 621 defer func() { 622 if !stoppedYet { 623 d.Stop(nil) 624 } 625 }() 626 627 snapdDone := make(chan struct{}) 628 go func() { 629 select { 630 case <-snapdAccept: 631 case <-time.After(2 * time.Second): 632 c.Fatal("snapd accept was not called") 633 } 634 close(snapdDone) 635 }() 636 637 snapDone := make(chan struct{}) 638 go func() { 639 select { 640 case <-snapAccept: 641 case <-time.After(2 * time.Second): 642 c.Fatal("snap accept was not called") 643 } 644 close(snapDone) 645 }() 646 647 <-snapdDone 648 <-snapDone 649 650 d.overlord.State().RequestRestart(state.RestartDaemon) 651 652 select { 653 case <-d.Dying(): 654 case <-time.After(2 * time.Second): 655 c.Fatal("RequestRestart -> overlord -> Kill chain didn't work") 656 } 657 658 d.Stop(nil) 659 stoppedYet = true 660 661 c.Assert(s.notified, check.DeepEquals, []string{"EXTEND_TIMEOUT_USEC=30000000", "READY=1", "STOPPING=1"}) 662 } 663 664 func (s *daemonSuite) TestGracefulStop(c *check.C) { 665 d := newTestDaemon(c) 666 667 responding := make(chan struct{}) 668 doRespond := make(chan bool, 1) 669 670 d.router.HandleFunc("/endp", func(w http.ResponseWriter, r *http.Request) { 671 close(responding) 672 if <-doRespond { 673 w.Write([]byte("OKOK")) 674 } else { 675 w.Write([]byte("Gone")) 676 } 677 }) 678 679 // mark as already seeded 680 s.markSeeded(d) 681 // and pretend we have snaps 682 st := d.overlord.State() 683 st.Lock() 684 snapstate.Set(st, "core", &snapstate.SnapState{ 685 Active: true, 686 Sequence: []*snap.SideInfo{ 687 {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, 688 }, 689 Current: snap.R(1), 690 }) 691 st.Unlock() 692 693 snapdL, err := net.Listen("tcp", "127.0.0.1:0") 694 c.Assert(err, check.IsNil) 695 696 snapL, err := net.Listen("tcp", "127.0.0.1:0") 697 c.Assert(err, check.IsNil) 698 699 snapdAccept := make(chan struct{}) 700 snapdClosed := make(chan struct{}) 701 d.snapdListener = &witnessAcceptListener{Listener: snapdL, accept: snapdAccept, closed: snapdClosed} 702 703 snapAccept := make(chan struct{}) 704 d.snapListener = &witnessAcceptListener{Listener: snapL, accept: snapAccept} 705 706 c.Assert(d.Start(), check.IsNil) 707 708 snapdAccepting := make(chan struct{}) 709 go func() { 710 select { 711 case <-snapdAccept: 712 case <-time.After(2 * time.Second): 713 c.Fatal("snapd accept was not called") 714 } 715 close(snapdAccepting) 716 }() 717 718 snapAccepting := make(chan struct{}) 719 go func() { 720 select { 721 case <-snapAccept: 722 case <-time.After(2 * time.Second): 723 c.Fatal("snapd accept was not called") 724 } 725 close(snapAccepting) 726 }() 727 728 <-snapdAccepting 729 <-snapAccepting 730 731 alright := make(chan struct{}) 732 733 go func() { 734 res, err := http.Get(fmt.Sprintf("http://%s/endp", snapdL.Addr())) 735 c.Assert(err, check.IsNil) 736 c.Check(res.StatusCode, check.Equals, 200) 737 body, err := ioutil.ReadAll(res.Body) 738 res.Body.Close() 739 c.Assert(err, check.IsNil) 740 c.Check(string(body), check.Equals, "OKOK") 741 close(alright) 742 }() 743 go func() { 744 <-snapdClosed 745 time.Sleep(200 * time.Millisecond) 746 doRespond <- true 747 }() 748 749 <-responding 750 err = d.Stop(nil) 751 doRespond <- false 752 c.Check(err, check.IsNil) 753 754 select { 755 case <-alright: 756 case <-time.After(2 * time.Second): 757 c.Fatal("never got proper response") 758 } 759 } 760 761 func (s *daemonSuite) TestGracefulStopHasLimits(c *check.C) { 762 d := newTestDaemon(c) 763 764 // mark as already seeded 765 s.markSeeded(d) 766 767 restore := MockShutdownTimeout(time.Second) 768 defer restore() 769 770 responding := make(chan struct{}) 771 doRespond := make(chan bool, 1) 772 773 d.router.HandleFunc("/endp", func(w http.ResponseWriter, r *http.Request) { 774 close(responding) 775 if <-doRespond { 776 for { 777 // write in a loop to keep the handler running 778 if _, err := w.Write([]byte("OKOK")); err != nil { 779 break 780 } 781 time.Sleep(50 * time.Millisecond) 782 } 783 } else { 784 w.Write([]byte("Gone")) 785 } 786 }) 787 788 snapdL, err := net.Listen("tcp", "127.0.0.1:0") 789 c.Assert(err, check.IsNil) 790 791 snapL, err := net.Listen("tcp", "127.0.0.1:0") 792 c.Assert(err, check.IsNil) 793 794 snapdAccept := make(chan struct{}) 795 snapdClosed := make(chan struct{}) 796 d.snapdListener = &witnessAcceptListener{Listener: snapdL, accept: snapdAccept, closed: snapdClosed} 797 798 snapAccept := make(chan struct{}) 799 d.snapListener = &witnessAcceptListener{Listener: snapL, accept: snapAccept} 800 801 c.Assert(d.Start(), check.IsNil) 802 803 snapdAccepting := make(chan struct{}) 804 go func() { 805 select { 806 case <-snapdAccept: 807 case <-time.After(2 * time.Second): 808 c.Fatal("snapd accept was not called") 809 } 810 close(snapdAccepting) 811 }() 812 813 snapAccepting := make(chan struct{}) 814 go func() { 815 select { 816 case <-snapAccept: 817 case <-time.After(2 * time.Second): 818 c.Fatal("snapd accept was not called") 819 } 820 close(snapAccepting) 821 }() 822 823 <-snapdAccepting 824 <-snapAccepting 825 826 clientErr := make(chan error) 827 828 go func() { 829 _, err := http.Get(fmt.Sprintf("http://%s/endp", snapdL.Addr())) 830 c.Assert(err, check.NotNil) 831 clientErr <- err 832 close(clientErr) 833 }() 834 go func() { 835 <-snapdClosed 836 time.Sleep(200 * time.Millisecond) 837 doRespond <- true 838 }() 839 840 <-responding 841 err = d.Stop(nil) 842 doRespond <- false 843 c.Check(err, check.IsNil) 844 845 select { 846 case cErr := <-clientErr: 847 c.Check(cErr, check.ErrorMatches, ".*: EOF") 848 case <-time.After(5 * time.Second): 849 c.Fatal("never got proper response") 850 } 851 } 852 853 func (s *daemonSuite) testRestartSystemWiring(c *check.C, restartKind state.RestartType) { 854 d := newTestDaemon(c) 855 // mark as already seeded 856 s.markSeeded(d) 857 858 l, err := net.Listen("tcp", "127.0.0.1:0") 859 c.Assert(err, check.IsNil) 860 861 snapdAccept := make(chan struct{}) 862 d.snapdListener = &witnessAcceptListener{Listener: l, accept: snapdAccept} 863 864 snapAccept := make(chan struct{}) 865 d.snapListener = &witnessAcceptListener{Listener: l, accept: snapAccept} 866 867 c.Assert(d.Start(), check.IsNil) 868 defer d.Stop(nil) 869 870 st := d.overlord.State() 871 872 snapdDone := make(chan struct{}) 873 go func() { 874 select { 875 case <-snapdAccept: 876 case <-time.After(2 * time.Second): 877 c.Fatal("snapd accept was not called") 878 } 879 close(snapdDone) 880 }() 881 882 snapDone := make(chan struct{}) 883 go func() { 884 select { 885 case <-snapAccept: 886 case <-time.After(2 * time.Second): 887 c.Fatal("snap accept was not called") 888 } 889 close(snapDone) 890 }() 891 892 <-snapdDone 893 <-snapDone 894 895 oldRebootNoticeWait := rebootNoticeWait 896 oldRebootWaitTimeout := rebootWaitTimeout 897 defer func() { 898 reboot = rebootImpl 899 rebootNoticeWait = oldRebootNoticeWait 900 rebootWaitTimeout = oldRebootWaitTimeout 901 }() 902 rebootWaitTimeout = 100 * time.Millisecond 903 rebootNoticeWait = 150 * time.Millisecond 904 905 var delays []time.Duration 906 reboot = func(d time.Duration) error { 907 delays = append(delays, d) 908 return nil 909 } 910 911 st.Lock() 912 st.RequestRestart(restartKind) 913 st.Unlock() 914 915 defer func() { 916 d.mu.Lock() 917 d.requestedRestart = state.RestartUnset 918 d.mu.Unlock() 919 }() 920 921 select { 922 case <-d.Dying(): 923 case <-time.After(2 * time.Second): 924 c.Fatal("RequestRestart -> overlord -> Kill chain didn't work") 925 } 926 927 d.mu.Lock() 928 rs := d.requestedRestart 929 d.mu.Unlock() 930 931 c.Check(rs, check.Equals, restartKind) 932 933 c.Check(delays, check.HasLen, 1) 934 c.Check(delays[0], check.DeepEquals, rebootWaitTimeout) 935 936 now := time.Now() 937 938 err = d.Stop(nil) 939 940 c.Check(err, check.ErrorMatches, "expected reboot did not happen") 941 942 c.Check(delays, check.HasLen, 2) 943 if restartKind == state.RestartSystem { 944 c.Check(delays[1], check.DeepEquals, 1*time.Minute) 945 } else if restartKind == state.RestartSystemNow { 946 c.Check(delays[1], check.DeepEquals, time.Duration(0)) 947 } 948 949 // we are not stopping, we wait for the reboot instead 950 c.Check(s.notified, check.DeepEquals, []string{"EXTEND_TIMEOUT_USEC=30000000", "READY=1"}) 951 952 st.Lock() 953 defer st.Unlock() 954 var rebootAt time.Time 955 err = st.Get("daemon-system-restart-at", &rebootAt) 956 c.Assert(err, check.IsNil) 957 if restartKind == state.RestartSystem { 958 approxAt := now.Add(time.Minute) 959 c.Check(rebootAt.After(approxAt) || rebootAt.Equal(approxAt), check.Equals, true) 960 } else if restartKind == state.RestartSystemNow { 961 // should be good enough 962 c.Check(rebootAt.Before(now.Add(10*time.Second)), check.Equals, true) 963 } 964 965 // finally check that maintenance.json was written appropriate for this 966 // restart reason 967 b, err := ioutil.ReadFile(dirs.SnapdMaintenanceFile) 968 c.Assert(err, check.IsNil) 969 970 maintErr := &errorResult{} 971 c.Assert(json.Unmarshal(b, maintErr), check.IsNil) 972 973 exp := maintenanceForRestartType(restartKind) 974 c.Assert(maintErr, check.DeepEquals, exp) 975 } 976 977 func (s *daemonSuite) TestRestartSystemGracefulWiring(c *check.C) { 978 s.testRestartSystemWiring(c, state.RestartSystem) 979 } 980 981 func (s *daemonSuite) TestRestartSystemImmediateWiring(c *check.C) { 982 s.testRestartSystemWiring(c, state.RestartSystemNow) 983 } 984 985 func (s *daemonSuite) TestRebootHelper(c *check.C) { 986 cmd := testutil.MockCommand(c, "shutdown", "") 987 defer cmd.Restore() 988 989 tests := []struct { 990 delay time.Duration 991 delayArg string 992 }{ 993 {-1, "+0"}, 994 {0, "+0"}, 995 {time.Minute, "+1"}, 996 {10 * time.Minute, "+10"}, 997 {30 * time.Second, "+0"}, 998 } 999 1000 for _, t := range tests { 1001 err := reboot(t.delay) 1002 c.Assert(err, check.IsNil) 1003 c.Check(cmd.Calls(), check.DeepEquals, [][]string{ 1004 {"shutdown", "-r", t.delayArg, "reboot scheduled to update the system"}, 1005 }) 1006 1007 cmd.ForgetCalls() 1008 } 1009 } 1010 1011 func makeDaemonListeners(c *check.C, d *Daemon) { 1012 snapdL, err := net.Listen("tcp", "127.0.0.1:0") 1013 c.Assert(err, check.IsNil) 1014 1015 snapL, err := net.Listen("tcp", "127.0.0.1:0") 1016 c.Assert(err, check.IsNil) 1017 1018 snapdAccept := make(chan struct{}) 1019 snapdClosed := make(chan struct{}) 1020 d.snapdListener = &witnessAcceptListener{Listener: snapdL, accept: snapdAccept, closed: snapdClosed} 1021 1022 snapAccept := make(chan struct{}) 1023 d.snapListener = &witnessAcceptListener{Listener: snapL, accept: snapAccept} 1024 } 1025 1026 // This test tests that when the snapd calls a restart of the system 1027 // a sigterm (from e.g. systemd) is handled when it arrives before 1028 // stop is fully done. 1029 func (s *daemonSuite) TestRestartShutdownWithSigtermInBetween(c *check.C) { 1030 oldRebootNoticeWait := rebootNoticeWait 1031 defer func() { 1032 rebootNoticeWait = oldRebootNoticeWait 1033 }() 1034 rebootNoticeWait = 150 * time.Millisecond 1035 1036 cmd := testutil.MockCommand(c, "shutdown", "") 1037 defer cmd.Restore() 1038 1039 d := newTestDaemon(c) 1040 makeDaemonListeners(c, d) 1041 s.markSeeded(d) 1042 1043 c.Assert(d.Start(), check.IsNil) 1044 st := d.overlord.State() 1045 1046 st.Lock() 1047 st.RequestRestart(state.RestartSystem) 1048 st.Unlock() 1049 1050 ch := make(chan os.Signal, 2) 1051 ch <- syscall.SIGTERM 1052 // stop will check if we got a sigterm in between (which we did) 1053 err := d.Stop(ch) 1054 c.Assert(err, check.IsNil) 1055 } 1056 1057 // This test tests that when there is a shutdown we close the sigterm 1058 // handler so that systemd can kill snapd. 1059 func (s *daemonSuite) TestRestartShutdown(c *check.C) { 1060 oldRebootNoticeWait := rebootNoticeWait 1061 oldRebootWaitTimeout := rebootWaitTimeout 1062 defer func() { 1063 rebootNoticeWait = oldRebootNoticeWait 1064 rebootWaitTimeout = oldRebootWaitTimeout 1065 }() 1066 rebootWaitTimeout = 100 * time.Millisecond 1067 rebootNoticeWait = 150 * time.Millisecond 1068 1069 cmd := testutil.MockCommand(c, "shutdown", "") 1070 defer cmd.Restore() 1071 1072 d := newTestDaemon(c) 1073 makeDaemonListeners(c, d) 1074 s.markSeeded(d) 1075 1076 c.Assert(d.Start(), check.IsNil) 1077 st := d.overlord.State() 1078 1079 st.Lock() 1080 st.RequestRestart(state.RestartSystem) 1081 st.Unlock() 1082 1083 sigCh := make(chan os.Signal, 2) 1084 // stop (this will timeout but that's not relevant for this test) 1085 d.Stop(sigCh) 1086 1087 // ensure that the sigCh got closed as part of the stop 1088 _, chOpen := <-sigCh 1089 c.Assert(chOpen, check.Equals, false) 1090 } 1091 1092 func (s *daemonSuite) TestRestartExpectedRebootDidNotHappen(c *check.C) { 1093 curBootID, err := osutil.BootID() 1094 c.Assert(err, check.IsNil) 1095 1096 fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"patch-sublevel":%d,"some":"data","refresh-privacy-key":"0123456789ABCDEF","system-restart-from-boot-id":%q,"daemon-system-restart-at":"%s"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0,"last-lane-id":0}`, patch.Level, patch.Sublevel, curBootID, time.Now().UTC().Format(time.RFC3339))) 1097 err = ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600) 1098 c.Assert(err, check.IsNil) 1099 1100 oldRebootNoticeWait := rebootNoticeWait 1101 oldRebootRetryWaitTimeout := rebootRetryWaitTimeout 1102 defer func() { 1103 rebootNoticeWait = oldRebootNoticeWait 1104 rebootRetryWaitTimeout = oldRebootRetryWaitTimeout 1105 }() 1106 rebootRetryWaitTimeout = 100 * time.Millisecond 1107 rebootNoticeWait = 150 * time.Millisecond 1108 1109 cmd := testutil.MockCommand(c, "shutdown", "") 1110 defer cmd.Restore() 1111 1112 d := newTestDaemon(c) 1113 c.Check(d.overlord, check.IsNil) 1114 c.Check(d.expectedRebootDidNotHappen, check.Equals, true) 1115 1116 var n int 1117 d.state.Lock() 1118 err = d.state.Get("daemon-system-restart-tentative", &n) 1119 d.state.Unlock() 1120 c.Check(err, check.IsNil) 1121 c.Check(n, check.Equals, 1) 1122 1123 c.Assert(d.Start(), check.IsNil) 1124 1125 c.Check(s.notified, check.DeepEquals, []string{"READY=1"}) 1126 1127 select { 1128 case <-d.Dying(): 1129 case <-time.After(2 * time.Second): 1130 c.Fatal("expected reboot not happening should proceed to try to shutdown again") 1131 } 1132 1133 sigCh := make(chan os.Signal, 2) 1134 // stop (this will timeout but thats not relevant for this test) 1135 d.Stop(sigCh) 1136 1137 // an immediate shutdown was scheduled again 1138 c.Check(cmd.Calls(), check.DeepEquals, [][]string{ 1139 {"shutdown", "-r", "+0", "reboot scheduled to update the system"}, 1140 }) 1141 } 1142 1143 func (s *daemonSuite) TestRestartExpectedRebootOK(c *check.C) { 1144 fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"patch-sublevel":%d,"some":"data","refresh-privacy-key":"0123456789ABCDEF","system-restart-from-boot-id":%q,"daemon-system-restart-at":"%s"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0,"last-lane-id":0}`, patch.Level, patch.Sublevel, "boot-id-0", time.Now().UTC().Format(time.RFC3339))) 1145 err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600) 1146 c.Assert(err, check.IsNil) 1147 1148 cmd := testutil.MockCommand(c, "shutdown", "") 1149 defer cmd.Restore() 1150 1151 d := newTestDaemon(c) 1152 c.Assert(d.overlord, check.NotNil) 1153 1154 st := d.overlord.State() 1155 st.Lock() 1156 defer st.Unlock() 1157 var v interface{} 1158 // these were cleared 1159 c.Check(st.Get("daemon-system-restart-at", &v), check.Equals, state.ErrNoState) 1160 c.Check(st.Get("system-restart-from-boot-id", &v), check.Equals, state.ErrNoState) 1161 } 1162 1163 func (s *daemonSuite) TestRestartExpectedRebootGiveUp(c *check.C) { 1164 // we give up trying to restart the system after 3 retry tentatives 1165 curBootID, err := osutil.BootID() 1166 c.Assert(err, check.IsNil) 1167 1168 fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"patch-sublevel":%d,"some":"data","refresh-privacy-key":"0123456789ABCDEF","system-restart-from-boot-id":%q,"daemon-system-restart-at":"%s","daemon-system-restart-tentative":3},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0,"last-lane-id":0}`, patch.Level, patch.Sublevel, curBootID, time.Now().UTC().Format(time.RFC3339))) 1169 err = ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600) 1170 c.Assert(err, check.IsNil) 1171 1172 cmd := testutil.MockCommand(c, "shutdown", "") 1173 defer cmd.Restore() 1174 1175 d := newTestDaemon(c) 1176 c.Assert(d.overlord, check.NotNil) 1177 1178 st := d.overlord.State() 1179 st.Lock() 1180 defer st.Unlock() 1181 var v interface{} 1182 // these were cleared 1183 c.Check(st.Get("daemon-system-restart-at", &v), check.Equals, state.ErrNoState) 1184 c.Check(st.Get("system-restart-from-boot-id", &v), check.Equals, state.ErrNoState) 1185 c.Check(st.Get("daemon-system-restart-tentative", &v), check.Equals, state.ErrNoState) 1186 } 1187 1188 func (s *daemonSuite) TestRestartIntoSocketModeNoNewChanges(c *check.C) { 1189 restore := standby.MockStandbyWait(5 * time.Millisecond) 1190 defer restore() 1191 1192 d := newTestDaemon(c) 1193 makeDaemonListeners(c, d) 1194 1195 // mark as already seeded, we also have no snaps so this will 1196 // go into socket activation mode 1197 s.markSeeded(d) 1198 1199 c.Assert(d.Start(), check.IsNil) 1200 // pretend some ensure happened 1201 for i := 0; i < 5; i++ { 1202 c.Check(d.overlord.StateEngine().Ensure(), check.IsNil) 1203 time.Sleep(5 * time.Millisecond) 1204 } 1205 1206 select { 1207 case <-d.Dying(): 1208 // exit the loop 1209 case <-time.After(15 * time.Second): 1210 c.Errorf("daemon did not stop after 15s") 1211 } 1212 err := d.Stop(nil) 1213 c.Check(err, check.Equals, ErrRestartSocket) 1214 c.Check(d.restartSocket, check.Equals, true) 1215 } 1216 1217 func (s *daemonSuite) TestRestartIntoSocketModePendingChanges(c *check.C) { 1218 restore := standby.MockStandbyWait(5 * time.Millisecond) 1219 defer restore() 1220 1221 d := newTestDaemon(c) 1222 makeDaemonListeners(c, d) 1223 1224 // mark as already seeded, we also have no snaps so this will 1225 // go into socket activation mode 1226 s.markSeeded(d) 1227 st := d.overlord.State() 1228 1229 c.Assert(d.Start(), check.IsNil) 1230 // pretend some ensure happened 1231 for i := 0; i < 5; i++ { 1232 c.Check(d.overlord.StateEngine().Ensure(), check.IsNil) 1233 time.Sleep(5 * time.Millisecond) 1234 } 1235 1236 select { 1237 case <-d.Dying(): 1238 // Pretend we got change while shutting down, this can 1239 // happen when e.g. the user requested a `snap install 1240 // foo` at the same time as the code in the overlord 1241 // checked that it can go into socket activated 1242 // mode. I.e. the daemon was processing the request 1243 // but no change was generated at the time yet. 1244 st.Lock() 1245 chg := st.NewChange("fake-install", "fake install some snap") 1246 chg.AddTask(st.NewTask("fake-install-task", "fake install task")) 1247 chgStatus := chg.Status() 1248 st.Unlock() 1249 // ensure our change is valid and ready 1250 c.Check(chgStatus, check.Equals, state.DoStatus) 1251 case <-time.After(5 * time.Second): 1252 c.Errorf("daemon did not stop after 5s") 1253 } 1254 // when the daemon got a pending change it just restarts 1255 err := d.Stop(nil) 1256 c.Check(err, check.IsNil) 1257 c.Check(d.restartSocket, check.Equals, false) 1258 } 1259 1260 func (s *daemonSuite) TestConnTrackerCanShutdown(c *check.C) { 1261 ct := &connTracker{conns: make(map[net.Conn]struct{})} 1262 c.Check(ct.CanStandby(), check.Equals, true) 1263 1264 con := &net.IPConn{} 1265 ct.trackConn(con, http.StateActive) 1266 c.Check(ct.CanStandby(), check.Equals, false) 1267 1268 ct.trackConn(con, http.StateIdle) 1269 c.Check(ct.CanStandby(), check.Equals, true) 1270 } 1271 1272 func doTestReq(c *check.C, cmd *Command, mth string) *httptest.ResponseRecorder { 1273 req, err := http.NewRequest(mth, "", nil) 1274 c.Assert(err, check.IsNil) 1275 req.RemoteAddr = "pid=100;uid=0;socket=;" 1276 rec := httptest.NewRecorder() 1277 cmd.ServeHTTP(rec, req) 1278 return rec 1279 } 1280 1281 func (s *daemonSuite) TestDegradedModeReply(c *check.C) { 1282 d := newTestDaemon(c) 1283 cmd := &Command{d: d} 1284 cmd.GET = func(*Command, *http.Request, *auth.UserState) Response { 1285 return SyncResponse(nil, nil) 1286 } 1287 cmd.POST = func(*Command, *http.Request, *auth.UserState) Response { 1288 return SyncResponse(nil, nil) 1289 } 1290 1291 // pretend we are in degraded mode 1292 d.SetDegradedMode(fmt.Errorf("foo error")) 1293 1294 // GET is ok even in degraded mode 1295 rec := doTestReq(c, cmd, "GET") 1296 c.Check(rec.Code, check.Equals, 200) 1297 // POST is not allowed 1298 rec = doTestReq(c, cmd, "POST") 1299 c.Check(rec.Code, check.Equals, 500) 1300 // verify we get the error 1301 var v struct{ Result errorResult } 1302 c.Assert(json.NewDecoder(rec.Body).Decode(&v), check.IsNil) 1303 c.Check(v.Result.Message, check.Equals, "foo error") 1304 1305 // clean degraded mode 1306 d.SetDegradedMode(nil) 1307 rec = doTestReq(c, cmd, "POST") 1308 c.Check(rec.Code, check.Equals, 200) 1309 }