github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/service/systemd/service_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package systemd_test
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"strings"
    11  
    12  	"github.com/coreos/go-systemd/v22/dbus"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names/v5"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/utils/v3/exec"
    17  	"github.com/juju/utils/v3/shell"
    18  	"go.uber.org/mock/gomock"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/core/paths"
    22  	"github.com/juju/juju/service"
    23  	"github.com/juju/juju/service/common"
    24  	"github.com/juju/juju/service/systemd"
    25  	systemdtesting "github.com/juju/juju/service/systemd/testing"
    26  	coretesting "github.com/juju/juju/testing"
    27  )
    28  
    29  var renderer = &shell.BashRenderer{}
    30  
    31  const confStr = `
    32  [Unit]
    33  Description=juju agent for %s
    34  After=syslog.target
    35  After=network.target
    36  After=systemd-user-sessions.service
    37  
    38  [Service]
    39  ExecStart=%s
    40  Restart=on-failure
    41  
    42  [Install]
    43  WantedBy=multi-user.target
    44  
    45  `
    46  
    47  const jujud = "/var/lib/juju/bin/jujud"
    48  
    49  var listCmdArg = exec.RunParams{
    50  	Commands: `/bin/systemctl list-unit-files --no-legend --no-page -l -t service | grep -o -P '^\w[\S]*(?=\.service)'`,
    51  }
    52  
    53  var errFailure = errors.New("you-failed")
    54  
    55  type initSystemSuite struct {
    56  	coretesting.BaseSuite
    57  
    58  	dataDir string
    59  	ch      chan string
    60  	dBus    *MockDBusAPI
    61  	fops    *MockFileSystemOps
    62  	exec    *systemd.MockShimExec
    63  
    64  	name string
    65  	tag  names.Tag
    66  	conf common.Conf
    67  }
    68  
    69  var _ = gc.Suite(&initSystemSuite{})
    70  
    71  func (s *initSystemSuite) SetUpTest(c *gc.C) {
    72  	s.BaseSuite.SetUpTest(c)
    73  
    74  	s.dataDir = paths.DataDir(paths.OSUnixLike)
    75  
    76  	// Set up the service config.
    77  	tagStr := "machine-0"
    78  	tag, err := names.ParseTag(tagStr)
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	s.tag = tag
    81  	s.name = "jujud-" + tagStr
    82  	s.conf = common.Conf{
    83  		Desc:      "juju agent for " + tagStr,
    84  		ExecStart: jujud + " " + tagStr,
    85  	}
    86  }
    87  
    88  func (s *initSystemSuite) patch(c *gc.C) *gomock.Controller {
    89  	ctrl := gomock.NewController(c)
    90  
    91  	s.fops = NewMockFileSystemOps(ctrl)
    92  	s.dBus = NewMockDBusAPI(ctrl)
    93  
    94  	s.ch = systemd.PatchNewChan(s)
    95  	s.exec = systemd.PatchExec(s, ctrl)
    96  
    97  	return ctrl
    98  }
    99  
   100  func (s *initSystemSuite) newService(c *gc.C) *systemd.Service {
   101  	var fac systemd.DBusAPIFactory
   102  	if s.dBus == nil {
   103  		fac = func() (systemd.DBusAPI, error) {
   104  			return nil, errors.New("Prior call to initSystemSuite.patch required before attempting DBusAPI connection")
   105  		}
   106  	} else {
   107  		fac = func() (systemd.DBusAPI, error) { return s.dBus, nil }
   108  	}
   109  
   110  	svc, err := systemd.NewService(s.name, s.conf, systemd.EtcSystemdDir, fac, s.fops, renderer.Join(s.dataDir, "init"))
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	return svc
   113  }
   114  
   115  func (s *initSystemSuite) expectConf(c *gc.C, conf common.Conf) *gomock.Call {
   116  	data, err := systemd.Serialize(s.name, conf, renderer)
   117  	c.Assert(err, jc.ErrorIsNil)
   118  
   119  	return s.exec.EXPECT().RunCommands(
   120  		exec.RunParams{
   121  			Commands: "cat /etc/systemd/system/jujud-machine-0.service",
   122  		},
   123  	).Return(&exec.ExecResponse{Stdout: data}, nil)
   124  }
   125  
   126  func (s *initSystemSuite) newConfStr(name string) string {
   127  	return s.newConfStrCmd(name, "")
   128  }
   129  
   130  func (s *initSystemSuite) newConfStrCmd(name, cmd string) string {
   131  	tag := name[len("jujud-"):]
   132  	if cmd == "" {
   133  		cmd = jujud + " " + tag
   134  	}
   135  	return fmt.Sprintf(confStr[1:], tag, cmd)
   136  }
   137  
   138  func (s *initSystemSuite) newConfStrEnv(name, env string) string {
   139  	const replace = "[Service]\n"
   140  	result := s.newConfStr(name)
   141  	result = strings.Replace(
   142  		result, replace,
   143  		fmt.Sprintf("%sEnvironment=%s\n", replace, env),
   144  		1,
   145  	)
   146  	return result
   147  }
   148  
   149  func (s *initSystemSuite) TestListServices(c *gc.C) {
   150  	ctrl := s.patch(c)
   151  	defer ctrl.Finish()
   152  
   153  	s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{
   154  		Stdout: []byte("jujud-machine-0\njujud-unit-wordpress-0"),
   155  	}, nil)
   156  
   157  	services, err := systemd.ListServices()
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	c.Check(services, jc.SameContents, []string{"jujud-machine-0", "jujud-unit-wordpress-0"})
   160  }
   161  
   162  func (s *initSystemSuite) TestListServicesEmpty(c *gc.C) {
   163  	ctrl := s.patch(c)
   164  	defer ctrl.Finish()
   165  
   166  	s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{Stdout: []byte("")}, nil)
   167  
   168  	services, err := systemd.ListServices()
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	c.Check(services, gc.HasLen, 0)
   171  }
   172  
   173  func (s *initSystemSuite) TestNewService(c *gc.C) {
   174  	svc := s.newService(c)
   175  	c.Check(svc.Service, jc.DeepEquals, common.Service{Name: s.name, Conf: s.conf})
   176  	c.Check(svc.ConfName, gc.Equals, s.name+".service")
   177  	c.Check(svc.UnitName, gc.Equals, s.name+".service")
   178  	c.Check(svc.DirName, gc.Equals, systemd.EtcSystemdDir)
   179  }
   180  
   181  func (s *initSystemSuite) TestNewServiceLogfile(c *gc.C) {
   182  	s.conf.Logfile = "/var/log/juju/machine-0.log"
   183  	svc := s.newService(c)
   184  
   185  	user, group := paths.SyslogUserGroup()
   186  	script := `
   187  #!/usr/bin/env bash
   188  
   189  # Set up logging.
   190  touch '/var/log/juju/machine-0.log'
   191  chown `[1:] + user + `:` + group + ` '/var/log/juju/machine-0.log'
   192  chmod 0640 '/var/log/juju/machine-0.log'
   193  exec >> '/var/log/juju/machine-0.log'
   194  exec 2>&1
   195  
   196  # Run the script.
   197  ` + jujud + " machine-0"
   198  
   199  	c.Check(svc.Service, jc.DeepEquals, common.Service{
   200  		Name: s.name,
   201  		Conf: common.Conf{
   202  			Desc:      s.conf.Desc,
   203  			ExecStart: path.Join(svc.DirName, svc.Name()+"-exec-start.sh"),
   204  			Logfile:   "/var/log/juju/machine-0.log",
   205  		},
   206  	})
   207  
   208  	c.Check(svc.ConfName, gc.Equals, s.name+".service")
   209  	c.Check(svc.UnitName, gc.Equals, s.name+".service")
   210  	c.Check(svc.DirName, gc.Equals, systemd.EtcSystemdDir)
   211  
   212  	// This gives us a more readable output if they aren't equal.
   213  	c.Check(string(svc.Script), gc.Equals, script)
   214  	c.Check(strings.Split(string(svc.Script), "\n"), jc.DeepEquals, strings.Split(script, "\n"))
   215  }
   216  
   217  func (s *initSystemSuite) TestNewServiceEmptyConf(c *gc.C) {
   218  	svc, err := systemd.NewService(
   219  		s.name, common.Conf{}, systemd.EtcSystemdDir, systemd.NewDBusAPI, s.fops, renderer.Join(s.dataDir, "init"))
   220  	c.Assert(err, gc.IsNil)
   221  	c.Check(svc.Service, jc.DeepEquals, common.Service{Name: s.name})
   222  	c.Check(svc.ConfName, gc.Equals, s.name+".service")
   223  	c.Check(svc.UnitName, gc.Equals, s.name+".service")
   224  	c.Check(svc.DirName, gc.Equals, systemd.EtcSystemdDir)
   225  }
   226  
   227  func (s *initSystemSuite) TestNewServiceBasic(c *gc.C) {
   228  	s.conf.ExecStart = "/path/to/some/other/command"
   229  	svc := s.newService(c)
   230  	c.Check(svc.Service, jc.DeepEquals, common.Service{Name: s.name, Conf: s.conf})
   231  	c.Check(svc.ConfName, gc.Equals, s.name+".service")
   232  	c.Check(svc.UnitName, gc.Equals, s.name+".service")
   233  	c.Check(svc.DirName, gc.Equals, systemd.EtcSystemdDir)
   234  }
   235  
   236  func (s *initSystemSuite) TestNewServiceExtraScript(c *gc.C) {
   237  	s.conf.ExtraScript = "'/path/to/another/command'"
   238  	svc := s.newService(c)
   239  
   240  	script := `
   241  #!/usr/bin/env bash
   242  
   243  '/path/to/another/command'
   244  `[1:] + jujud + " machine-0"
   245  
   246  	c.Check(svc.Service, jc.DeepEquals, common.Service{
   247  		Name: s.name,
   248  		Conf: common.Conf{
   249  			Desc:      s.conf.Desc,
   250  			ExecStart: path.Join(svc.DirName, svc.Name()+"-exec-start.sh"),
   251  		},
   252  	})
   253  
   254  	c.Check(svc.ConfName, gc.Equals, s.name+".service")
   255  	c.Check(svc.UnitName, gc.Equals, s.name+".service")
   256  	c.Check(svc.DirName, gc.Equals, systemd.EtcSystemdDir)
   257  	c.Check(string(svc.Script), gc.Equals, script)
   258  }
   259  
   260  func (s *initSystemSuite) TestNewServiceMultiLine(c *gc.C) {
   261  	s.conf.ExecStart = "a\nb\nc"
   262  	svc := s.newService(c)
   263  
   264  	script := `
   265  #!/usr/bin/env bash
   266  
   267  a
   268  b
   269  c`[1:]
   270  
   271  	c.Check(svc.Service, jc.DeepEquals, common.Service{
   272  		Name: s.name,
   273  		Conf: common.Conf{
   274  			Desc:      s.conf.Desc,
   275  			ExecStart: path.Join(svc.DirName, svc.Name()+"-exec-start.sh"),
   276  		},
   277  	})
   278  
   279  	c.Check(svc.ConfName, gc.Equals, s.name+".service")
   280  	c.Check(svc.UnitName, gc.Equals, s.name+".service")
   281  	c.Check(svc.DirName, gc.Equals, systemd.EtcSystemdDir)
   282  
   283  	// This gives us a more readable output if they aren't equal.
   284  	c.Check(string(svc.Script), gc.Equals, script)
   285  }
   286  
   287  func (s *initSystemSuite) TestInstalledTrue(c *gc.C) {
   288  	ctrl := s.patch(c)
   289  	defer ctrl.Finish()
   290  
   291  	s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{
   292  		Stdout: []byte("jujud-machine-0\njuju-mongod"),
   293  	}, nil)
   294  
   295  	installed, err := s.newService(c).Installed()
   296  	c.Assert(err, jc.ErrorIsNil)
   297  	c.Check(installed, jc.IsTrue)
   298  }
   299  
   300  func (s *initSystemSuite) TestInstalledFalse(c *gc.C) {
   301  	ctrl := s.patch(c)
   302  	defer ctrl.Finish()
   303  
   304  	s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{
   305  		Stdout: []byte("some-other-service"),
   306  	}, nil)
   307  
   308  	installed, err := s.newService(c).Installed()
   309  	c.Assert(err, jc.ErrorIsNil)
   310  	c.Check(installed, jc.IsFalse)
   311  }
   312  
   313  func (s *initSystemSuite) TestInstalledError(c *gc.C) {
   314  	ctrl := s.patch(c)
   315  	defer ctrl.Finish()
   316  
   317  	s.exec.EXPECT().RunCommands(listCmdArg).Return(nil, errFailure)
   318  
   319  	installed, err := s.newService(c).Installed()
   320  	c.Assert(errors.Cause(err), gc.Equals, errFailure)
   321  	c.Check(installed, jc.IsFalse)
   322  }
   323  
   324  func (s *initSystemSuite) TestExistsTrue(c *gc.C) {
   325  	ctrl := s.patch(c)
   326  	defer ctrl.Finish()
   327  	s.expectConf(c, s.conf)
   328  
   329  	exists, err := s.newService(c).Exists()
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	c.Check(exists, jc.IsTrue)
   332  }
   333  
   334  func (s *initSystemSuite) TestExistsFalse(c *gc.C) {
   335  	ctrl := s.patch(c)
   336  	defer ctrl.Finish()
   337  
   338  	// We force the systemd API to return a slightly different conf.
   339  	// In this case we simply set Conf.Env, which s.conf does not set.
   340  	// This causes Service.Exists to return false.
   341  	s.expectConf(c, common.Conf{
   342  		Desc:      s.conf.Desc,
   343  		ExecStart: s.conf.ExecStart,
   344  		Env:       map[string]string{"a": "b"},
   345  	})
   346  
   347  	exists, err := s.newService(c).Exists()
   348  	c.Assert(err, jc.ErrorIsNil)
   349  	c.Check(exists, jc.IsFalse)
   350  }
   351  
   352  func (s *initSystemSuite) TestExistsError(c *gc.C) {
   353  	ctrl := s.patch(c)
   354  	defer ctrl.Finish()
   355  
   356  	s.exec.EXPECT().RunCommands(
   357  		exec.RunParams{
   358  			Commands: "cat /etc/systemd/system/jujud-machine-0.service",
   359  		},
   360  	).Return(nil, errFailure)
   361  
   362  	exists, err := s.newService(c).Exists()
   363  	c.Assert(errors.Cause(err), gc.Equals, errFailure)
   364  	c.Check(exists, jc.IsFalse)
   365  }
   366  
   367  func (s *initSystemSuite) TestExistsEmptyConf(c *gc.C) {
   368  	svc := s.newService(c)
   369  	svc.Service.Conf = common.Conf{}
   370  	_, err := svc.Exists()
   371  	c.Check(err, gc.ErrorMatches, `.*no conf expected.*`)
   372  }
   373  
   374  func (s *initSystemSuite) TestRunningTrue(c *gc.C) {
   375  	ctrl := s.patch(c)
   376  	defer ctrl.Finish()
   377  
   378  	gomock.InOrder(
   379  		s.dBus.EXPECT().ListUnits().Return([]dbus.UnitStatus{
   380  			{Name: "jujud-machine-0.service", LoadState: "loaded", ActiveState: "active"},
   381  			{Name: "juju-mongod.service", LoadState: "loaded", ActiveState: "active"},
   382  		}, nil),
   383  		s.dBus.EXPECT().Close(),
   384  	)
   385  
   386  	running, err := s.newService(c).Running()
   387  	c.Assert(err, jc.ErrorIsNil)
   388  	c.Check(running, jc.IsTrue)
   389  }
   390  
   391  func (s *initSystemSuite) TestRunningFalse(c *gc.C) {
   392  	ctrl := s.patch(c)
   393  	defer ctrl.Finish()
   394  
   395  	gomock.InOrder(
   396  		s.dBus.EXPECT().ListUnits().Return([]dbus.UnitStatus{
   397  			{Name: "jujud-machine-0.service", LoadState: "loaded", ActiveState: "inactive"},
   398  			{Name: "juju-mongod.service", LoadState: "loaded", ActiveState: "active"},
   399  		}, nil),
   400  		s.dBus.EXPECT().Close(),
   401  	)
   402  
   403  	running, err := s.newService(c).Running()
   404  	c.Assert(err, jc.ErrorIsNil)
   405  	c.Check(running, jc.IsFalse)
   406  }
   407  
   408  func (s *initSystemSuite) TestRunningNotEnabled(c *gc.C) {
   409  	ctrl := s.patch(c)
   410  	defer ctrl.Finish()
   411  
   412  	gomock.InOrder(
   413  		s.dBus.EXPECT().ListUnits().Return([]dbus.UnitStatus{
   414  			{Name: "random-thing.service", LoadState: "loaded", ActiveState: "active"},
   415  		}, nil),
   416  		s.dBus.EXPECT().Close(),
   417  	)
   418  
   419  	running, err := s.newService(c).Running()
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	c.Check(running, jc.IsFalse)
   422  }
   423  
   424  func (s *initSystemSuite) TestRunningError(c *gc.C) {
   425  	ctrl := s.patch(c)
   426  	defer ctrl.Finish()
   427  
   428  	gomock.InOrder(
   429  		s.dBus.EXPECT().ListUnits().Return(nil, errFailure),
   430  		s.dBus.EXPECT().Close(),
   431  	)
   432  
   433  	_, err := s.newService(c).Running()
   434  	c.Check(errors.Cause(err), gc.Equals, errFailure)
   435  }
   436  
   437  func (s *initSystemSuite) TestStart(c *gc.C) {
   438  	ctrl := s.patch(c)
   439  	defer ctrl.Finish()
   440  
   441  	svc := s.newService(c)
   442  
   443  	gomock.InOrder(
   444  		s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{
   445  			Stdout: []byte("jujud-machine-0\njuju-mongod"),
   446  		}, nil),
   447  		s.dBus.EXPECT().ListUnits().Return([]dbus.UnitStatus{
   448  			{Name: svc.UnitName, LoadState: "loaded", ActiveState: "inactive"},
   449  		}, nil),
   450  		s.dBus.EXPECT().Close(),
   451  
   452  		// Equality check for the channel fails here, so we use Any().
   453  		// We know this is safe, because we notify on the channel we got from
   454  		// the patched call and everything proceeds happily.
   455  		s.dBus.EXPECT().StartUnit(svc.UnitName, "fail", gomock.Any()).Return(1, nil).Do(
   456  			func(string, string, chan<- string) { s.ch <- "done" },
   457  		),
   458  		s.dBus.EXPECT().Close(),
   459  	)
   460  
   461  	err := svc.Start()
   462  	c.Assert(err, jc.ErrorIsNil)
   463  }
   464  
   465  func (s *initSystemSuite) TestStartAlreadyRunning(c *gc.C) {
   466  	ctrl := s.patch(c)
   467  	defer ctrl.Finish()
   468  
   469  	svc := s.newService(c)
   470  
   471  	gomock.InOrder(
   472  		s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{
   473  			Stdout: []byte("jujud-machine-0\njuju-mongod"),
   474  		}, nil),
   475  		s.dBus.EXPECT().ListUnits().Return([]dbus.UnitStatus{
   476  			{Name: svc.UnitName, LoadState: "loaded", ActiveState: "active"},
   477  		}, nil),
   478  		s.dBus.EXPECT().Close(),
   479  	)
   480  
   481  	err := svc.Start()
   482  	c.Assert(err, jc.ErrorIsNil)
   483  }
   484  
   485  func (s *initSystemSuite) TestStartNotInstalled(c *gc.C) {
   486  	ctrl := s.patch(c)
   487  	defer ctrl.Finish()
   488  
   489  	s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{Stdout: []byte("")}, nil)
   490  
   491  	err := s.newService(c).Start()
   492  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   493  }
   494  
   495  func (s *initSystemSuite) TestStop(c *gc.C) {
   496  	ctrl := s.patch(c)
   497  	defer ctrl.Finish()
   498  
   499  	svc := s.newService(c)
   500  
   501  	gomock.InOrder(
   502  		s.dBus.EXPECT().ListUnits().Return([]dbus.UnitStatus{
   503  			{Name: svc.UnitName, LoadState: "loaded", ActiveState: "active"},
   504  		}, nil),
   505  		s.dBus.EXPECT().Close(),
   506  
   507  		// Equality check for the channel fails here, so we use Any().
   508  		// We know this is safe, because we notify on the channel we got from
   509  		// the patched call and everything proceeds happily.
   510  		s.dBus.EXPECT().StopUnit(svc.UnitName, "fail", gomock.Any()).Return(1, nil).Do(
   511  			func(string, string, chan<- string) { s.ch <- "done" },
   512  		),
   513  		s.dBus.EXPECT().Close(),
   514  	)
   515  
   516  	err := svc.Stop()
   517  	c.Assert(err, jc.ErrorIsNil)
   518  }
   519  
   520  func (s *initSystemSuite) TestStopNotRunning(c *gc.C) {
   521  	ctrl := s.patch(c)
   522  	defer ctrl.Finish()
   523  
   524  	svc := s.newService(c)
   525  
   526  	gomock.InOrder(
   527  		s.dBus.EXPECT().ListUnits().Return([]dbus.UnitStatus{
   528  			{Name: svc.UnitName, LoadState: "loaded", ActiveState: "inactive"},
   529  		}, nil),
   530  		s.dBus.EXPECT().Close(),
   531  	)
   532  
   533  	err := svc.Stop()
   534  	c.Assert(err, jc.ErrorIsNil)
   535  }
   536  
   537  func (s *initSystemSuite) TestStopNotInstalled(c *gc.C) {
   538  	ctrl := s.patch(c)
   539  	defer ctrl.Finish()
   540  
   541  	gomock.InOrder(
   542  		s.dBus.EXPECT().ListUnits().Return(nil, nil),
   543  		s.dBus.EXPECT().Close(),
   544  	)
   545  
   546  	err := s.newService(c).Stop()
   547  	c.Assert(err, jc.ErrorIsNil)
   548  }
   549  
   550  func (s *initSystemSuite) TestRemove(c *gc.C) {
   551  	ctrl := s.patch(c)
   552  	defer ctrl.Finish()
   553  
   554  	svc := s.newService(c)
   555  
   556  	gomock.InOrder(
   557  		s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{Stdout: []byte(svc.Name())}, nil),
   558  		s.dBus.EXPECT().DisableUnitFiles([]string{svc.UnitName}, false).Return(nil, nil),
   559  		s.dBus.EXPECT().Reload().Return(nil),
   560  		s.fops.EXPECT().Remove(path.Join(svc.DirName, svc.ConfName)).Return(nil),
   561  		s.fops.EXPECT().Remove(path.Join(svc.DirName, svc.Name()+"-exec-start.sh")).Return(nil),
   562  		s.dBus.EXPECT().Close(),
   563  	)
   564  
   565  	err := svc.Remove()
   566  	c.Assert(err, jc.ErrorIsNil)
   567  }
   568  
   569  func (s *initSystemSuite) TestRemoveNotInstalled(c *gc.C) {
   570  	ctrl := s.patch(c)
   571  	defer ctrl.Finish()
   572  
   573  	s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{Stdout: []byte("")}, nil)
   574  
   575  	err := s.newService(c).Remove()
   576  	c.Assert(err, jc.ErrorIsNil)
   577  }
   578  
   579  func (s *initSystemSuite) TestInstall(c *gc.C) {
   580  	ctrl := s.patch(c)
   581  	defer ctrl.Finish()
   582  
   583  	fileName := fmt.Sprintf("%s/%s.service", systemd.EtcSystemdDir, s.name)
   584  
   585  	gomock.InOrder(
   586  		s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{Stdout: []byte("")}, nil),
   587  		s.fops.EXPECT().WriteFile(fileName, []byte(s.newConfStr(s.name)), os.FileMode(0644)).Return(nil),
   588  		s.dBus.EXPECT().LinkUnitFiles([]string{fileName}, false, true).Return(nil, nil),
   589  		s.dBus.EXPECT().Reload().Return(nil),
   590  		s.dBus.EXPECT().EnableUnitFiles([]string{fileName}, false, true).Return(true, nil, nil),
   591  		s.dBus.EXPECT().Close(),
   592  	)
   593  
   594  	err := s.newService(c).Install()
   595  	c.Assert(err, jc.ErrorIsNil)
   596  }
   597  
   598  func (s *initSystemSuite) TestInstallAlreadyInstalled(c *gc.C) {
   599  	ctrl := s.patch(c)
   600  	defer ctrl.Finish()
   601  
   602  	s.expectConf(c, s.conf)
   603  	svc := s.newService(c)
   604  
   605  	s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{Stdout: []byte(svc.Name())}, nil)
   606  
   607  	err := svc.Install()
   608  	c.Assert(err, jc.ErrorIsNil)
   609  }
   610  
   611  func (s *initSystemSuite) TestInstallZombie(c *gc.C) {
   612  	ctrl := s.patch(c)
   613  	defer ctrl.Finish()
   614  
   615  	// We force the systemd API to return a slightly different conf.
   616  	// In this case we simply set a different Env value between the
   617  	// conf we are installing and the conf returned by the systemd API.
   618  	// This causes Service.Exists to return false.
   619  	conf := common.Conf{
   620  		Desc:      s.conf.Desc,
   621  		ExecStart: s.conf.ExecStart,
   622  		Env:       map[string]string{"a": "b"},
   623  	}
   624  	s.expectConf(c, conf)
   625  	conf.Env["a"] = "c"
   626  	svc, err := systemd.NewService(
   627  		s.name,
   628  		conf,
   629  		systemd.EtcSystemdDir,
   630  		func() (systemd.DBusAPI, error) { return s.dBus, nil },
   631  		s.fops,
   632  		renderer.Join(s.dataDir, "init"),
   633  	)
   634  	c.Assert(err, jc.ErrorIsNil)
   635  
   636  	fileName := fmt.Sprintf("%s/%s.service", systemd.EtcSystemdDir, s.name)
   637  
   638  	s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{Stdout: []byte(svc.Name())}, nil).Times(2)
   639  	s.dBus.EXPECT().Close().Times(3)
   640  
   641  	s.dBus.EXPECT().ListUnits().Return([]dbus.UnitStatus{
   642  		{Name: svc.Name(), LoadState: "loaded", ActiveState: "active"},
   643  	}, nil)
   644  	s.dBus.EXPECT().DisableUnitFiles([]string{svc.UnitName}, false).Return(nil, nil)
   645  	s.dBus.EXPECT().Reload().Return(nil)
   646  	s.fops.EXPECT().Remove(path.Join(svc.DirName, svc.ConfName)).Return(nil)
   647  	s.fops.EXPECT().Remove(path.Join(svc.DirName, svc.Name()+"-exec-start.sh")).Return(nil)
   648  	s.fops.EXPECT().WriteFile(fileName, []byte(s.newConfStrEnv(s.name, `"a=c"`)), os.FileMode(0644)).Return(nil)
   649  	s.dBus.EXPECT().LinkUnitFiles([]string{fileName}, false, true).Return(nil, nil)
   650  	s.dBus.EXPECT().Reload().Return(nil)
   651  	s.dBus.EXPECT().EnableUnitFiles([]string{fileName}, false, true).Return(true, nil, nil)
   652  
   653  	err = svc.Install()
   654  	c.Assert(err, jc.ErrorIsNil)
   655  }
   656  
   657  func (s *initSystemSuite) TestInstallMultiLine(c *gc.C) {
   658  	ctrl := s.patch(c)
   659  	defer ctrl.Finish()
   660  
   661  	fileName := fmt.Sprintf("%s/%s.service", systemd.EtcSystemdDir, s.name)
   662  	scriptPath := fmt.Sprintf("%s/%s-exec-start.sh", systemd.EtcSystemdDir, s.name)
   663  	cmd := "a\nb\nc"
   664  
   665  	svc := s.newService(c)
   666  	svc.Service.Conf.ExecStart = scriptPath
   667  	svc.Script = []byte(cmd)
   668  
   669  	gomock.InOrder(
   670  		s.exec.EXPECT().RunCommands(listCmdArg).Return(&exec.ExecResponse{Stdout: []byte("")}, nil),
   671  		s.fops.EXPECT().WriteFile(scriptPath, []byte(cmd), os.FileMode(0755)).Return(nil),
   672  		s.fops.EXPECT().WriteFile(fileName, []byte(s.newConfStrCmd(s.name, scriptPath)), os.FileMode(0644)).Return(nil),
   673  		s.dBus.EXPECT().LinkUnitFiles([]string{fileName}, false, true).Return(nil, nil),
   674  		s.dBus.EXPECT().Reload().Return(nil),
   675  		s.dBus.EXPECT().EnableUnitFiles([]string{fileName}, false, true).Return(true, nil, nil),
   676  		s.dBus.EXPECT().Close(),
   677  	)
   678  
   679  	err := svc.Install()
   680  	c.Assert(err, jc.ErrorIsNil)
   681  }
   682  
   683  func (s *initSystemSuite) TestInstallEmptyConf(c *gc.C) {
   684  	svc := s.newService(c)
   685  	svc.Service.Conf = common.Conf{}
   686  	err := svc.Install()
   687  	c.Check(err, gc.ErrorMatches, `.*missing conf.*`)
   688  }
   689  
   690  func (s *initSystemSuite) TestInstallCommands(c *gc.C) {
   691  	name := "jujud-machine-0"
   692  	commands, err := s.newService(c).InstallCommands()
   693  	c.Assert(err, jc.ErrorIsNil)
   694  
   695  	test := systemdtesting.WriteConfTest{
   696  		Service:  name,
   697  		DataDir:  systemd.EtcSystemdDir,
   698  		Expected: s.newConfStr(name),
   699  	}
   700  	test.CheckCommands(c, commands)
   701  }
   702  
   703  func (s *initSystemSuite) TestInstallCommandsLogfile(c *gc.C) {
   704  	name := "jujud-machine-0"
   705  	s.conf.Logfile = "/var/log/juju/machine-0.log"
   706  	svc := s.newService(c)
   707  	commands, err := svc.InstallCommands()
   708  	c.Assert(err, jc.ErrorIsNil)
   709  
   710  	user, group := paths.SyslogUserGroup()
   711  	test := systemdtesting.WriteConfTest{
   712  		Service: name,
   713  		DataDir: systemd.EtcSystemdDir,
   714  		Expected: strings.Replace(
   715  			s.newConfStr(name),
   716  			"ExecStart=/var/lib/juju/bin/jujud machine-0",
   717  			"ExecStart=/etc/systemd/system/jujud-machine-0-exec-start.sh",
   718  			-1),
   719  		Script: `
   720  # Set up logging.
   721  touch '/var/log/juju/machine-0.log'
   722  chown `[1:] + user + `:` + group + ` '/var/log/juju/machine-0.log'
   723  chmod 0640 '/var/log/juju/machine-0.log'
   724  exec >> '/var/log/juju/machine-0.log'
   725  exec 2>&1
   726  
   727  # Run the script.
   728  ` + jujud + " machine-0",
   729  	}
   730  
   731  	test.CheckCommands(c, commands)
   732  }
   733  
   734  func (s *initSystemSuite) TestInstallCommandsShutdown(c *gc.C) {
   735  	name := "juju-shutdown-job"
   736  	conf, err := service.ShutdownAfterConf("cloud-final")
   737  	c.Assert(err, jc.ErrorIsNil)
   738  
   739  	svc, err := systemd.NewService(
   740  		name, conf, systemd.EtcSystemdDir, systemd.NewDBusAPI, s.fops, renderer.Join(s.dataDir, "init"))
   741  	c.Assert(err, jc.ErrorIsNil)
   742  
   743  	commands, err := svc.InstallCommands()
   744  	c.Assert(err, jc.ErrorIsNil)
   745  
   746  	test := systemdtesting.WriteConfTest{
   747  		Service: name,
   748  		DataDir: systemd.EtcSystemdDir,
   749  		Expected: `
   750  [Unit]
   751  Description=juju shutdown job
   752  After=syslog.target
   753  After=network.target
   754  After=systemd-user-sessions.service
   755  After=cloud-final
   756  
   757  [Service]
   758  ExecStart=/sbin/shutdown -h now
   759  ExecStopPost=/bin/systemctl disable juju-shutdown-job.service
   760  
   761  [Install]
   762  WantedBy=multi-user.target
   763  `[1:],
   764  	}
   765  
   766  	test.CheckCommands(c, commands)
   767  }
   768  
   769  func (s *initSystemSuite) TestInstallCommandsEmptyConf(c *gc.C) {
   770  	svc := s.newService(c)
   771  	svc.Service.Conf = common.Conf{}
   772  	_, err := svc.InstallCommands()
   773  	c.Check(err, gc.ErrorMatches, `.*missing conf.*`)
   774  }
   775  
   776  func (s *initSystemSuite) TestStartCommands(c *gc.C) {
   777  	commands, err := s.newService(c).StartCommands()
   778  	c.Assert(err, jc.ErrorIsNil)
   779  	c.Check(commands, jc.DeepEquals, []string{"/bin/systemctl start jujud-machine-0.service"})
   780  }
   781  
   782  func (s *initSystemSuite) TestInstallLimits(c *gc.C) {
   783  	name := "juju-job"
   784  	conf := common.Conf{
   785  		Desc:      "juju agent for juju-job",
   786  		ExecStart: "/usr/bin/jujud juju-job",
   787  		Limit: map[string]string{
   788  			"fsize":   "unlimited",
   789  			"cpu":     "unlimited",
   790  			"as":      "12345",
   791  			"memlock": "unlimited",
   792  			"nofile":  "64000",
   793  		},
   794  	}
   795  	data, err := systemd.Serialize(name, conf, renderer)
   796  	c.Assert(err, jc.ErrorIsNil)
   797  	c.Check(string(data), gc.Equals, `
   798  [Unit]
   799  Description=juju agent for juju-job
   800  After=syslog.target
   801  After=network.target
   802  After=systemd-user-sessions.service
   803  
   804  [Service]
   805  LimitAS=12345
   806  LimitCPU=infinity
   807  LimitFSIZE=infinity
   808  LimitMEMLOCK=infinity
   809  LimitNOFILE=64000
   810  ExecStart=/usr/bin/jujud juju-job
   811  Restart=on-failure
   812  
   813  [Install]
   814  WantedBy=multi-user.target
   815  
   816  `[1:])
   817  }