gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/servicestate/servicestate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2021 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 servicestate_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/client"
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/gadget/quantity"
    34  	"github.com/snapcore/snapd/overlord/configstate/config"
    35  	"github.com/snapcore/snapd/overlord/servicestate"
    36  	"github.com/snapcore/snapd/overlord/servicestate/servicestatetest"
    37  	"github.com/snapcore/snapd/overlord/state"
    38  	"github.com/snapcore/snapd/snap"
    39  	"github.com/snapcore/snapd/snap/quota"
    40  	"github.com/snapcore/snapd/systemd"
    41  	"github.com/snapcore/snapd/testutil"
    42  	"github.com/snapcore/snapd/wrappers"
    43  )
    44  
    45  type statusDecoratorSuite struct{}
    46  
    47  var _ = Suite(&statusDecoratorSuite{})
    48  
    49  func (s *statusDecoratorSuite) TestDecorateWithStatus(c *C) {
    50  	dirs.SetRootDir(c.MkDir())
    51  	defer dirs.SetRootDir("")
    52  	snp := &snap.Info{
    53  		SideInfo: snap.SideInfo{
    54  			RealName: "foo",
    55  			Revision: snap.R(1),
    56  		},
    57  	}
    58  	err := os.MkdirAll(snp.MountDir(), 0755)
    59  	c.Assert(err, IsNil)
    60  	err = os.Symlink(snp.Revision.String(), filepath.Join(filepath.Dir(snp.MountDir()), "current"))
    61  	c.Assert(err, IsNil)
    62  
    63  	disabled := false
    64  	r := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) {
    65  		switch args[0] {
    66  		case "show":
    67  			c.Assert(args[0], Equals, "show")
    68  			unit := args[2]
    69  			activeState, unitState := "active", "enabled"
    70  			if disabled {
    71  				activeState = "inactive"
    72  				unitState = "disabled"
    73  			}
    74  			if strings.HasSuffix(unit, ".timer") || strings.HasSuffix(unit, ".socket") {
    75  				return []byte(fmt.Sprintf(`Id=%s
    76  ActiveState=%s
    77  UnitFileState=%s
    78  `, args[2], activeState, unitState)), nil
    79  			} else {
    80  				return []byte(fmt.Sprintf(`Id=%s
    81  Type=simple
    82  ActiveState=%s
    83  UnitFileState=%s
    84  `, args[2], activeState, unitState)), nil
    85  			}
    86  		case "--user":
    87  			c.Assert(args[1], Equals, "--global")
    88  			c.Assert(args[2], Equals, "is-enabled")
    89  			unitState := "enabled\n"
    90  			if disabled {
    91  				unitState = "disabled\n"
    92  			}
    93  			return bytes.Repeat([]byte(unitState), len(args)-3), nil
    94  		default:
    95  			c.Errorf("unexpected systemctl command: %v", args)
    96  			return nil, fmt.Errorf("should not be reached")
    97  		}
    98  	})
    99  	defer r()
   100  
   101  	sd := servicestate.NewStatusDecorator(nil)
   102  
   103  	// not a service
   104  	app := &client.AppInfo{
   105  		Snap: "foo",
   106  		Name: "app",
   107  	}
   108  	snapApp := &snap.AppInfo{Snap: snp, Name: "app"}
   109  
   110  	err = sd.DecorateWithStatus(app, snapApp)
   111  	c.Assert(err, IsNil)
   112  
   113  	for _, enabled := range []bool{true, false} {
   114  		disabled = !enabled
   115  
   116  		// service only
   117  		app = &client.AppInfo{
   118  			Snap:   snp.InstanceName(),
   119  			Name:   "svc",
   120  			Daemon: "simple",
   121  		}
   122  		snapApp = &snap.AppInfo{
   123  			Snap:        snp,
   124  			Name:        "svc",
   125  			Daemon:      "simple",
   126  			DaemonScope: snap.SystemDaemon,
   127  		}
   128  
   129  		err = sd.DecorateWithStatus(app, snapApp)
   130  		c.Assert(err, IsNil)
   131  		c.Check(app.Active, Equals, enabled)
   132  		c.Check(app.Enabled, Equals, enabled)
   133  
   134  		// service  + timer
   135  		app = &client.AppInfo{
   136  			Snap:   snp.InstanceName(),
   137  			Name:   "svc",
   138  			Daemon: "simple",
   139  		}
   140  		snapApp = &snap.AppInfo{
   141  			Snap:        snp,
   142  			Name:        "svc",
   143  			Daemon:      "simple",
   144  			DaemonScope: snap.SystemDaemon,
   145  		}
   146  		snapApp.Timer = &snap.TimerInfo{
   147  			App:   snapApp,
   148  			Timer: "10:00",
   149  		}
   150  
   151  		err = sd.DecorateWithStatus(app, snapApp)
   152  		c.Assert(err, IsNil)
   153  		c.Check(app.Active, Equals, enabled)
   154  		c.Check(app.Enabled, Equals, enabled)
   155  		c.Check(app.Activators, DeepEquals, []client.AppActivator{
   156  			{Name: "svc", Type: "timer", Active: enabled, Enabled: enabled},
   157  		})
   158  
   159  		// service with socket
   160  		app = &client.AppInfo{
   161  			Snap:   snp.InstanceName(),
   162  			Name:   "svc",
   163  			Daemon: "simple",
   164  		}
   165  		snapApp = &snap.AppInfo{
   166  			Snap:        snp,
   167  			Name:        "svc",
   168  			Daemon:      "simple",
   169  			DaemonScope: snap.SystemDaemon,
   170  		}
   171  		snapApp.Sockets = map[string]*snap.SocketInfo{
   172  			"socket1": {
   173  				App:          snapApp,
   174  				Name:         "socket1",
   175  				ListenStream: "a.socket",
   176  			},
   177  		}
   178  
   179  		err = sd.DecorateWithStatus(app, snapApp)
   180  		c.Assert(err, IsNil)
   181  		c.Check(app.Active, Equals, enabled)
   182  		c.Check(app.Enabled, Equals, enabled)
   183  		c.Check(app.Activators, DeepEquals, []client.AppActivator{
   184  			{Name: "socket1", Type: "socket", Active: enabled, Enabled: enabled},
   185  		})
   186  
   187  		// service with D-Bus activation
   188  		app = &client.AppInfo{
   189  			Snap:   snp.InstanceName(),
   190  			Name:   "svc",
   191  			Daemon: "simple",
   192  		}
   193  		snapApp = &snap.AppInfo{
   194  			Snap:        snp,
   195  			Name:        "svc",
   196  			Daemon:      "simple",
   197  			DaemonScope: snap.SystemDaemon,
   198  		}
   199  		snapApp.ActivatesOn = []*snap.SlotInfo{
   200  			{
   201  				Snap:      snp,
   202  				Name:      "dbus-slot",
   203  				Interface: "dbus",
   204  				Attrs: map[string]interface{}{
   205  					"bus":  "system",
   206  					"name": "org.example.Svc",
   207  				},
   208  			},
   209  		}
   210  
   211  		err = sd.DecorateWithStatus(app, snapApp)
   212  		c.Assert(err, IsNil)
   213  		c.Check(app.Active, Equals, enabled)
   214  		c.Check(app.Enabled, Equals, enabled)
   215  		c.Check(app.Activators, DeepEquals, []client.AppActivator{
   216  			{Name: "org.example.Svc", Type: "dbus", Active: true, Enabled: true},
   217  		})
   218  
   219  		// No state is currently extracted for user daemons
   220  		app = &client.AppInfo{
   221  			Snap:   snp.InstanceName(),
   222  			Name:   "svc",
   223  			Daemon: "simple",
   224  		}
   225  		snapApp = &snap.AppInfo{
   226  			Snap:        snp,
   227  			Name:        "svc",
   228  			Daemon:      "simple",
   229  			DaemonScope: snap.UserDaemon,
   230  		}
   231  		snapApp.Sockets = map[string]*snap.SocketInfo{
   232  			"socket1": {
   233  				App:          snapApp,
   234  				Name:         "socket1",
   235  				ListenStream: "a.socket",
   236  			},
   237  		}
   238  		snapApp.Timer = &snap.TimerInfo{
   239  			App:   snapApp,
   240  			Timer: "10:00",
   241  		}
   242  		snapApp.ActivatesOn = []*snap.SlotInfo{
   243  			{
   244  				Snap:      snp,
   245  				Name:      "dbus-slot",
   246  				Interface: "dbus",
   247  				Attrs: map[string]interface{}{
   248  					"bus":  "session",
   249  					"name": "org.example.Svc",
   250  				},
   251  			},
   252  		}
   253  
   254  		err = sd.DecorateWithStatus(app, snapApp)
   255  		c.Assert(err, IsNil)
   256  		c.Check(app.Active, Equals, false)
   257  		c.Check(app.Enabled, Equals, enabled)
   258  		c.Check(app.Activators, DeepEquals, []client.AppActivator{
   259  			{Name: "socket1", Type: "socket", Active: false, Enabled: enabled},
   260  			{Name: "svc", Type: "timer", Active: false, Enabled: enabled},
   261  			{Name: "org.example.Svc", Type: "dbus", Active: true, Enabled: true},
   262  		})
   263  	}
   264  }
   265  
   266  type snapServiceOptionsSuite struct {
   267  	testutil.BaseTest
   268  	state *state.State
   269  }
   270  
   271  var _ = Suite(&snapServiceOptionsSuite{})
   272  
   273  func (s *snapServiceOptionsSuite) SetUpTest(c *C) {
   274  	s.BaseTest.SetUpTest(c)
   275  	s.state = state.New(nil)
   276  }
   277  
   278  func (s *snapServiceOptionsSuite) TestSnapServiceOptionsVitalityRank(c *C) {
   279  	st := s.state
   280  	st.Lock()
   281  	defer st.Unlock()
   282  	t := config.NewTransaction(st)
   283  	err := t.Set("core", "resilience.vitality-hint", "bar,foo")
   284  	c.Assert(err, IsNil)
   285  	t.Commit()
   286  
   287  	opts, err := servicestate.SnapServiceOptions(st, "foo", nil)
   288  	c.Assert(err, IsNil)
   289  	c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{
   290  		VitalityRank: 2,
   291  	})
   292  	opts, err = servicestate.SnapServiceOptions(st, "bar", nil)
   293  	c.Assert(err, IsNil)
   294  	c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{
   295  		VitalityRank: 1,
   296  	})
   297  	opts, err = servicestate.SnapServiceOptions(st, "unknown", nil)
   298  	c.Assert(err, IsNil)
   299  	c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{
   300  		VitalityRank: 0,
   301  	})
   302  }
   303  
   304  func (s *snapServiceOptionsSuite) TestSnapServiceOptionsQuotaGroups(c *C) {
   305  	st := s.state
   306  	st.Lock()
   307  	defer st.Unlock()
   308  
   309  	// make a quota group
   310  	grp, err := quota.NewGroup("foogroup", quantity.SizeGiB)
   311  	c.Assert(err, IsNil)
   312  
   313  	grp.Snaps = []string{"foosnap"}
   314  
   315  	// add it into the state
   316  	newGrps, err := servicestatetest.PatchQuotas(st, grp)
   317  	c.Assert(err, IsNil)
   318  	c.Assert(newGrps, DeepEquals, map[string]*quota.Group{
   319  		"foogroup": grp,
   320  	})
   321  
   322  	opts, err := servicestate.SnapServiceOptions(st, "foosnap", nil)
   323  	c.Assert(err, IsNil)
   324  	c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{
   325  		QuotaGroup: grp,
   326  	})
   327  
   328  	// save the current state of the quota group before modifying it to prove
   329  	// that the group caching works
   330  	grps, err := servicestate.AllQuotas(st)
   331  	c.Assert(err, IsNil)
   332  
   333  	// modify state to use an instance name instead now
   334  	grp.Snaps = []string{"foosnap_instance"}
   335  	newGrps, err = servicestatetest.PatchQuotas(st, grp)
   336  	c.Assert(err, IsNil)
   337  	c.Assert(newGrps, DeepEquals, map[string]*quota.Group{
   338  		"foogroup": grp,
   339  	})
   340  
   341  	// we can still get the quota group using the local map we got before
   342  	// modifying state
   343  	opts, err = servicestate.SnapServiceOptions(st, "foosnap", grps)
   344  	c.Assert(err, IsNil)
   345  	grp.Snaps = []string{"foosnap"}
   346  	c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{
   347  		QuotaGroup: grp,
   348  	})
   349  
   350  	// but using state produces nothing for the non-instance name snap
   351  	opts, err = servicestate.SnapServiceOptions(st, "foosnap", nil)
   352  	c.Assert(err, IsNil)
   353  	c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{})
   354  
   355  	// but it does work with instance names
   356  	grp.Snaps = []string{"foosnap_instance"}
   357  	opts, err = servicestate.SnapServiceOptions(st, "foosnap_instance", nil)
   358  	c.Assert(err, IsNil)
   359  	c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{
   360  		QuotaGroup: grp,
   361  	})
   362  
   363  	// works with vitality rank for the snap too
   364  	t := config.NewTransaction(st)
   365  	err = t.Set("core", "resilience.vitality-hint", "bar,foosnap_instance")
   366  	c.Assert(err, IsNil)
   367  	t.Commit()
   368  
   369  	opts, err = servicestate.SnapServiceOptions(st, "foosnap_instance", nil)
   370  	c.Assert(err, IsNil)
   371  	c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{
   372  		VitalityRank: 2,
   373  		QuotaGroup:   grp,
   374  	})
   375  }