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