github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/snapstate/handlers_rerefresh_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 snapstate_test
    21  
    22  import (
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"sort"
    27  	"strings"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/overlord/snapstate"
    32  	"github.com/snapcore/snapd/overlord/state"
    33  	"github.com/snapcore/snapd/snap"
    34  	. "github.com/snapcore/snapd/testutil"
    35  )
    36  
    37  type reRefreshSuite struct {
    38  	baseHandlerSuite
    39  }
    40  
    41  var _ = Suite(&reRefreshSuite{})
    42  
    43  func logstr(task *state.Task) string {
    44  	return strings.Join(task.Log(), "\n")
    45  }
    46  
    47  func changeWithLanesAndSnapSetups(st *state.State, snapNames ...string) *state.Change {
    48  	chg := st.NewChange("dummy", "...")
    49  	for _, snapName := range snapNames {
    50  		lane := st.NewLane()
    51  		tsk := st.NewTask(fmt.Sprintf("a-task-for-snap-%s-in-lane-%d", snapName, lane), "test")
    52  		tsk.Set("snap-setup", &snapstate.SnapSetup{
    53  			SideInfo: &snap.SideInfo{RealName: snapName},
    54  		})
    55  		chg.AddTask(tsk)
    56  		tsk.JoinLane(lane)
    57  		tsk.SetStatus(state.DoneStatus)
    58  	}
    59  	return chg
    60  }
    61  
    62  func (s *reRefreshSuite) TestDoCheckReRefreshFailsWithoutReRefreshSetup(c *C) {
    63  	s.state.Lock()
    64  	chg := changeWithLanesAndSnapSetups(s.state, "some-snap")
    65  	task := s.state.NewTask("check-rerefresh", "test")
    66  	chg.AddTask(task)
    67  	s.state.Unlock()
    68  
    69  	s.se.Ensure()
    70  	s.se.Wait()
    71  
    72  	s.state.Lock()
    73  	defer s.state.Unlock()
    74  
    75  	c.Check(task.Status(), Equals, state.ErrorStatus)
    76  	c.Check(logstr(task), Contains, `no state entry for key`)
    77  }
    78  
    79  func (s *reRefreshSuite) TestDoCheckReRefreshFailsIfUpdateFails(c *C) {
    80  	defer snapstate.MockReRefreshUpdateMany(func(context.Context, *state.State, []string, int, snapstate.UpdateFilter, *snapstate.Flags, string) ([]string, []*state.TaskSet, error) {
    81  		return nil, nil, errors.New("bzzt")
    82  	})()
    83  
    84  	s.state.Lock()
    85  	chg := changeWithLanesAndSnapSetups(s.state, "some-snap")
    86  	task := s.state.NewTask("check-rerefresh", "test")
    87  	task.Set("rerefresh-setup", map[string]interface{}{})
    88  	chg.AddTask(task)
    89  	s.state.Unlock()
    90  
    91  	s.se.Ensure()
    92  	s.se.Wait()
    93  
    94  	s.state.Lock()
    95  	defer s.state.Unlock()
    96  
    97  	c.Check(task.Status(), Equals, state.ErrorStatus)
    98  	c.Check(logstr(task), Contains, `bzzt`)
    99  }
   100  
   101  func (s *reRefreshSuite) TestDoCheckReRefreshNoReRefreshes(c *C) {
   102  	updaterCalled := false
   103  	defer snapstate.MockReRefreshUpdateMany(func(context.Context, *state.State, []string, int, snapstate.UpdateFilter, *snapstate.Flags, string) ([]string, []*state.TaskSet, error) {
   104  		updaterCalled = true
   105  		return nil, nil, nil
   106  	})()
   107  
   108  	s.state.Lock()
   109  	chg := changeWithLanesAndSnapSetups(s.state, "some-snap")
   110  	task := s.state.NewTask("check-rerefresh", "test")
   111  	task.Set("rerefresh-setup", map[string]interface{}{})
   112  	chg.AddTask(task)
   113  	s.state.Unlock()
   114  
   115  	s.se.Ensure()
   116  	s.se.Wait()
   117  
   118  	s.state.Lock()
   119  	defer s.state.Unlock()
   120  
   121  	c.Check(task.Status(), Equals, state.DoneStatus)
   122  	c.Check(logstr(task), Contains, `No re-refreshes found.`)
   123  	c.Check(updaterCalled, Equals, true)
   124  }
   125  
   126  func (s *reRefreshSuite) TestDoCheckReRefreshPassesReRefreshSetupData(c *C) {
   127  	var chgID string
   128  	defer snapstate.MockReRefreshUpdateMany(func(ctx context.Context, st *state.State, snaps []string, userID int, filter snapstate.UpdateFilter, flags *snapstate.Flags, changeID string) ([]string, []*state.TaskSet, error) {
   129  		c.Check(changeID, Equals, chgID)
   130  		expected := []string{"won", "too", "tree"}
   131  		sort.Strings(expected)
   132  		sort.Strings(snaps)
   133  		c.Check(snaps, DeepEquals, expected)
   134  		c.Check(userID, Equals, 42)
   135  		c.Check(flags, DeepEquals, &snapstate.Flags{
   136  			DevMode:  true,
   137  			JailMode: true,
   138  		})
   139  		return nil, nil, nil
   140  	})()
   141  
   142  	s.state.Lock()
   143  	task := s.state.NewTask("check-rerefresh", "test")
   144  	task.Set("rerefresh-setup", map[string]interface{}{
   145  		"user-id":  42,
   146  		"devmode":  true,
   147  		"jailmode": true,
   148  	})
   149  	chg := changeWithLanesAndSnapSetups(s.state, "won", "too", "tree")
   150  	chg.AddTask(task)
   151  	chgID = chg.ID()
   152  	s.state.Unlock()
   153  
   154  	s.se.Ensure()
   155  	s.se.Wait()
   156  
   157  	s.state.Lock()
   158  	defer s.state.Unlock()
   159  
   160  	c.Check(task.Status(), Equals, state.DoneStatus)
   161  	c.Check(logstr(task), Contains, `No re-refreshes found.`)
   162  }
   163  
   164  func (s *reRefreshSuite) TestDoCheckReRefreshAddsNewTasks(c *C) {
   165  	defer snapstate.MockReRefreshUpdateMany(func(ctx context.Context, st *state.State, snaps []string, userID int, filter snapstate.UpdateFilter, flags *snapstate.Flags, changeID string) ([]string, []*state.TaskSet, error) {
   166  		expected := []string{"won", "too", "tree"}
   167  		sort.Strings(expected)
   168  		sort.Strings(snaps)
   169  		c.Check(snaps, DeepEquals, expected)
   170  
   171  		task := st.NewTask("witness", "...")
   172  
   173  		return []string{"won"}, []*state.TaskSet{state.NewTaskSet(task)}, nil
   174  	})()
   175  
   176  	s.state.Lock()
   177  	chg := changeWithLanesAndSnapSetups(s.state, "won", "too", "tree")
   178  	task := s.state.NewTask("check-rerefresh", "test")
   179  	task.Set("rerefresh-setup", map[string]interface{}{})
   180  	chg.AddTask(task)
   181  	s.state.Unlock()
   182  
   183  	s.se.Ensure()
   184  	s.se.Wait()
   185  
   186  	s.state.Lock()
   187  	defer s.state.Unlock()
   188  
   189  	c.Check(task.Status(), Equals, state.DoneStatus)
   190  	c.Check(logstr(task), Contains, `Found re-refresh for "won".`)
   191  
   192  	tasks := chg.Tasks()
   193  	c.Assert(tasks, HasLen, 5)
   194  	for i, kind := range []string{
   195  		"a-task-for-snap-won-in-lane-1",
   196  		"a-task-for-snap-too-in-lane-2",
   197  		"a-task-for-snap-tree-in-lane-3",
   198  		"check-rerefresh",
   199  		"witness",
   200  	} {
   201  		c.Check(tasks[i].Kind(), Equals, kind)
   202  	}
   203  }
   204  
   205  // wrapper around snapstate.RefreshedSnaps for easier testing
   206  func refreshedSnaps(task *state.Task) string {
   207  	snaps := snapstate.RefreshedSnaps(task)
   208  	sort.Strings(snaps)
   209  	return strings.Join(snaps, ",")
   210  }
   211  
   212  // add a lane with two tasks to chg, the first one with a SnapSetup
   213  // for a snap with t1snap, the second one with status t2status.
   214  func addLane(st *state.State, chg *state.Change, t1snap string, t2status state.Status) {
   215  	lane := st.NewLane()
   216  	t1 := st.NewTask("dummy1", "...")
   217  	t1.JoinLane(lane)
   218  	t1.Set("snap-setup", snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: t1snap}})
   219  	t1.SetStatus(state.DoneStatus)
   220  	chg.AddTask(t1)
   221  
   222  	t2 := st.NewTask("dummy2", "...")
   223  	t2.JoinLane(lane)
   224  	t2.WaitFor(t1)
   225  	t2.SetStatus(t2status)
   226  	chg.AddTask(t2)
   227  }
   228  
   229  func (s *reRefreshSuite) TestLaneSnapsSimple(c *C) {
   230  	s.state.Lock()
   231  	defer s.state.Unlock()
   232  	chg := s.state.NewChange("testing", "...")
   233  	addLane(s.state, chg, "aaa", state.DoneStatus)
   234  	task := s.state.NewTask("check-rerefresh", "...")
   235  	chg.AddTask(task)
   236  	c.Check(refreshedSnaps(task), Equals, "aaa")
   237  }
   238  
   239  func (s *reRefreshSuite) TestLaneSnapsMoreLanes(c *C) {
   240  	s.state.Lock()
   241  	defer s.state.Unlock()
   242  	chg := s.state.NewChange("testing", "...")
   243  	addLane(s.state, chg, "aaa", state.DoneStatus)
   244  	// more lanes, no problem
   245  	addLane(s.state, chg, "bbb", state.DoneStatus)
   246  	task := s.state.NewTask("check-rerefresh", "...")
   247  	chg.AddTask(task)
   248  	c.Check(refreshedSnaps(task), Equals, "aaa,bbb")
   249  }
   250  
   251  func (s *reRefreshSuite) TestLaneSnapsFailedLane(c *C) {
   252  	s.state.Lock()
   253  	defer s.state.Unlock()
   254  	chg := s.state.NewChange("testing", "...")
   255  	addLane(s.state, chg, "aaa", state.DoneStatus)
   256  	addLane(s.state, chg, "bbb", state.DoneStatus)
   257  	// a lane that's failed, no problem
   258  	addLane(s.state, chg, "ccc", state.ErrorStatus)
   259  	task := s.state.NewTask("check-rerefresh", "...")
   260  	chg.AddTask(task)
   261  	c.Check(refreshedSnaps(task), Equals, "aaa,bbb")
   262  }
   263  
   264  func (s *reRefreshSuite) TestLaneSnapsRerefreshResets(c *C) {
   265  	s.state.Lock()
   266  	defer s.state.Unlock()
   267  	chg := s.state.NewChange("testing", "...")
   268  	addLane(s.state, chg, "aaa", state.DoneStatus)
   269  	addLane(s.state, chg, "bbb", state.DoneStatus)
   270  	// a check-rerefresh task resets the list
   271  	chg.AddTask(s.state.NewTask("check-rerefresh", "..."))
   272  	addLane(s.state, chg, "ddd", state.DoneStatus)
   273  	task := s.state.NewTask("check-rerefresh", "...")
   274  	chg.AddTask(task)
   275  	c.Check(refreshedSnaps(task), Equals, "ddd")
   276  }
   277  
   278  func (s *reRefreshSuite) TestLaneSnapsStopsAtSelf(c *C) {
   279  	s.state.Lock()
   280  	defer s.state.Unlock()
   281  	chg := s.state.NewChange("testing", "...")
   282  	addLane(s.state, chg, "aaa", state.DoneStatus)
   283  	addLane(s.state, chg, "bbb", state.DoneStatus)
   284  	task := s.state.NewTask("check-rerefresh", "...")
   285  	chg.AddTask(task)
   286  	addLane(s.state, chg, "ddd", state.DoneStatus)
   287  	chg.AddTask(s.state.NewTask("check-rerefresh", "..."))
   288  
   289  	// unless we're looking for _that_ task (this is defensive; can't really happen)
   290  	c.Check(refreshedSnaps(task), Equals, "aaa,bbb")
   291  }
   292  
   293  func (s *reRefreshSuite) TestLaneSnapsTwoSetups(c *C) {
   294  	// check that only the first SnapSetup is important
   295  	s.state.Lock()
   296  	defer s.state.Unlock()
   297  
   298  	ts := state.NewTaskSet()
   299  	t1 := s.state.NewTask("dummy1", "...")
   300  	t1.Set("snap-setup", snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "one"}})
   301  	t1.SetStatus(state.DoneStatus)
   302  	ts.AddTask(t1)
   303  	t2 := s.state.NewTask("dummy2", "...")
   304  	t2.Set("snap-setup", snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "two"}})
   305  	t2.WaitFor(t1)
   306  	ts.AddTask(t2)
   307  	t2.SetStatus(state.DoneStatus)
   308  	ts.JoinLane(s.state.NewLane())
   309  	chg := s.state.NewChange("testing", "...")
   310  	chg.AddAll(ts)
   311  
   312  	task := s.state.NewTask("check-rerefresh", "...")
   313  	chg.AddTask(task)
   314  
   315  	c.Check(refreshedSnaps(task), Equals, "one")
   316  }
   317  
   318  func (s *reRefreshSuite) TestLaneSnapsBadSetup(c *C) {
   319  	// check that a bad SnapSetup doesn't make the thing fail
   320  	s.state.Lock()
   321  	defer s.state.Unlock()
   322  
   323  	ts := state.NewTaskSet()
   324  	t1 := s.state.NewTask("dummy1", "...")
   325  	t1.Set("snap-setup", "what is this")
   326  	t1.SetStatus(state.DoneStatus)
   327  	ts.AddTask(t1)
   328  	t2 := s.state.NewTask("dummy2", "...")
   329  	t2.Set("snap-setup", snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "two"}})
   330  	t2.WaitFor(t1)
   331  	ts.AddTask(t2)
   332  	t2.SetStatus(state.DoneStatus)
   333  	ts.JoinLane(s.state.NewLane())
   334  	chg := s.state.NewChange("testing", "...")
   335  	chg.AddAll(ts)
   336  
   337  	task := s.state.NewTask("check-rerefresh", "...")
   338  	chg.AddTask(task)
   339  
   340  	c.Check(refreshedSnaps(task), Equals, "two")
   341  }
   342  
   343  func (*reRefreshSuite) TestFilterReturnsFalseIfEpochEqual(c *C) {
   344  	// these work because we're mocking ReadInfo
   345  	snapst := &snapstate.SnapState{
   346  		Active: true,
   347  		Sequence: []*snap.SideInfo{
   348  			{RealName: "some-snap", Revision: snap.R(7)},
   349  		},
   350  		Current:  snap.R(7),
   351  		SnapType: "app",
   352  	}
   353  
   354  	c.Check(snapstate.ReRefreshFilter(&snap.Info{Epoch: snap.E("0")}, snapst), Equals, true)
   355  	c.Check(snapstate.ReRefreshFilter(&snap.Info{Epoch: snap.E("1*")}, snapst), Equals, false)
   356  	c.Check(snapstate.ReRefreshFilter(&snap.Info{Epoch: snap.E("1")}, snapst), Equals, true)
   357  }
   358  
   359  func (s *reRefreshSuite) TestFilterReturnsFalseIfEpochEqualZero(c *C) {
   360  	// these work because we're mocking ReadInfo
   361  	snapst := &snapstate.SnapState{
   362  		Active: true,
   363  		Sequence: []*snap.SideInfo{
   364  			{RealName: "snap-with-empty-epoch", Revision: snap.R(7)},
   365  		},
   366  		Current:  snap.R(7),
   367  		SnapType: "app",
   368  	}
   369  	c.Check(snapstate.ReRefreshFilter(&snap.Info{Epoch: snap.E("0")}, snapst), Equals, false)
   370  	c.Check(snapstate.ReRefreshFilter(&snap.Info{Epoch: snap.Epoch{}}, snapst), Equals, false)
   371  }