github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/configstate/configcore/vitality_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 configcore_test
    21  
    22  import (
    23  	"path/filepath"
    24  	"strconv"
    25  	"strings"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/asserts/assertstest"
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/gadget/quantity"
    33  	"github.com/snapcore/snapd/overlord/configstate/config"
    34  	"github.com/snapcore/snapd/overlord/configstate/configcore"
    35  	"github.com/snapcore/snapd/overlord/servicestate"
    36  	"github.com/snapcore/snapd/overlord/snapstate"
    37  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    38  	"github.com/snapcore/snapd/snap"
    39  	"github.com/snapcore/snapd/snap/snaptest"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  type vitalitySuite struct {
    44  	configcoreSuite
    45  }
    46  
    47  var _ = Suite(&vitalitySuite{})
    48  
    49  func (s *vitalitySuite) SetUpTest(c *C) {
    50  	s.configcoreSuite.SetUpTest(c)
    51  
    52  	uc18model := assertstest.FakeAssertion(map[string]interface{}{
    53  		"type":         "model",
    54  		"authority-id": "canonical",
    55  		"series":       "16",
    56  		"brand-id":     "canonical",
    57  		"model":        "pc",
    58  		"gadget":       "pc",
    59  		"kernel":       "kernel",
    60  		"architecture": "amd64",
    61  		"base":         "core18",
    62  	}).(*asserts.Model)
    63  
    64  	s.AddCleanup(snapstatetest.MockDeviceModel(uc18model))
    65  }
    66  
    67  func (s *vitalitySuite) TestConfigureVitalityUnhappyName(c *C) {
    68  	err := configcore.Run(&mockConf{
    69  		state: s.state,
    70  		changes: map[string]interface{}{
    71  			"resilience.vitality-hint": "-invalid-snap-name!yf",
    72  		},
    73  	})
    74  	c.Assert(err, ErrorMatches, `cannot set "resilience.vitality-hint": invalid snap name: ".*"`)
    75  }
    76  
    77  func (s *vitalitySuite) TestConfigureVitalityNoSnapd(c *C) {
    78  	err := configcore.Run(&mockConf{
    79  		state: s.state,
    80  		changes: map[string]interface{}{
    81  			"resilience.vitality-hint": "snapd",
    82  		},
    83  	})
    84  	c.Assert(err, ErrorMatches, `cannot set "resilience.vitality-hint": snapd snap vitality cannot be changed`)
    85  }
    86  
    87  func (s *vitalitySuite) TestConfigureVitalityhappyName(c *C) {
    88  	err := configcore.Run(&mockConf{
    89  		state: s.state,
    90  		changes: map[string]interface{}{
    91  			"resilience.vitality-hint": "valid-snapname",
    92  		},
    93  	})
    94  	c.Assert(err, IsNil)
    95  	// no snap named "valid-snapname" is installed, so no systemd action
    96  	c.Check(s.systemctlArgs, HasLen, 0)
    97  }
    98  
    99  var mockSnapWithService = `name: test-snap
   100  version: 1.0
   101  apps:
   102   foo:
   103    daemon: simple
   104  `
   105  
   106  func (s *vitalitySuite) TestConfigureVitalityWithValidSnapUC16(c *C) {
   107  	uc16model := assertstest.FakeAssertion(map[string]interface{}{
   108  		"type":         "model",
   109  		"authority-id": "canonical",
   110  		"series":       "16",
   111  		"brand-id":     "canonical",
   112  		"model":        "pc",
   113  		"gadget":       "pc",
   114  		"kernel":       "kernel",
   115  		"architecture": "amd64",
   116  	}).(*asserts.Model)
   117  
   118  	defer snapstatetest.MockDeviceModel(uc16model)()
   119  
   120  	s.testConfigureVitalityWithValidSnap(c, false)
   121  }
   122  
   123  func (s *vitalitySuite) TestConfigureVitalityWithValidSnapUC18(c *C) {
   124  	s.testConfigureVitalityWithValidSnap(c, true)
   125  }
   126  
   127  func (s *vitalitySuite) testConfigureVitalityWithValidSnap(c *C, uc18 bool) {
   128  	si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(1)}
   129  	snaptest.MockSnap(c, mockSnapWithService, si)
   130  	s.state.Lock()
   131  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   132  		Sequence: []*snap.SideInfo{si},
   133  		Current:  snap.R(1),
   134  		Active:   true,
   135  		SnapType: "app",
   136  	})
   137  	s.state.Unlock()
   138  
   139  	err := configcore.Run(&mockConf{
   140  		state: s.state,
   141  		changes: map[string]interface{}{
   142  			"resilience.vitality-hint": "unrelated,test-snap",
   143  		},
   144  	})
   145  	c.Assert(err, IsNil)
   146  	svcName := "snap.test-snap.foo.service"
   147  	c.Check(s.systemctlArgs, DeepEquals, [][]string{
   148  		{"daemon-reload"},
   149  		{"is-enabled", "snap.test-snap.foo.service"},
   150  		{"enable", "snap.test-snap.foo.service"},
   151  		{"start", "snap.test-snap.foo.service"},
   152  	})
   153  	svcPath := filepath.Join(dirs.SnapServicesDir, svcName)
   154  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   155  	if uc18 {
   156  		c.Check(svcPath, testutil.FileContains, "\nWants=usr-lib-snapd.mount\n")
   157  	}
   158  }
   159  
   160  func (s *vitalitySuite) TestConfigureVitalityWithQuotaGroup(c *C) {
   161  	r := servicestate.MockSystemdVersion(248)
   162  	defer r()
   163  
   164  	si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(1)}
   165  	snaptest.MockSnap(c, mockSnapWithService, si)
   166  	s.state.Lock()
   167  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   168  		Sequence: []*snap.SideInfo{si},
   169  		Current:  snap.R(1),
   170  		Active:   true,
   171  		SnapType: "app",
   172  	})
   173  
   174  	tr := config.NewTransaction(s.state)
   175  	tr.Set("core", "experimental.quota-groups", true)
   176  	tr.Commit()
   177  
   178  	// make a new quota group with this snap in it
   179  	err := servicestate.CreateQuota(s.state, "foogroup", "", []string{"test-snap"}, quantity.SizeMiB)
   180  	c.Assert(err, IsNil)
   181  
   182  	// CreateQuota uses systemctl, but we don't care about that here
   183  	s.systemctlArgs = nil
   184  
   185  	s.state.Unlock()
   186  
   187  	err = configcore.Run(&mockConf{
   188  		state: s.state,
   189  		changes: map[string]interface{}{
   190  			"resilience.vitality-hint": "unrelated,test-snap",
   191  		},
   192  	})
   193  	c.Assert(err, IsNil)
   194  	svcName := "snap.test-snap.foo.service"
   195  	c.Check(s.systemctlArgs, DeepEquals, [][]string{
   196  		{"daemon-reload"},
   197  		{"is-enabled", "snap.test-snap.foo.service"},
   198  		{"enable", "snap.test-snap.foo.service"},
   199  		{"start", "snap.test-snap.foo.service"},
   200  	})
   201  	svcPath := filepath.Join(dirs.SnapServicesDir, svcName)
   202  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   203  	c.Check(svcPath, testutil.FileContains, "\nSlice=snap.foogroup.slice\n")
   204  }
   205  
   206  func (s *vitalitySuite) TestConfigureVitalityHintTooMany(c *C) {
   207  	l := make([]string, 101)
   208  	for i := range l {
   209  		l[i] = strconv.Itoa(i)
   210  	}
   211  	manyStr := strings.Join(l, ",")
   212  	err := configcore.Run(&mockConf{
   213  		state: s.state,
   214  		changes: map[string]interface{}{
   215  			"resilience.vitality-hint": manyStr,
   216  		},
   217  	})
   218  	c.Assert(err, ErrorMatches, `cannot set more than 100 snaps in "resilience.vitality-hint": got 101`)
   219  }
   220  
   221  func (s *vitalitySuite) TestConfigureVitalityManySnaps(c *C) {
   222  	for _, snapName := range []string{"snap1", "snap2", "snap3"} {
   223  		si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)}
   224  		snaptest.MockSnap(c, mockSnapWithService, si)
   225  		s.state.Lock()
   226  		snapstate.Set(s.state, snapName, &snapstate.SnapState{
   227  			Sequence: []*snap.SideInfo{si},
   228  			Current:  snap.R(1),
   229  			Active:   true,
   230  			SnapType: "app",
   231  		})
   232  		s.state.Unlock()
   233  	}
   234  
   235  	// snap1,snap2,snap3
   236  	err := configcore.Run(&mockConf{
   237  		state: s.state,
   238  		changes: map[string]interface{}{
   239  			"resilience.vitality-hint": "snap1,snap2,snap3",
   240  		},
   241  	})
   242  	c.Assert(err, IsNil)
   243  	// test
   244  	svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service")
   245  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899\n")
   246  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service")
   247  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   248  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service")
   249  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-897\n")
   250  }
   251  
   252  func (s *vitalitySuite) TestConfigureVitalityManySnapsDelta(c *C) {
   253  	for _, snapName := range []string{"snap1", "snap2", "snap3"} {
   254  		si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)}
   255  		snaptest.MockSnap(c, mockSnapWithService, si)
   256  		s.state.Lock()
   257  		snapstate.Set(s.state, snapName, &snapstate.SnapState{
   258  			Sequence: []*snap.SideInfo{si},
   259  			Current:  snap.R(1),
   260  			Active:   true,
   261  			SnapType: "app",
   262  		})
   263  		s.state.Unlock()
   264  	}
   265  
   266  	// snap1,snap2,snap3 switch to snap3,snap1
   267  	err := configcore.Run(&mockConf{
   268  		state: s.state,
   269  		conf: map[string]interface{}{
   270  			"resilience.vitality-hint": "snap1,snap2,snap3",
   271  		},
   272  		changes: map[string]interface{}{
   273  			"resilience.vitality-hint": "snap3,snap1",
   274  		},
   275  	})
   276  	c.Assert(err, IsNil)
   277  	// test that snap1,snap3 got the new rank
   278  	svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service")
   279  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898")
   280  	// and that snap2 no longer has a OOMScoreAdjust setting
   281  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service")
   282  	c.Check(svcPath, Not(testutil.FileContains), "\nOOMScoreAdjust=")
   283  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service")
   284  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899\n")
   285  }
   286  
   287  func (s *vitalitySuite) TestConfigureVitalityManySnapsOneRemovedOneUnchanged(c *C) {
   288  	for _, snapName := range []string{"snap1", "snap2", "snap3"} {
   289  		si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)}
   290  		snaptest.MockSnap(c, mockSnapWithService, si)
   291  		s.state.Lock()
   292  		snapstate.Set(s.state, snapName, &snapstate.SnapState{
   293  			Sequence: []*snap.SideInfo{si},
   294  			Current:  snap.R(1),
   295  			Active:   true,
   296  			SnapType: "app",
   297  		})
   298  		s.state.Unlock()
   299  	}
   300  
   301  	// first run generates the snap1,snap2 configs
   302  	err := configcore.Run(&mockConf{
   303  		state: s.state,
   304  		changes: map[string]interface{}{
   305  			"resilience.vitality-hint": "snap1,snap2",
   306  		},
   307  	})
   308  	c.Assert(err, IsNil)
   309  	svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service")
   310  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899")
   311  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service")
   312  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   313  	c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap1.foo.service"})
   314  	c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap2.foo.service"})
   315  	s.systemctlArgs = nil
   316  
   317  	// now we change the configuration and set snap1,snap3
   318  	err = configcore.Run(&mockConf{
   319  		state: s.state,
   320  		conf: map[string]interface{}{
   321  			"resilience.vitality-hint": "snap1,snap2",
   322  		},
   323  		changes: map[string]interface{}{
   324  			"resilience.vitality-hint": "snap1,snap3",
   325  		},
   326  	})
   327  	c.Assert(err, IsNil)
   328  	// test that snap1 is unchanged
   329  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service")
   330  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899")
   331  	// and that snap2 no longer has a OOMScoreAdjust setting
   332  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service")
   333  	c.Check(svcPath, Not(testutil.FileContains), "\nOOMScoreAdjust=")
   334  	// snap3 got added
   335  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service")
   336  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   337  
   338  	// ensure that snap1 did not get started again (it is unchanged)
   339  	c.Check(s.systemctlArgs, Not(testutil.DeepContains), []string{"start", "snap.snap1.foo.service"})
   340  	// snap2 changed (no OOMScoreAdjust anymore) so needs restart
   341  	c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap2.foo.service"})
   342  	// snap3 changed so needs restart
   343  	c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap3.foo.service"})
   344  }
   345  
   346  func (s *vitalitySuite) TestConfigureVitalityNotActiveSnap(c *C) {
   347  	si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(1)}
   348  	snaptest.MockSnap(c, mockSnapWithService, si)
   349  	s.state.Lock()
   350  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   351  		Sequence: []*snap.SideInfo{si},
   352  		Current:  snap.R(1),
   353  		Active:   false,
   354  		SnapType: "app",
   355  	})
   356  	s.state.Unlock()
   357  
   358  	err := configcore.Run(&mockConf{
   359  		state: s.state,
   360  		changes: map[string]interface{}{
   361  			"resilience.vitality-hint": "unrelated,test-snap",
   362  		},
   363  	})
   364  	c.Assert(err, IsNil)
   365  	c.Check(s.systemctlArgs, HasLen, 0)
   366  }