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