github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/cmd/snap-failure/cmd_snapd_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	failure "github.com/snapcore/snapd/cmd/snap-failure"
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/osutil"
    34  	"github.com/snapcore/snapd/snap"
    35  	"github.com/snapcore/snapd/testutil"
    36  )
    37  
    38  func (r *failureSuite) TestRun(c *C) {
    39  	origArgs := os.Args
    40  	defer func() { os.Args = origArgs }()
    41  	os.Args = []string{"snap-failure", "snapd"}
    42  	err := failure.Run()
    43  	c.Check(err, IsNil)
    44  	c.Check(r.Stderr(), HasLen, 0)
    45  }
    46  
    47  func writeSeqFile(c *C, name string, current snap.Revision, seq []*snap.SideInfo) {
    48  	seqPath := filepath.Join(dirs.SnapSeqDir, name+".json")
    49  
    50  	err := os.MkdirAll(dirs.SnapSeqDir, 0755)
    51  	c.Assert(err, IsNil)
    52  
    53  	b, err := json.Marshal(&struct {
    54  		Sequence []*snap.SideInfo `json:"sequence"`
    55  		Current  string           `json:"current"`
    56  	}{
    57  		Sequence: seq,
    58  		Current:  current.String(),
    59  	})
    60  	c.Assert(err, IsNil)
    61  
    62  	err = ioutil.WriteFile(seqPath, b, 0644)
    63  	c.Assert(err, IsNil)
    64  }
    65  
    66  func (r *failureSuite) TestCallPrevSnapdFromSnap(c *C) {
    67  	origArgs := os.Args
    68  	defer func() { os.Args = origArgs }()
    69  
    70  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
    71  		{Revision: snap.R(99)},
    72  		{Revision: snap.R(100)},
    73  		{Revision: snap.R(123)},
    74  	})
    75  
    76  	// mock snapd command from 'previous' revision
    77  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
    78  		`test "$SNAPD_REVERT_TO_REV" = "100"`)
    79  	defer snapdCmd.Restore()
    80  
    81  	systemctlCmd := testutil.MockCommand(c, "systemctl", "")
    82  	defer systemctlCmd.Restore()
    83  
    84  	os.Args = []string{"snap-failure", "snapd"}
    85  	err := failure.Run()
    86  	c.Check(err, IsNil)
    87  	c.Check(r.Stderr(), HasLen, 0)
    88  
    89  	c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
    90  		{"snapd"},
    91  	})
    92  	c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
    93  		{"systemctl", "stop", "snapd.socket"},
    94  		{"systemctl", "is-failed", "snapd.socket", "snapd.service"},
    95  		{"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
    96  		{"systemctl", "restart", "snapd.socket"},
    97  	})
    98  }
    99  
   100  func (r *failureSuite) TestCallPrevSnapdFromSnapRestartSnapdFallback(c *C) {
   101  	defer failure.MockWaitTimes(1*time.Millisecond, 1*time.Millisecond)()
   102  
   103  	origArgs := os.Args
   104  	defer func() { os.Args = origArgs }()
   105  
   106  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
   107  		{Revision: snap.R(99)},
   108  		{Revision: snap.R(100)},
   109  		{Revision: snap.R(123)},
   110  	})
   111  
   112  	// mock snapd command from 'previous' revision
   113  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
   114  		`test "$SNAPD_REVERT_TO_REV" = "100"`)
   115  	defer snapdCmd.Restore()
   116  
   117  	systemctlCmd := testutil.MockCommand(c, "systemctl", `
   118  if [ "$1" = restart ] && [ "$2" == snapd.socket ] ; then
   119      exit 1
   120  fi
   121  `)
   122  	defer systemctlCmd.Restore()
   123  
   124  	os.Args = []string{"snap-failure", "snapd"}
   125  	err := failure.Run()
   126  	c.Check(err, IsNil)
   127  	c.Check(r.Stderr(), HasLen, 0)
   128  
   129  	c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
   130  		{"snapd"},
   131  	})
   132  	c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
   133  		{"systemctl", "stop", "snapd.socket"},
   134  		{"systemctl", "is-failed", "snapd.socket", "snapd.service"},
   135  		{"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
   136  		{"systemctl", "restart", "snapd.socket"},
   137  		{"systemctl", "restart", "snapd.service"},
   138  	})
   139  }
   140  
   141  func (r *failureSuite) TestCallPrevSnapdFromSnapBackToFullyActive(c *C) {
   142  	defer failure.MockWaitTimes(1*time.Millisecond, 0)()
   143  
   144  	origArgs := os.Args
   145  	defer func() { os.Args = origArgs }()
   146  
   147  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
   148  		{Revision: snap.R(99)},
   149  		{Revision: snap.R(100)},
   150  		{Revision: snap.R(123)},
   151  	})
   152  
   153  	// mock snapd command from 'previous' revision
   154  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
   155  		`test "$SNAPD_REVERT_TO_REV" = "100"`)
   156  	defer snapdCmd.Restore()
   157  
   158  	systemctlCmd := testutil.MockCommand(c, "systemctl", `
   159  if [ "$1" = is-failed ] ; then
   160      exit 1
   161  fi
   162  `)
   163  	defer systemctlCmd.Restore()
   164  
   165  	// mock the sockets re-appearing
   166  	err := os.MkdirAll(filepath.Dir(dirs.SnapdSocket), 0755)
   167  	c.Assert(err, IsNil)
   168  	err = ioutil.WriteFile(dirs.SnapdSocket, nil, 0755)
   169  	c.Assert(err, IsNil)
   170  	err = ioutil.WriteFile(dirs.SnapSocket, nil, 0755)
   171  	c.Assert(err, IsNil)
   172  
   173  	os.Args = []string{"snap-failure", "snapd"}
   174  	err = failure.Run()
   175  	c.Check(err, IsNil)
   176  	c.Check(r.Stderr(), HasLen, 0)
   177  
   178  	c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
   179  		{"snapd"},
   180  	})
   181  	c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
   182  		{"systemctl", "stop", "snapd.socket"},
   183  		{"systemctl", "is-failed", "snapd.socket", "snapd.service"},
   184  		{"systemctl", "is-active", "snapd.socket", "snapd.service"},
   185  	})
   186  }
   187  
   188  func (r *failureSuite) TestCallPrevSnapdFromSnapBackActiveNoSockets(c *C) {
   189  	defer failure.MockWaitTimes(1*time.Millisecond, 0)()
   190  
   191  	origArgs := os.Args
   192  	defer func() { os.Args = origArgs }()
   193  
   194  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
   195  		{Revision: snap.R(99)},
   196  		{Revision: snap.R(100)},
   197  		{Revision: snap.R(123)},
   198  	})
   199  
   200  	// mock snapd command from 'previous' revision
   201  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
   202  		`test "$SNAPD_REVERT_TO_REV" = "100"`)
   203  	defer snapdCmd.Restore()
   204  
   205  	systemctlCmd := testutil.MockCommand(c, "systemctl", `
   206  if [ "$1" = is-failed ] ; then
   207      exit 1
   208  fi
   209  `)
   210  	defer systemctlCmd.Restore()
   211  
   212  	// no sockets
   213  
   214  	os.Args = []string{"snap-failure", "snapd"}
   215  	err := failure.Run()
   216  	c.Check(err, IsNil)
   217  	c.Check(r.Stderr(), HasLen, 0)
   218  
   219  	c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
   220  		{"snapd"},
   221  	})
   222  	c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
   223  		{"systemctl", "stop", "snapd.socket"},
   224  		{"systemctl", "is-failed", "snapd.socket", "snapd.service"},
   225  		{"systemctl", "is-active", "snapd.socket", "snapd.service"},
   226  		{"systemctl", "is-active", "snapd.socket", "snapd.service"},
   227  		{"systemctl", "is-active", "snapd.socket", "snapd.service"},
   228  		{"systemctl", "is-active", "snapd.socket", "snapd.service"},
   229  		{"systemctl", "is-active", "snapd.socket", "snapd.service"},
   230  		{"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
   231  		{"systemctl", "restart", "snapd.socket"},
   232  	})
   233  }
   234  
   235  func (r *failureSuite) TestCallPrevSnapdFromCore(c *C) {
   236  	origArgs := os.Args
   237  	defer func() { os.Args = origArgs }()
   238  
   239  	// only one entry in sequence
   240  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
   241  		{Revision: snap.R(123)},
   242  	})
   243  
   244  	// mock snapd in the core snap
   245  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "core", "current", "/usr/lib/snapd/snapd"),
   246  		`test "$SNAPD_REVERT_TO_REV" = "0"`)
   247  	defer snapdCmd.Restore()
   248  
   249  	systemctlCmd := testutil.MockCommand(c, "systemctl", "")
   250  	defer systemctlCmd.Restore()
   251  
   252  	os.Args = []string{"snap-failure", "snapd"}
   253  	err := failure.Run()
   254  	c.Check(err, IsNil)
   255  	c.Check(r.Stderr(), HasLen, 0)
   256  
   257  	c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
   258  		{"snapd"},
   259  	})
   260  	c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
   261  		{"systemctl", "stop", "snapd.socket"},
   262  		{"systemctl", "is-failed", "snapd.socket", "snapd.service"},
   263  		{"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
   264  		{"systemctl", "restart", "snapd.socket"},
   265  	})
   266  }
   267  
   268  func (r *failureSuite) TestCallPrevSnapdFail(c *C) {
   269  	origArgs := os.Args
   270  	defer func() { os.Args = origArgs }()
   271  
   272  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
   273  		{Revision: snap.R(100)},
   274  		{Revision: snap.R(123)},
   275  	})
   276  
   277  	// mock snapd in the core snap
   278  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
   279  		`exit 2`)
   280  	defer snapdCmd.Restore()
   281  
   282  	systemctlCmd := testutil.MockCommand(c, "systemctl", "")
   283  	defer systemctlCmd.Restore()
   284  
   285  	os.Args = []string{"snap-failure", "snapd"}
   286  	err := failure.Run()
   287  	c.Check(err, ErrorMatches, "snapd failed: exit status 2")
   288  	c.Check(r.Stderr(), HasLen, 0)
   289  
   290  	c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
   291  		{"snapd"},
   292  	})
   293  	c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
   294  		{"systemctl", "stop", "snapd.socket"},
   295  	})
   296  }
   297  
   298  func (r *failureSuite) TestGarbageSeq(c *C) {
   299  	origArgs := os.Args
   300  	defer func() { os.Args = origArgs }()
   301  
   302  	seqPath := filepath.Join(dirs.SnapSeqDir, "snapd.json")
   303  	err := os.MkdirAll(dirs.SnapSeqDir, 0755)
   304  	c.Assert(err, IsNil)
   305  
   306  	err = ioutil.WriteFile(seqPath, []byte("this is garbage"), 0644)
   307  	c.Assert(err, IsNil)
   308  
   309  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
   310  		`exit 99`)
   311  	defer snapdCmd.Restore()
   312  
   313  	systemctlCmd := testutil.MockCommand(c, "systemctl", "exit 98")
   314  	defer systemctlCmd.Restore()
   315  
   316  	os.Args = []string{"snap-failure", "snapd"}
   317  	err = failure.Run()
   318  	c.Check(err, ErrorMatches, `cannot parse "snapd.json" sequence file: invalid .*`)
   319  	c.Check(r.Stderr(), HasLen, 0)
   320  
   321  	c.Check(snapdCmd.Calls(), HasLen, 0)
   322  	c.Check(systemctlCmd.Calls(), HasLen, 0)
   323  }
   324  
   325  func (r *failureSuite) TestBadSeq(c *C) {
   326  	origArgs := os.Args
   327  	defer func() { os.Args = origArgs }()
   328  
   329  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
   330  		{Revision: snap.R(100)},
   331  		// current not in sequence
   332  	})
   333  
   334  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"), "")
   335  	defer snapdCmd.Restore()
   336  	systemctlCmd := testutil.MockCommand(c, "systemctl", "")
   337  	defer systemctlCmd.Restore()
   338  
   339  	os.Args = []string{"snap-failure", "snapd"}
   340  	err := failure.Run()
   341  	c.Check(err, ErrorMatches, "internal error: current 123 not found in sequence: .*Revision:100.*")
   342  	c.Check(r.Stderr(), HasLen, 0)
   343  
   344  	c.Check(snapdCmd.Calls(), HasLen, 0)
   345  	c.Check(systemctlCmd.Calls(), HasLen, 0)
   346  }
   347  
   348  func (r *failureSuite) TestSnapdOutputPassthrough(c *C) {
   349  	origArgs := os.Args
   350  	defer func() { os.Args = origArgs }()
   351  
   352  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
   353  		{Revision: snap.R(100)},
   354  		{Revision: snap.R(123)},
   355  	})
   356  
   357  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"), `
   358  echo 'stderr: hello from snapd' >&2
   359  echo 'stdout: hello from snapd'
   360  exit 123
   361  `)
   362  	defer snapdCmd.Restore()
   363  	systemctlCmd := testutil.MockCommand(c, "systemctl", "")
   364  	defer systemctlCmd.Restore()
   365  
   366  	os.Args = []string{"snap-failure", "snapd"}
   367  	err := failure.Run()
   368  	c.Check(err, ErrorMatches, "snapd failed: exit status 123")
   369  	c.Check(r.Stderr(), Equals, "stderr: hello from snapd\n")
   370  	c.Check(r.Stdout(), Equals, "stdout: hello from snapd\n")
   371  
   372  	c.Check(snapdCmd.Calls(), HasLen, 1)
   373  	c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
   374  		{"systemctl", "stop", "snapd.socket"},
   375  	})
   376  }
   377  
   378  func (r *failureSuite) TestStickySnapdSocket(c *C) {
   379  	origArgs := os.Args
   380  	defer func() { os.Args = origArgs }()
   381  
   382  	writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
   383  		{Revision: snap.R(100)},
   384  		{Revision: snap.R(123)},
   385  	})
   386  
   387  	err := os.MkdirAll(filepath.Dir(dirs.SnapdSocket), 0755)
   388  	c.Assert(err, IsNil)
   389  	err = ioutil.WriteFile(dirs.SnapdSocket, []byte{}, 0755)
   390  	c.Assert(err, IsNil)
   391  
   392  	// mock snapd in the core snap
   393  	snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
   394  		`test "$SNAPD_REVERT_TO_REV" = "100"`)
   395  	defer snapdCmd.Restore()
   396  
   397  	systemctlCmd := testutil.MockCommand(c, "systemctl", "")
   398  	defer systemctlCmd.Restore()
   399  
   400  	os.Args = []string{"snap-failure", "snapd"}
   401  	err = failure.Run()
   402  	c.Check(err, IsNil)
   403  	c.Check(r.Stderr(), HasLen, 0)
   404  
   405  	c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
   406  		{"snapd"},
   407  	})
   408  	c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
   409  		{"systemctl", "stop", "snapd.socket"},
   410  		{"systemctl", "is-failed", "snapd.socket", "snapd.service"},
   411  		{"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
   412  		{"systemctl", "restart", "snapd.socket"},
   413  	})
   414  
   415  	// make sure the socket file was deleted
   416  	c.Assert(osutil.FileExists(dirs.SnapdSocket), Equals, false)
   417  }