github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_routine_console_conf_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 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 main_test 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "io/ioutil" 26 "net/http" 27 "os" 28 "path/filepath" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/client" 33 snap "github.com/snapcore/snapd/cmd/snap" 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 func (s *SnapSuite) TestRoutineConsoleConfStartTrivialCase(c *C) { 39 n := 0 40 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 41 n++ 42 switch n { 43 case 1: 44 c.Check(r.Method, Equals, "POST") 45 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 46 47 fmt.Fprintf(w, `{"type":"sync", "status-code": 200, "result": {}}`) 48 default: 49 c.Errorf("unexpected request %v", n) 50 } 51 }) 52 53 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 54 c.Assert(err, IsNil) 55 c.Check(s.Stdout(), Equals, "") 56 c.Check(s.Stderr(), Equals, "") 57 c.Assert(n, Equals, 1) 58 } 59 60 func (s *SnapSuite) TestRoutineConsoleConfStartInconsistentAPIResponseError(c *C) { 61 n := 0 62 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 63 n++ 64 switch n { 65 case 1: 66 c.Check(r.Method, Equals, "POST") 67 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 68 69 // return just refresh changes but no snap ids 70 fmt.Fprintf(w, `{ 71 "type":"sync", 72 "status-code": 200, 73 "result": { 74 "active-auto-refreshes": ["1"] 75 } 76 }`) 77 default: 78 c.Errorf("unexpected request %v", n) 79 } 80 }) 81 82 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 83 c.Assert(err, ErrorMatches, `internal error: returned changes .* but no snap names`) 84 c.Check(s.Stdout(), Equals, "") 85 c.Check(s.Stderr(), Equals, "") 86 c.Assert(n, Equals, 1) 87 88 } 89 90 func (s *SnapSuite) TestRoutineConsoleConfStartNonMaintenanceErrorReturned(c *C) { 91 n := 0 92 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 93 n++ 94 switch n { 95 case 1: 96 c.Check(r.Method, Equals, "POST") 97 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 98 99 // return internal server error 100 fmt.Fprintf(w, `{ 101 "type":"error", 102 "status-code": 500, 103 "result": { 104 "message": "broken server" 105 } 106 }`) 107 default: 108 c.Errorf("unexpected request %v", n) 109 } 110 }) 111 112 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 113 c.Assert(err, ErrorMatches, "broken server") 114 c.Check(s.Stdout(), Equals, "") 115 c.Check(s.Stderr(), Equals, "") 116 c.Assert(n, Equals, 1) 117 } 118 119 func (s *SnapSuite) TestRoutineConsoleConfStartSingleSnap(c *C) { 120 // make the command hit the API as fast as possible for testing 121 r := snap.MockSnapdAPIInterval(0) 122 defer r() 123 124 n := 0 125 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 126 n++ 127 switch n { 128 // first 4 times we hit the API there is a snap refresh ongoing 129 case 1, 2, 3, 4: 130 c.Check(r.Method, Equals, "POST") 131 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 132 133 // return just refresh changes but no snap ids 134 fmt.Fprintf(w, `{ 135 "type":"sync", 136 "status-code": 200, 137 "result": { 138 "active-auto-refreshes": ["1"], 139 "active-auto-refresh-snaps": ["pc-kernel"] 140 } 141 }`) 142 // 5th time we return nothing as we are done 143 case 5: 144 c.Check(r.Method, Equals, "POST") 145 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 146 147 fmt.Fprintf(w, `{"type":"sync", "status-code": 200, "result": {}}`) 148 149 default: 150 c.Errorf("unexpected request %v", n) 151 } 152 }) 153 154 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 155 c.Assert(err, IsNil) 156 c.Check(s.Stdout(), Equals, "") 157 c.Check(s.Stderr(), Equals, "Snaps (pc-kernel) are refreshing, please wait...\n") 158 c.Assert(n, Equals, 5) 159 } 160 161 func (s *SnapSuite) TestRoutineConsoleConfStartTwoSnaps(c *C) { 162 // make the command hit the API as fast as possible for testing 163 r := snap.MockSnapdAPIInterval(0) 164 defer r() 165 166 n := 0 167 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 168 n++ 169 switch n { 170 // first 4 times we hit the API there is a snap refresh ongoing 171 case 1, 2, 3, 4: 172 c.Check(r.Method, Equals, "POST") 173 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 174 175 // return just refresh changes but no snap ids 176 fmt.Fprintf(w, `{ 177 "type":"sync", 178 "status-code": 200, 179 "result": { 180 "active-auto-refreshes": ["1"], 181 "active-auto-refresh-snaps": ["pc-kernel","core20"] 182 } 183 }`) 184 // 5th time we return nothing as we are done 185 case 5: 186 c.Check(r.Method, Equals, "POST") 187 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 188 189 fmt.Fprintf(w, `{"type":"sync", "status-code": 200, "result": {}}`) 190 191 default: 192 c.Errorf("unexpected request %v", n) 193 } 194 }) 195 196 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 197 c.Assert(err, IsNil) 198 c.Check(s.Stdout(), Equals, "") 199 c.Check(s.Stderr(), Equals, "Snaps (core20 and pc-kernel) are refreshing, please wait...\n") 200 c.Assert(n, Equals, 5) 201 } 202 203 func (s *SnapSuite) TestRoutineConsoleConfStartMultipleSnaps(c *C) { 204 // make the command hit the API as fast as possible for testing 205 r := snap.MockSnapdAPIInterval(0) 206 defer r() 207 208 n := 0 209 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 210 n++ 211 switch n { 212 // first 4 times we hit the API there are snap refreshes ongoing 213 case 1, 2, 3, 4: 214 c.Check(r.Method, Equals, "POST") 215 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 216 217 fmt.Fprintf(w, `{ 218 "type":"sync", 219 "status-code": 200, 220 "result": { 221 "active-auto-refreshes": ["1"], 222 "active-auto-refresh-snaps": ["pc-kernel","snapd","core20","pc"] 223 } 224 }`) 225 // 5th time we return nothing as we are done 226 case 5: 227 c.Check(r.Method, Equals, "POST") 228 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 229 230 fmt.Fprintf(w, `{"type":"sync", "status-code": 200, "result": {}}`) 231 232 default: 233 c.Errorf("unexpected request %v", n) 234 } 235 }) 236 237 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 238 c.Assert(err, IsNil) 239 c.Check(s.Stdout(), Equals, "") 240 c.Check(s.Stderr(), Equals, "Snaps (core20, pc, pc-kernel, and snapd) are refreshing, please wait...\n") 241 c.Assert(n, Equals, 5) 242 } 243 244 func (s *SnapSuite) TestRoutineConsoleConfStartSnapdRefreshMaintenanceJSON(c *C) { 245 // make the command hit the API as fast as possible for testing 246 r := snap.MockSnapdAPIInterval(0) 247 defer r() 248 249 // write a maintenance.json before any requests and then the first request 250 // should fail and see the maintenance.json and then subsequent operations 251 // succeed 252 maintErr := client.Error{ 253 Kind: client.ErrorKindDaemonRestart, 254 Message: "daemon is restarting", 255 } 256 b, err := json.Marshal(&maintErr) 257 c.Assert(err, IsNil) 258 err = os.MkdirAll(filepath.Dir(dirs.SnapdMaintenanceFile), 0755) 259 c.Assert(err, IsNil) 260 err = ioutil.WriteFile(dirs.SnapdMaintenanceFile, b, 0644) 261 c.Assert(err, IsNil) 262 263 n := 0 264 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 265 n++ 266 switch n { 267 // 1st time we don't respond at all to simulate what happens if the user 268 // triggers console-conf to start after snapd has shut down for a 269 // refresh 270 case 1: 271 c.Check(r.Method, Equals, "POST") 272 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 273 274 // 2nd time we hit the API, return an in-progress refresh 275 case 2: 276 c.Check(r.Method, Equals, "POST") 277 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 278 279 fmt.Fprintf(w, `{ 280 "type":"sync", 281 "status-code": 200, 282 "result": { 283 "active-auto-refreshes": ["1"], 284 "active-auto-refresh-snaps": ["snapd"] 285 } 286 }`) 287 // 3rd time we are actually done 288 case 3: 289 c.Check(r.Method, Equals, "POST") 290 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 291 292 fmt.Fprintf(w, `{"type":"sync", "status-code": 200, "result": {}}`) 293 294 default: 295 c.Errorf("unexpected request %v", n) 296 } 297 }) 298 299 _, err = snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 300 c.Assert(err, IsNil) 301 c.Check(s.Stdout(), Equals, "") 302 c.Check(s.Stderr(), testutil.Contains, "Snapd is reloading, please wait...\n") 303 c.Check(s.Stderr(), testutil.Contains, "Snaps (snapd) are refreshing, please wait...\n") 304 c.Assert(n, Equals, 3) 305 } 306 307 func (s *SnapSuite) TestRoutineConsoleConfStartSystemRebootMaintenanceJSON(c *C) { 308 // make the command hit the API as fast as possible for testing 309 r := snap.MockSnapdAPIInterval(0) 310 defer r() 311 312 r = snap.MockSnapdWaitForFullSystemReboot(0) 313 defer r() 314 315 // write a maintenance.json before any requests and then the first request 316 // should fail and see the maintenance.json and then subsequent operations 317 // succeed 318 maintErr := client.Error{ 319 Kind: client.ErrorKindSystemRestart, 320 Message: "system is restarting", 321 } 322 b, err := json.Marshal(&maintErr) 323 c.Assert(err, IsNil) 324 err = os.MkdirAll(filepath.Dir(dirs.SnapdMaintenanceFile), 0755) 325 c.Assert(err, IsNil) 326 err = ioutil.WriteFile(dirs.SnapdMaintenanceFile, b, 0644) 327 c.Assert(err, IsNil) 328 329 n := 0 330 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 331 n++ 332 switch n { 333 // 1st time we don't respond at all to simulate what happens if the user 334 // triggers console-conf to start after snapd has shut down for a 335 // refresh 336 case 1: 337 c.Check(r.Method, Equals, "POST") 338 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 339 340 default: 341 c.Errorf("unexpected request %v", n) 342 } 343 }) 344 345 _, err = snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 346 c.Assert(err, ErrorMatches, "system didn't reboot after 10 minutes even though snapd daemon is in maintenance") 347 c.Check(s.Stdout(), Equals, "") 348 c.Check(s.Stderr(), testutil.Contains, "System is rebooting, please wait for reboot...\n") 349 c.Assert(n, Equals, 1) 350 } 351 352 func (s *SnapSuite) TestRoutineConsoleConfStartSnapdRefreshRestart(c *C) { 353 // make the command hit the API as fast as possible for testing 354 r := snap.MockSnapdAPIInterval(0) 355 defer r() 356 357 n := 0 358 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 359 n++ 360 switch n { 361 362 // 1st time we hit the API there is a snapd snap refresh ongoing 363 case 1: 364 c.Check(r.Method, Equals, "POST") 365 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 366 367 fmt.Fprintf(w, `{ 368 "type":"sync", 369 "status-code": 200, 370 "result": { 371 "active-auto-refreshes": ["1"], 372 "active-auto-refresh-snaps": ["snapd"] 373 } 374 }`) 375 376 // 2nd time we hit the API, set maintenance in the response 377 case 2: 378 c.Check(r.Method, Equals, "POST") 379 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 380 381 fmt.Fprintf(w, `{ 382 "type":"sync", 383 "status-code": 200, 384 "result": { 385 "active-auto-refreshes": ["1"], 386 "active-auto-refresh-snaps": ["snapd"] 387 }, 388 "maintenance": { 389 "kind": "daemon-restart", 390 "message": "daemon is restarting" 391 } 392 }`) 393 394 // 3rd time we return nothing as if we are down for maintenance 395 case 3: 396 c.Check(r.Method, Equals, "POST") 397 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 398 399 // 4th time we resume responding, but still in progress 400 case 4: 401 c.Check(r.Method, Equals, "POST") 402 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 403 404 fmt.Fprintf(w, `{ 405 "type":"sync", 406 "status-code": 200, 407 "result": { 408 "active-auto-refreshes": ["1"], 409 "active-auto-refresh-snaps": ["snapd"] 410 } 411 }`) 412 413 // 5th time we are actually done 414 case 5: 415 c.Check(r.Method, Equals, "POST") 416 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 417 418 fmt.Fprintf(w, `{"type":"sync", "status-code": 200, "result": {}}`) 419 420 default: 421 c.Errorf("unexpected request %v", n) 422 } 423 }) 424 425 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 426 c.Assert(err, IsNil) 427 c.Check(s.Stdout(), Equals, "") 428 c.Check(s.Stderr(), testutil.Contains, "Snapd is reloading, please wait...\n") 429 c.Check(s.Stderr(), testutil.Contains, "Snaps (snapd) are refreshing, please wait...\n") 430 c.Assert(n, Equals, 5) 431 } 432 433 func (s *SnapSuite) TestRoutineConsoleConfStartKernelRefreshReboot(c *C) { 434 // make the command hit the API as fast as possible for testing 435 r := snap.MockSnapdAPIInterval(0) 436 defer r() 437 r = snap.MockSnapdWaitForFullSystemReboot(0) 438 defer r() 439 440 n := 0 441 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 442 n++ 443 switch n { 444 445 // 1st time we hit the API there is a snapd snap refresh ongoing 446 case 1: 447 c.Check(r.Method, Equals, "POST") 448 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 449 450 fmt.Fprintf(w, `{ 451 "type":"sync", 452 "status-code": 200, 453 "result": { 454 "active-auto-refreshes": ["1"], 455 "active-auto-refresh-snaps": ["pc-kernel"] 456 } 457 }`) 458 459 // 2nd time we hit the API, set maintenance in the response, but still 460 // give a valid response (so that it reads the maintenance) 461 case 2: 462 c.Check(r.Method, Equals, "POST") 463 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 464 465 fmt.Fprintf(w, `{ 466 "type":"sync", 467 "status-code": 200, 468 "result": { 469 "active-auto-refreshes": ["1"], 470 "active-auto-refresh-snaps": ["pc-kernel"] 471 }, 472 "maintenance": { 473 "kind": "system-restart", 474 "message": "system is restarting" 475 } 476 }`) 477 478 // 3rd time we hit the API, we need to not return anything so that the 479 // client will inspect the error and see there is a maintenance error 480 case 3: 481 c.Check(r.Method, Equals, "POST") 482 c.Check(r.URL.Path, Equals, "/v2/internal/console-conf-start") 483 default: 484 c.Errorf("unexpected %s request (number %d) to %s", r.Method, n, r.URL.Path) 485 } 486 }) 487 488 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "console-conf-start"}) 489 // this is the internal error, which we will hit immediately for testing, 490 // in a real scenario a reboot would happen OOTB from the snap client 491 c.Assert(err, ErrorMatches, "system didn't reboot after 10 minutes even though snapd daemon is in maintenance") 492 c.Check(s.Stdout(), Equals, "") 493 c.Check(s.Stderr(), testutil.Contains, "System is rebooting, please wait for reboot...\n") 494 c.Check(s.Stderr(), testutil.Contains, "Snaps (pc-kernel) are refreshing, please wait...\n") 495 c.Assert(n, Equals, 3) 496 }