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