github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/snapstate/storehelpers_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 snapstate_test
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/interfaces"
    29  	"github.com/snapcore/snapd/overlord/auth"
    30  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    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/snap/snaptest"
    35  	"github.com/snapcore/snapd/store"
    36  )
    37  
    38  const snapYaml1 = `
    39  name: some-snap
    40  version: 1.0
    41  `
    42  const snapYaml2 = `
    43  name: some-snap
    44  version: 1.0
    45  base: none
    46  `
    47  
    48  const snapYamlWithBase1 = `
    49  name: some-snap1
    50  version: 1.0
    51  base: some-base
    52  `
    53  const snapYamlWithBase2 = `
    54  name: some-snap2
    55  version: 1.0
    56  base: some-base
    57  `
    58  const snapYamlWithBase3 = `
    59  name: some-snap3
    60  version: 2.0
    61  base: other-base
    62  `
    63  const snapYamlWithBase4 = `
    64  name: some-snap4
    65  version: 1.0
    66  base: yet-another-base
    67  `
    68  const snapYamlWithContentPlug1 = `
    69  name: some-snap
    70  version: 1.0
    71  base: some-base
    72  plugs:
    73    some-plug:
    74      interface: content
    75      content: shared-content
    76      default-provider: snap-content-slot
    77  `
    78  
    79  const snapYamlWithContentPlug2 = `
    80  name: some-snap2
    81  version: 1.0
    82  base: some-base
    83  plugs:
    84    some-plug:
    85      interface: content
    86      content: shared-content
    87      default-provider: snap-content-slot
    88  `
    89  
    90  const snapYamlWithContentPlug3 = `
    91  name: some-snap
    92  version: 1.0
    93  base: some-base
    94  plugs:
    95    some-plug:
    96      interface: content
    97      content: shared-content
    98      default-provider: snap-content-slot-other
    99  `
   100  
   101  const (
   102  	// use sizes that make it easier to spot unexpected dependencies in the
   103  	// total sum.
   104  	someBaseSize             = 1
   105  	otherBaseSize            = 100
   106  	snap1Size                = 1000
   107  	snap2Size                = 10000
   108  	snap3Size                = 100000
   109  	snap4Size                = 1000000
   110  	snapContentSlotSize      = 10000000
   111  	snapOtherContentSlotSize = 100000000
   112  	someOtherBaseSize        = 1000000000
   113  )
   114  
   115  type installSizeTestStore struct {
   116  	*fakeStore
   117  }
   118  
   119  func (f installSizeTestStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) {
   120  	sizes := map[string]int64{
   121  		"some-base":               someBaseSize,
   122  		"other-base":              otherBaseSize,
   123  		"snap-content-slot":       snapContentSlotSize,
   124  		"snap-content-slot-other": snapOtherContentSlotSize,
   125  		"some-other-base":         someOtherBaseSize,
   126  	}
   127  	for _, sa := range actions {
   128  		if sa.Action != "install" {
   129  			panic(fmt.Sprintf("unexpected action: %s", sa.Action))
   130  		}
   131  		if sa.Channel != "stable" {
   132  			panic(fmt.Sprintf("unexpected channel: %s", sa.Channel))
   133  		}
   134  		if _, ok := sizes[sa.InstanceName]; !ok {
   135  			panic(fmt.Sprintf("unexpected snap: %q", sa.InstanceName))
   136  		}
   137  	}
   138  	sars, _, err := f.fakeStore.SnapAction(ctx, currentSnaps, actions, assertQuery, user, opts)
   139  	if err != nil {
   140  		return nil, nil, err
   141  	}
   142  
   143  	for _, sr := range sars {
   144  		if sz, ok := sizes[sr.Info.InstanceName()]; ok {
   145  			sr.Info.Size = sz
   146  		} else {
   147  			panic(fmt.Sprintf("unexpected snap: %q", sr.Info.InstanceName()))
   148  		}
   149  		if sr.Info.InstanceName() == "snap-content-slot-other" {
   150  			sr.Info.Base = "some-other-base"
   151  		}
   152  	}
   153  	return sars, nil, nil
   154  }
   155  
   156  func (s *snapmgrTestSuite) mockCoreSnap(c *C) {
   157  	snapstate.Set(s.state, "core", &snapstate.SnapState{
   158  		Active: true,
   159  		Sequence: []*snap.SideInfo{
   160  			{RealName: "core", SnapID: "core-id", Revision: snap.R(1)},
   161  		},
   162  		Current:  snap.R(1),
   163  		SnapType: "os",
   164  	})
   165  	// mock the yaml
   166  	makeInstalledMockCoreSnap(c)
   167  }
   168  
   169  func (s *snapmgrTestSuite) setupInstallSizeStore() {
   170  	fakestore := installSizeTestStore{fakeStore: s.fakeStore}
   171  	snapstate.ReplaceStore(s.state, fakestore)
   172  }
   173  
   174  func (s *snapmgrTestSuite) TestInstallSizeSimple(c *C) {
   175  	st := s.state
   176  	st.Lock()
   177  	defer st.Unlock()
   178  
   179  	s.setupInstallSizeStore()
   180  	s.mockCoreSnap(c)
   181  
   182  	snap1 := snaptest.MockSnap(c, snapYaml1, &snap.SideInfo{
   183  		RealName: "some-snap1",
   184  		Revision: snap.R(1),
   185  	})
   186  	snap1.Size = snap1Size
   187  	snap2 := snaptest.MockSnap(c, snapYaml2, &snap.SideInfo{
   188  		RealName: "some-snap2",
   189  		Revision: snap.R(2),
   190  	})
   191  	snap2.Size = snap2Size
   192  
   193  	sz, err := snapstate.InstallSize(st, []*snap.Info{snap1, snap2}, 0)
   194  	c.Assert(err, IsNil)
   195  	c.Check(sz, Equals, uint64(snap1Size+snap2Size))
   196  }
   197  
   198  func (s *snapmgrTestSuite) TestInstallSizeWithBases(c *C) {
   199  	st := s.state
   200  	st.Lock()
   201  	defer st.Unlock()
   202  
   203  	s.setupInstallSizeStore()
   204  
   205  	snap1 := snaptest.MockSnap(c, snapYamlWithBase1, &snap.SideInfo{
   206  		RealName: "some-snap1",
   207  		Revision: snap.R(1),
   208  	})
   209  	snap1.Size = snap1Size
   210  	snap2 := snaptest.MockSnap(c, snapYamlWithBase2, &snap.SideInfo{
   211  		RealName: "some-snap2",
   212  		Revision: snap.R(2),
   213  	})
   214  	snap2.Size = snap2Size
   215  	snap3 := snaptest.MockSnap(c, snapYamlWithBase3, &snap.SideInfo{
   216  		RealName: "some-snap3",
   217  		Revision: snap.R(4),
   218  	})
   219  	snap3.Size = snap3Size
   220  	snap4 := snaptest.MockSnap(c, snapYamlWithBase4, &snap.SideInfo{
   221  		RealName: "some-snap4",
   222  		Revision: snap.R(1),
   223  	})
   224  	snap4.Size = snap4Size
   225  
   226  	// base of some-snap4 is already installed
   227  	snapstate.Set(st, "yet-another-base", &snapstate.SnapState{
   228  		Active: true,
   229  		Sequence: []*snap.SideInfo{
   230  			{RealName: "yet-another-base", Revision: snap.R(1), SnapID: "yet-another-base-id"},
   231  		},
   232  		Current: snap.R(1),
   233  	})
   234  
   235  	sz, err := snapstate.InstallSize(st, []*snap.Info{snap1, snap2, snap3, snap4}, 0)
   236  	c.Assert(err, IsNil)
   237  	c.Check(sz, Equals, uint64(snap1Size+snap2Size+snap3Size+snap4Size+someBaseSize+otherBaseSize))
   238  }
   239  
   240  func (s *snapmgrTestSuite) TestInstallSizeWithContentProviders(c *C) {
   241  	st := s.state
   242  	st.Lock()
   243  	defer st.Unlock()
   244  
   245  	repo := interfaces.NewRepository()
   246  	ifacerepo.Replace(st, repo)
   247  
   248  	s.setupInstallSizeStore()
   249  
   250  	snap1 := snaptest.MockSnap(c, snapYamlWithContentPlug1, &snap.SideInfo{
   251  		RealName: "some-snap",
   252  		Revision: snap.R(1),
   253  	})
   254  	snap1.Size = snap1Size
   255  
   256  	snap2 := snaptest.MockSnap(c, snapYamlWithContentPlug2, &snap.SideInfo{
   257  		RealName: "some-snap2",
   258  		Revision: snap.R(1),
   259  	})
   260  	snap2.Size = snap2Size
   261  
   262  	s.mockCoreSnap(c)
   263  
   264  	// both snaps have same content providers and base
   265  	sz, err := snapstate.InstallSize(st, []*snap.Info{snap1, snap2}, 0)
   266  	c.Assert(err, IsNil)
   267  	c.Check(sz, Equals, uint64(snap1Size+snap2Size+someBaseSize+snapContentSlotSize))
   268  }
   269  
   270  func (s *snapmgrTestSuite) TestInstallSizeWithNestedDependencies(c *C) {
   271  	st := s.state
   272  	st.Lock()
   273  	defer st.Unlock()
   274  
   275  	repo := interfaces.NewRepository()
   276  	ifacerepo.Replace(st, repo)
   277  
   278  	s.setupInstallSizeStore()
   279  	snap1 := snaptest.MockSnap(c, snapYamlWithContentPlug3, &snap.SideInfo{
   280  		RealName: "some-snap",
   281  		Revision: snap.R(1),
   282  	})
   283  	snap1.Size = snap1Size
   284  
   285  	s.mockCoreSnap(c)
   286  
   287  	sz, err := snapstate.InstallSize(st, []*snap.Info{snap1}, 0)
   288  	c.Assert(err, IsNil)
   289  	c.Check(sz, Equals, uint64(snap1Size+someBaseSize+snapOtherContentSlotSize+someOtherBaseSize))
   290  }
   291  
   292  func (s *snapmgrTestSuite) TestInstallSizeWithOtherChangeAffectingSameSnaps(c *C) {
   293  	st := s.state
   294  	st.Lock()
   295  	defer st.Unlock()
   296  
   297  	var currentSnapsCalled int
   298  	restore := snapstate.MockCurrentSnaps(func(st *state.State) ([]*store.CurrentSnap, error) {
   299  		currentSnapsCalled++
   300  		// call original implementation of currentSnaps
   301  		curr, err := snapstate.CurrentSnaps(st)
   302  		if currentSnapsCalled == 1 {
   303  			return curr, err
   304  		}
   305  		// simulate other change that installed some-snap3 and other-base while
   306  		// we release the lock inside InstallSize.
   307  		curr = append(curr, &store.CurrentSnap{InstanceName: "some-snap3"})
   308  		curr = append(curr, &store.CurrentSnap{InstanceName: "other-base"})
   309  		return curr, nil
   310  	})
   311  	defer restore()
   312  
   313  	s.setupInstallSizeStore()
   314  
   315  	snap1 := snaptest.MockSnap(c, snapYamlWithBase1, &snap.SideInfo{
   316  		RealName: "some-snap1",
   317  		Revision: snap.R(1),
   318  	})
   319  	snap1.Size = snap1Size
   320  	snap3 := snaptest.MockSnap(c, snapYamlWithBase3, &snap.SideInfo{
   321  		RealName: "some-snap3",
   322  		Revision: snap.R(2),
   323  	})
   324  	snap3.Size = snap3Size
   325  
   326  	sz, err := snapstate.InstallSize(st, []*snap.Info{snap1, snap3}, 0)
   327  	c.Assert(err, IsNil)
   328  	// snap3 and its base installed by another change, not counted here
   329  	c.Check(sz, Equals, uint64(snap1Size+someBaseSize))
   330  
   331  	// sanity
   332  	c.Check(currentSnapsCalled, Equals, 2)
   333  }
   334  
   335  func (s *snapmgrTestSuite) TestInstallSizeErrorNoDownloadInfo(c *C) {
   336  	st := s.state
   337  	st.Lock()
   338  	defer st.Unlock()
   339  
   340  	snap1 := &snap.Info{
   341  		SideInfo: snap.SideInfo{
   342  			RealName: "snap",
   343  		}}
   344  
   345  	_, err := snapstate.InstallSize(st, []*snap.Info{snap1}, 0)
   346  	c.Assert(err, ErrorMatches, `internal error: download info missing.*`)
   347  }