github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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, []snapstate.MinimalInstallInfo{snapstate.InstallSnapInfo{Info: snap1}, snapstate.InstallSnapInfo{Info: 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, []snapstate.MinimalInstallInfo{
   236  		snapstate.InstallSnapInfo{Info: snap1},
   237  		snapstate.InstallSnapInfo{Info: snap2},
   238  		snapstate.InstallSnapInfo{Info: snap3},
   239  		snapstate.InstallSnapInfo{Info: snap4}}, 0)
   240  	c.Assert(err, IsNil)
   241  	c.Check(sz, Equals, uint64(snap1Size+snap2Size+snap3Size+snap4Size+someBaseSize+otherBaseSize))
   242  }
   243  
   244  func (s *snapmgrTestSuite) TestInstallSizeWithContentProviders(c *C) {
   245  	st := s.state
   246  	st.Lock()
   247  	defer st.Unlock()
   248  
   249  	repo := interfaces.NewRepository()
   250  	ifacerepo.Replace(st, repo)
   251  
   252  	s.setupInstallSizeStore()
   253  
   254  	snap1 := snaptest.MockSnap(c, snapYamlWithContentPlug1, &snap.SideInfo{
   255  		RealName: "some-snap",
   256  		Revision: snap.R(1),
   257  	})
   258  	snap1.Size = snap1Size
   259  
   260  	snap2 := snaptest.MockSnap(c, snapYamlWithContentPlug2, &snap.SideInfo{
   261  		RealName: "some-snap2",
   262  		Revision: snap.R(1),
   263  	})
   264  	snap2.Size = snap2Size
   265  
   266  	s.mockCoreSnap(c)
   267  
   268  	// both snaps have same content providers and base
   269  	sz, err := snapstate.InstallSize(st, []snapstate.MinimalInstallInfo{
   270  		snapstate.InstallSnapInfo{Info: snap1}, snapstate.InstallSnapInfo{Info: snap2}}, 0)
   271  	c.Assert(err, IsNil)
   272  	c.Check(sz, Equals, uint64(snap1Size+snap2Size+someBaseSize+snapContentSlotSize))
   273  }
   274  
   275  func (s *snapmgrTestSuite) TestInstallSizeWithNestedDependencies(c *C) {
   276  	st := s.state
   277  	st.Lock()
   278  	defer st.Unlock()
   279  
   280  	repo := interfaces.NewRepository()
   281  	ifacerepo.Replace(st, repo)
   282  
   283  	s.setupInstallSizeStore()
   284  	snap1 := snaptest.MockSnap(c, snapYamlWithContentPlug3, &snap.SideInfo{
   285  		RealName: "some-snap",
   286  		Revision: snap.R(1),
   287  	})
   288  	snap1.Size = snap1Size
   289  
   290  	s.mockCoreSnap(c)
   291  
   292  	sz, err := snapstate.InstallSize(st, []snapstate.MinimalInstallInfo{snapstate.InstallSnapInfo{Info: snap1}}, 0)
   293  	c.Assert(err, IsNil)
   294  	c.Check(sz, Equals, uint64(snap1Size+someBaseSize+snapOtherContentSlotSize+someOtherBaseSize))
   295  }
   296  
   297  func (s *snapmgrTestSuite) TestInstallSizeWithOtherChangeAffectingSameSnaps(c *C) {
   298  	st := s.state
   299  	st.Lock()
   300  	defer st.Unlock()
   301  
   302  	var currentSnapsCalled int
   303  	restore := snapstate.MockCurrentSnaps(func(st *state.State) ([]*store.CurrentSnap, error) {
   304  		currentSnapsCalled++
   305  		// call original implementation of currentSnaps
   306  		curr, err := snapstate.CurrentSnaps(st)
   307  		if currentSnapsCalled == 1 {
   308  			return curr, err
   309  		}
   310  		// simulate other change that installed some-snap3 and other-base while
   311  		// we release the lock inside InstallSize.
   312  		curr = append(curr, &store.CurrentSnap{InstanceName: "some-snap3"})
   313  		curr = append(curr, &store.CurrentSnap{InstanceName: "other-base"})
   314  		return curr, nil
   315  	})
   316  	defer restore()
   317  
   318  	s.setupInstallSizeStore()
   319  
   320  	snap1 := snaptest.MockSnap(c, snapYamlWithBase1, &snap.SideInfo{
   321  		RealName: "some-snap1",
   322  		Revision: snap.R(1),
   323  	})
   324  	snap1.Size = snap1Size
   325  	snap3 := snaptest.MockSnap(c, snapYamlWithBase3, &snap.SideInfo{
   326  		RealName: "some-snap3",
   327  		Revision: snap.R(2),
   328  	})
   329  	snap3.Size = snap3Size
   330  
   331  	sz, err := snapstate.InstallSize(st, []snapstate.MinimalInstallInfo{
   332  		snapstate.InstallSnapInfo{Info: snap1}, snapstate.InstallSnapInfo{Info: snap3}}, 0)
   333  	c.Assert(err, IsNil)
   334  	// snap3 and its base installed by another change, not counted here
   335  	c.Check(sz, Equals, uint64(snap1Size+someBaseSize))
   336  
   337  	// sanity
   338  	c.Check(currentSnapsCalled, Equals, 2)
   339  }
   340  
   341  func (s *snapmgrTestSuite) TestInstallSizeErrorNoDownloadInfo(c *C) {
   342  	st := s.state
   343  	st.Lock()
   344  	defer st.Unlock()
   345  
   346  	snap1 := &snap.Info{
   347  		SideInfo: snap.SideInfo{
   348  			RealName: "snap",
   349  		}}
   350  
   351  	_, err := snapstate.InstallSize(st, []snapstate.MinimalInstallInfo{snapstate.InstallSnapInfo{Info: snap1}}, 0)
   352  	c.Assert(err, ErrorMatches, `internal error: download info missing.*`)
   353  }