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  }