github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/snapstate/handlers_prereq_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017-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 snapstate_test
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  	"gopkg.in/tomb.v2"
    29  
    30  	"github.com/snapcore/snapd/overlord/snapstate"
    31  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    32  	"github.com/snapcore/snapd/overlord/state"
    33  	"github.com/snapcore/snapd/release"
    34  	"github.com/snapcore/snapd/snap"
    35  	"github.com/snapcore/snapd/store"
    36  )
    37  
    38  type prereqSuite struct {
    39  	baseHandlerSuite
    40  
    41  	fakeStore *fakeStore
    42  }
    43  
    44  var _ = Suite(&prereqSuite{})
    45  
    46  func (s *prereqSuite) SetUpTest(c *C) {
    47  	s.setup(c, nil)
    48  
    49  	s.fakeStore = &fakeStore{
    50  		state:       s.state,
    51  		fakeBackend: s.fakeBackend,
    52  	}
    53  	s.state.Lock()
    54  	defer s.state.Unlock()
    55  	snapstate.ReplaceStore(s.state, s.fakeStore)
    56  
    57  	s.state.Set("seeded", true)
    58  	s.state.Set("refresh-privacy-key", "privacy-key")
    59  	s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel()))
    60  }
    61  
    62  func (s *prereqSuite) TestDoPrereqNothingToDo(c *C) {
    63  	s.state.Lock()
    64  
    65  	si1 := &snap.SideInfo{
    66  		RealName: "core",
    67  		Revision: snap.R(1),
    68  	}
    69  	snapstate.Set(s.state, "core", &snapstate.SnapState{
    70  		Sequence: []*snap.SideInfo{si1},
    71  		Current:  si1.Revision,
    72  	})
    73  
    74  	t := s.state.NewTask("prerequisites", "test")
    75  	t.Set("snap-setup", &snapstate.SnapSetup{
    76  		SideInfo: &snap.SideInfo{
    77  			RealName: "foo",
    78  			Revision: snap.R(33),
    79  		},
    80  	})
    81  	s.state.NewChange("dummy", "...").AddTask(t)
    82  	s.state.Unlock()
    83  
    84  	s.se.Ensure()
    85  	s.se.Wait()
    86  
    87  	s.state.Lock()
    88  	defer s.state.Unlock()
    89  	c.Assert(s.fakeBackend.ops, HasLen, 0)
    90  	c.Check(t.Status(), Equals, state.DoneStatus)
    91  }
    92  
    93  func (s *prereqSuite) TestDoPrereqWithBaseNone(c *C) {
    94  	s.state.Lock()
    95  
    96  	t := s.state.NewTask("prerequisites", "test")
    97  	t.Set("snap-setup", &snapstate.SnapSetup{
    98  		SideInfo: &snap.SideInfo{
    99  			RealName: "foo",
   100  			Revision: snap.R(33),
   101  		},
   102  		Base:   "none",
   103  		Prereq: []string{"prereq1"},
   104  	})
   105  	chg := s.state.NewChange("dummy", "...")
   106  	chg.AddTask(t)
   107  	s.state.Unlock()
   108  
   109  	s.se.Ensure()
   110  	s.se.Wait()
   111  
   112  	s.state.Lock()
   113  	defer s.state.Unlock()
   114  	c.Check(t.Status(), Equals, state.DoneStatus)
   115  
   116  	// check that the do-prereq task added all needed prereqs
   117  	expectedLinkedSnaps := []string{"prereq1", "snapd"}
   118  	linkedSnaps := make([]string, 0, len(expectedLinkedSnaps))
   119  	for _, t := range chg.Tasks() {
   120  		if t.Kind() == "link-snap" {
   121  			snapsup, err := snapstate.TaskSnapSetup(t)
   122  			c.Assert(err, IsNil)
   123  			linkedSnaps = append(linkedSnaps, snapsup.InstanceName())
   124  		}
   125  	}
   126  	c.Check(linkedSnaps, DeepEquals, expectedLinkedSnaps)
   127  }
   128  
   129  func (s *prereqSuite) TestDoPrereqTalksToStoreAndQueues(c *C) {
   130  	s.state.Lock()
   131  
   132  	snapstate.Set(s.state, "core", &snapstate.SnapState{
   133  		Active: true,
   134  		Sequence: []*snap.SideInfo{
   135  			{RealName: "core", Revision: snap.R(1)},
   136  		},
   137  		Current:  snap.R(1),
   138  		SnapType: "os",
   139  	})
   140  
   141  	t := s.state.NewTask("prerequisites", "test")
   142  	t.Set("snap-setup", &snapstate.SnapSetup{
   143  		SideInfo: &snap.SideInfo{
   144  			RealName: "foo",
   145  			Revision: snap.R(33),
   146  		},
   147  		Channel: "beta",
   148  		Base:    "some-base",
   149  		Prereq:  []string{"prereq1", "prereq2"},
   150  	})
   151  	chg := s.state.NewChange("dummy", "...")
   152  	chg.AddTask(t)
   153  	s.state.Unlock()
   154  
   155  	s.se.Ensure()
   156  	s.se.Wait()
   157  
   158  	s.state.Lock()
   159  	defer s.state.Unlock()
   160  	c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{
   161  		{
   162  			op: "storesvc-snap-action",
   163  		},
   164  		{
   165  			op: "storesvc-snap-action:action",
   166  			action: store.SnapAction{
   167  				Action:       "install",
   168  				InstanceName: "prereq1",
   169  				Channel:      "stable",
   170  			},
   171  			revno: snap.R(11),
   172  		},
   173  		{
   174  			op: "storesvc-snap-action",
   175  		},
   176  		{
   177  			op: "storesvc-snap-action:action",
   178  			action: store.SnapAction{
   179  				Action:       "install",
   180  				InstanceName: "prereq2",
   181  				Channel:      "stable",
   182  			},
   183  			revno: snap.R(11),
   184  		},
   185  		{
   186  			op: "storesvc-snap-action",
   187  		},
   188  		{
   189  			op: "storesvc-snap-action:action",
   190  			action: store.SnapAction{
   191  				Action:       "install",
   192  				InstanceName: "some-base",
   193  				Channel:      "stable",
   194  			},
   195  			revno: snap.R(11),
   196  		},
   197  	})
   198  	c.Check(t.Status(), Equals, state.DoneStatus)
   199  
   200  	// check that the do-prereq task added all needed prereqs
   201  	expectedLinkedSnaps := []string{"prereq1", "prereq2", "some-base"}
   202  	linkedSnaps := make([]string, 0, len(expectedLinkedSnaps))
   203  	for _, t := range chg.Tasks() {
   204  		if t.Kind() == "link-snap" {
   205  			snapsup, err := snapstate.TaskSnapSetup(t)
   206  			c.Assert(err, IsNil)
   207  			linkedSnaps = append(linkedSnaps, snapsup.InstanceName())
   208  		}
   209  	}
   210  	c.Check(linkedSnaps, DeepEquals, expectedLinkedSnaps)
   211  }
   212  
   213  func (s *prereqSuite) TestDoPrereqRetryWhenBaseInFlight(c *C) {
   214  	restore := snapstate.MockPrerequisitesRetryTimeout(5 * time.Millisecond)
   215  	defer restore()
   216  
   217  	calls := 0
   218  	s.runner.AddHandler("link-snap",
   219  		func(task *state.Task, _ *tomb.Tomb) error {
   220  			if calls == 0 {
   221  				// retry again later, this forces ordering of
   222  				// tasks, so that the prerequisites tasks ends
   223  				// up waiting for this one
   224  				calls += 1
   225  				return &state.Retry{After: 1 * time.Millisecond}
   226  			}
   227  
   228  			// setup everything as if the snap is installed
   229  			st := task.State()
   230  			st.Lock()
   231  			defer st.Unlock()
   232  			snapsup, _ := snapstate.TaskSnapSetup(task)
   233  			var snapst snapstate.SnapState
   234  			snapstate.Get(st, snapsup.InstanceName(), &snapst)
   235  			snapst.Current = snapsup.Revision()
   236  			snapst.Sequence = append(snapst.Sequence, snapsup.SideInfo)
   237  			snapstate.Set(st, snapsup.InstanceName(), &snapst)
   238  			return nil
   239  		},
   240  		func(*state.Task, *tomb.Tomb) error {
   241  			return nil
   242  		})
   243  	s.state.Lock()
   244  	tCore := s.state.NewTask("link-snap", "Pretend core gets installed")
   245  	tCore.Set("snap-setup", &snapstate.SnapSetup{
   246  		SideInfo: &snap.SideInfo{
   247  			RealName: "core",
   248  			Revision: snap.R(11),
   249  		},
   250  	})
   251  
   252  	// pretend foo gets installed and needs core (which is in progress)
   253  	t := s.state.NewTask("prerequisites", "foo")
   254  	t.Set("snap-setup", &snapstate.SnapSetup{
   255  		SideInfo: &snap.SideInfo{
   256  			RealName: "foo",
   257  		},
   258  	})
   259  
   260  	chg := s.state.NewChange("dummy", "...")
   261  	chg.AddTask(t)
   262  	chg.AddTask(tCore)
   263  
   264  	// NOTE: tasks are iterated on in undefined order, we have fixed the
   265  	// link-snap handler to return a 'fake' retry what results
   266  	// 'prerequisites' task handler observing the state of the world we
   267  	// want, even if 'link-snap' ran first
   268  
   269  	for i := 0; i < 10; i++ {
   270  		time.Sleep(1 * time.Millisecond)
   271  		s.state.Unlock()
   272  		s.se.Ensure()
   273  		s.se.Wait()
   274  		s.state.Lock()
   275  		if tCore.Status() == state.DoneStatus {
   276  			break
   277  		}
   278  	}
   279  
   280  	// check that t is not done yet, it must wait for core
   281  	c.Check(t.Status(), Equals, state.DoingStatus)
   282  	c.Check(tCore.Status(), Equals, state.DoneStatus)
   283  
   284  	// wait, we will hit prereq-retry-timeout eventually
   285  	// (this can take a while on very slow machines)
   286  	for i := 0; i < 50; i++ {
   287  		time.Sleep(10 * time.Millisecond)
   288  		s.state.Unlock()
   289  		s.se.Ensure()
   290  		s.se.Wait()
   291  		s.state.Lock()
   292  		if t.Status() == state.DoneStatus {
   293  			break
   294  		}
   295  	}
   296  	c.Check(t.Status(), Equals, state.DoneStatus)
   297  }
   298  
   299  func (s *prereqSuite) TestDoPrereqChannelEnvvars(c *C) {
   300  	os.Setenv("SNAPD_BASES_CHANNEL", "edge")
   301  	defer os.Unsetenv("SNAPD_BASES_CHANNEL")
   302  	os.Setenv("SNAPD_PREREQS_CHANNEL", "candidate")
   303  	defer os.Unsetenv("SNAPD_PREREQS_CHANNEL")
   304  	s.state.Lock()
   305  
   306  	snapstate.Set(s.state, "core", &snapstate.SnapState{
   307  		Active: true,
   308  		Sequence: []*snap.SideInfo{
   309  			{RealName: "core", Revision: snap.R(1)},
   310  		},
   311  		Current:  snap.R(1),
   312  		SnapType: "os",
   313  	})
   314  
   315  	t := s.state.NewTask("prerequisites", "test")
   316  	t.Set("snap-setup", &snapstate.SnapSetup{
   317  		SideInfo: &snap.SideInfo{
   318  			RealName: "foo",
   319  			Revision: snap.R(33),
   320  		},
   321  		Channel: "beta",
   322  		Base:    "some-base",
   323  		Prereq:  []string{"prereq1", "prereq2"},
   324  	})
   325  	chg := s.state.NewChange("dummy", "...")
   326  	chg.AddTask(t)
   327  	s.state.Unlock()
   328  
   329  	s.se.Ensure()
   330  	s.se.Wait()
   331  
   332  	s.state.Lock()
   333  	defer s.state.Unlock()
   334  	c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{
   335  		{
   336  			op: "storesvc-snap-action",
   337  		},
   338  		{
   339  			op: "storesvc-snap-action:action",
   340  			action: store.SnapAction{
   341  				Action:       "install",
   342  				InstanceName: "prereq1",
   343  				Channel:      "candidate",
   344  			},
   345  			revno: snap.R(11),
   346  		},
   347  		{
   348  			op: "storesvc-snap-action",
   349  		},
   350  		{
   351  			op: "storesvc-snap-action:action",
   352  			action: store.SnapAction{
   353  				Action:       "install",
   354  				InstanceName: "prereq2",
   355  				Channel:      "candidate",
   356  			},
   357  			revno: snap.R(11),
   358  		},
   359  		{
   360  			op: "storesvc-snap-action",
   361  		},
   362  		{
   363  			op: "storesvc-snap-action:action",
   364  			action: store.SnapAction{
   365  				Action:       "install",
   366  				InstanceName: "some-base",
   367  				Channel:      "edge",
   368  			},
   369  			revno: snap.R(11),
   370  		},
   371  	})
   372  	c.Check(t.Status(), Equals, state.DoneStatus)
   373  }
   374  
   375  func (s *prereqSuite) TestDoPrereqNothingToDoForBase(c *C) {
   376  	for _, typ := range []snap.Type{
   377  		snap.TypeOS,
   378  		snap.TypeGadget,
   379  		snap.TypeKernel,
   380  		snap.TypeBase,
   381  	} {
   382  
   383  		s.state.Lock()
   384  		t := s.state.NewTask("prerequisites", "test")
   385  		t.Set("snap-setup", &snapstate.SnapSetup{
   386  			SideInfo: &snap.SideInfo{
   387  				RealName: fmt.Sprintf("foo-%s", typ),
   388  				Revision: snap.R(1),
   389  			},
   390  			Type: typ,
   391  		})
   392  		s.state.NewChange("dummy", "...").AddTask(t)
   393  		s.state.Unlock()
   394  
   395  		s.se.Ensure()
   396  		s.se.Wait()
   397  
   398  		s.state.Lock()
   399  		c.Assert(s.fakeBackend.ops, HasLen, 0)
   400  		c.Check(t.Status(), Equals, state.DoneStatus)
   401  		s.state.Unlock()
   402  	}
   403  }
   404  
   405  func (s *prereqSuite) TestDoPrereqNothingToDoForSnapdSnap(c *C) {
   406  	s.state.Lock()
   407  	t := s.state.NewTask("prerequisites", "test")
   408  	t.Set("snap-setup", &snapstate.SnapSetup{
   409  		// type is normally set from snap info at install time
   410  		Type: snap.TypeSnapd,
   411  		SideInfo: &snap.SideInfo{
   412  			RealName: "snapd",
   413  			Revision: snap.R(1),
   414  		},
   415  	})
   416  	s.state.NewChange("dummy", "...").AddTask(t)
   417  	s.state.Unlock()
   418  
   419  	s.se.Ensure()
   420  	s.se.Wait()
   421  
   422  	s.state.Lock()
   423  	c.Assert(s.fakeBackend.ops, HasLen, 0)
   424  	c.Check(t.Status(), Equals, state.DoneStatus)
   425  	s.state.Unlock()
   426  }
   427  
   428  func (s *prereqSuite) TestDoPrereqCore16wCoreNothingToDo(c *C) {
   429  	s.state.Lock()
   430  
   431  	si1 := &snap.SideInfo{
   432  		RealName: "core",
   433  		Revision: snap.R(1),
   434  	}
   435  	snapstate.Set(s.state, "core", &snapstate.SnapState{
   436  		Sequence: []*snap.SideInfo{si1},
   437  		Current:  si1.Revision,
   438  	})
   439  
   440  	t := s.state.NewTask("prerequisites", "test")
   441  	t.Set("snap-setup", &snapstate.SnapSetup{
   442  		SideInfo: &snap.SideInfo{
   443  			RealName: "foo",
   444  			Revision: snap.R(33),
   445  		},
   446  		Base: "core16",
   447  	})
   448  	s.state.NewChange("dummy", "...").AddTask(t)
   449  	s.state.Unlock()
   450  
   451  	s.se.Ensure()
   452  	s.se.Wait()
   453  
   454  	s.state.Lock()
   455  	defer s.state.Unlock()
   456  	c.Assert(s.fakeBackend.ops, HasLen, 0)
   457  	c.Check(t.Status(), Equals, state.DoneStatus)
   458  }
   459  
   460  func (s *prereqSuite) testDoPrereqNoCorePullsInSnaps(c *C, base string) {
   461  	restore := release.MockOnClassic(true)
   462  	defer restore()
   463  
   464  	s.state.Lock()
   465  
   466  	t := s.state.NewTask("prerequisites", "test")
   467  	t.Set("snap-setup", &snapstate.SnapSetup{
   468  		SideInfo: &snap.SideInfo{
   469  			RealName: "foo",
   470  			Revision: snap.R(33),
   471  		},
   472  		Base: base,
   473  	})
   474  	s.state.NewChange("dummy", "...").AddTask(t)
   475  	s.state.Unlock()
   476  
   477  	s.se.Ensure()
   478  	s.se.Wait()
   479  
   480  	s.state.Lock()
   481  	defer s.state.Unlock()
   482  	c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{
   483  		{
   484  			op: "storesvc-snap-action",
   485  		},
   486  		{
   487  			op: "storesvc-snap-action:action",
   488  			action: store.SnapAction{
   489  				Action:       "install",
   490  				InstanceName: base,
   491  				Channel:      "stable",
   492  			},
   493  			revno: snap.R(11),
   494  		},
   495  		{
   496  			op: "storesvc-snap-action",
   497  		},
   498  		{
   499  			op: "storesvc-snap-action:action",
   500  			action: store.SnapAction{
   501  				Action:       "install",
   502  				InstanceName: "snapd",
   503  				Channel:      "stable",
   504  			},
   505  			revno: snap.R(11),
   506  		},
   507  	})
   508  
   509  	c.Check(t.Change().Err(), IsNil)
   510  	c.Check(t.Status(), Equals, state.DoneStatus)
   511  }
   512  
   513  func (s *prereqSuite) TestDoPrereqCore16noCore(c *C) {
   514  	s.testDoPrereqNoCorePullsInSnaps(c, "core16")
   515  }
   516  
   517  func (s *prereqSuite) TestDoPrereqCore18NoCorePullsInSnapd(c *C) {
   518  	s.testDoPrereqNoCorePullsInSnaps(c, "core18")
   519  }
   520  
   521  func (s *prereqSuite) TestDoPrereqOtherBaseNoCorePullsInSnapd(c *C) {
   522  	s.testDoPrereqNoCorePullsInSnaps(c, "some-base")
   523  }
   524  
   525  func (s *prereqSuite) TestDoPrereqBaseIsNotBase(c *C) {
   526  	s.state.Lock()
   527  
   528  	t := s.state.NewTask("prerequisites", "test")
   529  	t.Set("snap-setup", &snapstate.SnapSetup{
   530  		SideInfo: &snap.SideInfo{
   531  			RealName: "foo",
   532  			Revision: snap.R(33),
   533  		},
   534  		Channel: "beta",
   535  		Base:    "some-epoch-snap",
   536  		Prereq:  []string{"prereq1"},
   537  	})
   538  	chg := s.state.NewChange("dummy", "...")
   539  	chg.AddTask(t)
   540  	s.state.Unlock()
   541  
   542  	s.se.Ensure()
   543  	s.se.Wait()
   544  
   545  	s.state.Lock()
   546  	defer s.state.Unlock()
   547  	c.Check(chg.Status(), Equals, state.ErrorStatus)
   548  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*- test \(declared snap base "some-epoch-snap" has unexpected type "app", instead of 'base'\)`)
   549  }