github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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/dirs"
    30  	"github.com/snapcore/snapd/overlord/configstate/configcore"
    31  	"github.com/snapcore/snapd/overlord/snapstate"
    32  	"github.com/snapcore/snapd/snap"
    33  	"github.com/snapcore/snapd/snap/snaptest"
    34  	"github.com/snapcore/snapd/testutil"
    35  )
    36  
    37  type vitalitySuite struct {
    38  	configcoreSuite
    39  }
    40  
    41  var _ = Suite(&vitalitySuite{})
    42  
    43  func (s *vitalitySuite) TestConfigureVitalityUnhappyName(c *C) {
    44  	err := configcore.Run(&mockConf{
    45  		state: s.state,
    46  		changes: map[string]interface{}{
    47  			"resilience.vitality-hint": "-invalid-snap-name!yf",
    48  		},
    49  	})
    50  	c.Assert(err, ErrorMatches, `cannot set "resilience.vitality-hint": invalid snap name: ".*"`)
    51  }
    52  
    53  func (s *vitalitySuite) TestConfigureVitalityNoSnapd(c *C) {
    54  	err := configcore.Run(&mockConf{
    55  		state: s.state,
    56  		changes: map[string]interface{}{
    57  			"resilience.vitality-hint": "snapd",
    58  		},
    59  	})
    60  	c.Assert(err, ErrorMatches, `cannot set "resilience.vitality-hint": snapd snap vitality cannot be changed`)
    61  }
    62  
    63  func (s *vitalitySuite) TestConfigureVitalityhappyName(c *C) {
    64  	err := configcore.Run(&mockConf{
    65  		state: s.state,
    66  		changes: map[string]interface{}{
    67  			"resilience.vitality-hint": "valid-snapname",
    68  		},
    69  	})
    70  	c.Assert(err, IsNil)
    71  	// no snap named "valid-snapname" is installed, so no systemd action
    72  	c.Check(s.systemctlArgs, HasLen, 0)
    73  }
    74  
    75  var mockSnapWithService = `name: test-snap
    76  version: 1.0
    77  apps:
    78   foo:
    79    daemon: simple
    80  `
    81  
    82  func (s *vitalitySuite) TestConfigureVitalityWithValidSnap(c *C) {
    83  	si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(1)}
    84  	snaptest.MockSnap(c, mockSnapWithService, si)
    85  	s.state.Lock()
    86  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
    87  		Sequence: []*snap.SideInfo{si},
    88  		Current:  snap.R(1),
    89  		Active:   true,
    90  		SnapType: "app",
    91  	})
    92  	s.state.Unlock()
    93  
    94  	err := configcore.Run(&mockConf{
    95  		state: s.state,
    96  		changes: map[string]interface{}{
    97  			"resilience.vitality-hint": "unrelated,test-snap",
    98  		},
    99  	})
   100  	c.Assert(err, IsNil)
   101  	svcName := "snap.test-snap.foo.service"
   102  	c.Check(s.systemctlArgs, DeepEquals, [][]string{
   103  		{"is-enabled", "snap.test-snap.foo.service"},
   104  		{"enable", "snap.test-snap.foo.service"},
   105  		{"daemon-reload"},
   106  		{"is-enabled", "snap.test-snap.foo.service"},
   107  		{"start", "snap.test-snap.foo.service"},
   108  	})
   109  	svcPath := filepath.Join(dirs.SnapServicesDir, svcName)
   110  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   111  }
   112  
   113  func (s *vitalitySuite) TestConfigureVitalityHintTooMany(c *C) {
   114  	l := make([]string, 101)
   115  	for i := range l {
   116  		l[i] = strconv.Itoa(i)
   117  	}
   118  	manyStr := strings.Join(l, ",")
   119  	err := configcore.Run(&mockConf{
   120  		state: s.state,
   121  		changes: map[string]interface{}{
   122  			"resilience.vitality-hint": manyStr,
   123  		},
   124  	})
   125  	c.Assert(err, ErrorMatches, `cannot set more than 100 snaps in "resilience.vitality-hint": got 101`)
   126  }
   127  
   128  func (s *vitalitySuite) TestConfigureVitalityManySnaps(c *C) {
   129  	for _, snapName := range []string{"snap1", "snap2", "snap3"} {
   130  		si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)}
   131  		snaptest.MockSnap(c, mockSnapWithService, si)
   132  		s.state.Lock()
   133  		snapstate.Set(s.state, snapName, &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  
   142  	// snap1,snap2,snap3
   143  	err := configcore.Run(&mockConf{
   144  		state: s.state,
   145  		changes: map[string]interface{}{
   146  			"resilience.vitality-hint": "snap1,snap2,snap3",
   147  		},
   148  	})
   149  	c.Assert(err, IsNil)
   150  	// test
   151  	svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service")
   152  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899\n")
   153  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service")
   154  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   155  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service")
   156  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-897\n")
   157  }
   158  
   159  func (s *vitalitySuite) TestConfigureVitalityManySnapsDelta(c *C) {
   160  	for _, snapName := range []string{"snap1", "snap2", "snap3"} {
   161  		si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)}
   162  		snaptest.MockSnap(c, mockSnapWithService, si)
   163  		s.state.Lock()
   164  		snapstate.Set(s.state, snapName, &snapstate.SnapState{
   165  			Sequence: []*snap.SideInfo{si},
   166  			Current:  snap.R(1),
   167  			Active:   true,
   168  			SnapType: "app",
   169  		})
   170  		s.state.Unlock()
   171  	}
   172  
   173  	// snap1,snap2,snap3 switch to snap3,snap1
   174  	err := configcore.Run(&mockConf{
   175  		state: s.state,
   176  		conf: map[string]interface{}{
   177  			"resilience.vitality-hint": "snap1,snap2,snap3",
   178  		},
   179  		changes: map[string]interface{}{
   180  			"resilience.vitality-hint": "snap3,snap1",
   181  		},
   182  	})
   183  	c.Assert(err, IsNil)
   184  	// test that snap1,snap3 got the new rank
   185  	svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service")
   186  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898")
   187  	// and that snap2 no longer has a OOMScoreAdjust setting
   188  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service")
   189  	c.Check(svcPath, Not(testutil.FileContains), "\nOOMScoreAdjust=")
   190  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service")
   191  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899\n")
   192  }
   193  
   194  func (s *vitalitySuite) TestConfigureVitalityManySnapsOneRemovedOneUnchanged(c *C) {
   195  	for _, snapName := range []string{"snap1", "snap2", "snap3"} {
   196  		si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)}
   197  		snaptest.MockSnap(c, mockSnapWithService, si)
   198  		s.state.Lock()
   199  		snapstate.Set(s.state, snapName, &snapstate.SnapState{
   200  			Sequence: []*snap.SideInfo{si},
   201  			Current:  snap.R(1),
   202  			Active:   true,
   203  			SnapType: "app",
   204  		})
   205  		s.state.Unlock()
   206  	}
   207  
   208  	// first run generates the snap1,snap2 configs
   209  	err := configcore.Run(&mockConf{
   210  		state: s.state,
   211  		changes: map[string]interface{}{
   212  			"resilience.vitality-hint": "snap1,snap2",
   213  		},
   214  	})
   215  	c.Assert(err, IsNil)
   216  	svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service")
   217  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899")
   218  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service")
   219  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   220  	c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap1.foo.service"})
   221  	c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap2.foo.service"})
   222  	s.systemctlArgs = nil
   223  
   224  	// now we change the configuration and set snap1,snap3
   225  	err = configcore.Run(&mockConf{
   226  		state: s.state,
   227  		conf: map[string]interface{}{
   228  			"resilience.vitality-hint": "snap1,snap2",
   229  		},
   230  		changes: map[string]interface{}{
   231  			"resilience.vitality-hint": "snap1,snap3",
   232  		},
   233  	})
   234  	c.Assert(err, IsNil)
   235  	// test that snap1 is unchanged
   236  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service")
   237  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899")
   238  	// and that snap2 no longer has a OOMScoreAdjust setting
   239  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service")
   240  	c.Check(svcPath, Not(testutil.FileContains), "\nOOMScoreAdjust=")
   241  	// snap3 got added
   242  	svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service")
   243  	c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n")
   244  
   245  	// ensure that snap1 did not get started again (it is unchanged)
   246  	c.Check(s.systemctlArgs, Not(testutil.DeepContains), []string{"start", "snap.snap1.foo.service"})
   247  	// snap2 changed (no OOMScoreAdjust anymore) so needs restart
   248  	c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap2.foo.service"})
   249  	// snap3 changed so needs restart
   250  	c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap3.foo.service"})
   251  }
   252  
   253  func (s *vitalitySuite) TestConfigureVitalityNotActiveSnap(c *C) {
   254  	si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(1)}
   255  	snaptest.MockSnap(c, mockSnapWithService, si)
   256  	s.state.Lock()
   257  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   258  		Sequence: []*snap.SideInfo{si},
   259  		Current:  snap.R(1),
   260  		Active:   false,
   261  		SnapType: "app",
   262  	})
   263  	s.state.Unlock()
   264  
   265  	err := configcore.Run(&mockConf{
   266  		state: s.state,
   267  		changes: map[string]interface{}{
   268  			"resilience.vitality-hint": "unrelated,test-snap",
   269  		},
   270  	})
   271  	c.Assert(err, IsNil)
   272  	c.Check(s.systemctlArgs, HasLen, 0)
   273  }