1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
12
"github.com/coreos/go-systemd/unit"
13
"github.com/juju/errors"
14
"github.com/juju/testing"
15
jc "github.com/juju/testing/checkers"
16
"github.com/juju/utils/exec"
17
"github.com/juju/utils/shell"
18
gc "gopkg.in/check.v1"
19
"gopkg.in/juju/names.v2"
21
"github.com/juju/juju/juju/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"
29
var renderer = &shell.BashRenderer{}
33
Description=juju agent for %s
36
After=systemd-user-sessions.service
43
WantedBy=multi-user.target
47
const jujud = "/var/lib/juju/bin/jujud"
49
var listCmdArg = exec.RunParams{
50
Commands: `/bin/systemctl list-unit-files --no-legend --no-page -t service | grep -o -P '^\w[\S]*(?=\.service)'`,
53
type initSystemSuite struct {
59
conn *systemd.StubDbusAPI
60
fops *systemd.StubFileOps
61
exec *systemd.StubExec
67
service *systemd.Service
70
var _ = gc.Suite(&initSystemSuite{})
72
func (s *initSystemSuite) SetUpTest(c *gc.C) {
73
s.BaseSuite.SetUpTest(c)
75
dataDir, err := paths.DataDir("vivid")
76
c.Assert(err, jc.ErrorIsNil)
79
s.ch = systemd.PatchNewChan(s)
81
s.stub = &testing.Stub{}
82
s.conn = systemd.PatchNewConn(s, s.stub)
83
s.fops = systemd.PatchFileOps(s, s.stub)
84
s.exec = systemd.PatchExec(s, s.stub)
86
// Set up the service.
88
tag, err := names.ParseTag(tagStr)
89
c.Assert(err, jc.ErrorIsNil)
91
s.name = "jujud-" + tagStr
93
Desc: "juju agent for " + tagStr,
94
ExecStart: jujud + " " + tagStr,
96
s.service = s.newService(c)
98
// Reset any incidental calls.
102
func (s *initSystemSuite) newService(c *gc.C) *systemd.Service {
103
service, err := systemd.NewService(s.name, s.conf, s.dataDir)
104
c.Assert(err, jc.ErrorIsNil)
108
func (s *initSystemSuite) newConfStr(name string) string {
109
return s.newConfStrCmd(name, "")
112
func (s *initSystemSuite) newConfStrCmd(name, cmd string) string {
113
tag := name[len("jujud-"):]
115
cmd = jujud + " " + tag
117
return fmt.Sprintf(confStr[1:], tag, cmd)
120
func (s *initSystemSuite) newConfStrEnv(name, env string) string {
121
const replace = "[Service]\n"
122
result := s.newConfStr(name)
123
result = strings.Replace(
125
fmt.Sprintf("%sEnvironment=%s\n", replace, env),
131
func (s *initSystemSuite) addService(name, status string) {
132
tag := name[len("jujud-"):]
133
desc := "juju agent for " + tag
134
s.conn.AddService(name, desc, status)
137
func (s *initSystemSuite) addListResponse() {
139
for _, unit := range s.conn.Units {
140
lines = append(lines, strings.TrimSuffix(unit.Name, ".service"))
143
s.exec.Responses = append(s.exec.Responses, exec.ExecResponse{
145
Stdout: []byte(strings.Join(lines, "\n")),
150
func (s *initSystemSuite) setConf(c *gc.C, conf common.Conf) {
151
data, err := systemd.Serialize(s.name, conf, renderer)
152
c.Assert(err, jc.ErrorIsNil)
153
s.exec.Responses = append(s.exec.Responses, exec.ExecResponse{
160
func (s *initSystemSuite) checkCreateFileCall(c *gc.C, index int, filename, content string, perm os.FileMode) {
163
filename = fmt.Sprintf("%s/init/%s/%s.service", s.dataDir, name, name)
164
content = s.newConfStr(name)
167
call := s.stub.Calls()[index]
168
if !c.Check(call.FuncName, gc.Equals, "CreateFile") {
171
if !c.Check(call.Args, gc.HasLen, 3) {
175
callFilename, callData, callPerm := call.Args[0], call.Args[1], call.Args[2]
176
c.Check(callFilename, gc.Equals, filename)
178
// Some tests don't generate valid ini files, instead including placeholder
179
// strings (e.g. "a\nb\nc\n"). To avoid parsing errors, we only try and
180
// parse actual and expected file content if they don't exactly match.
181
if content != string(callData.([]byte)) {
182
// Parse the ini configurations and compare those.
183
expected, err := unit.Deserialize(bytes.NewReader(callData.([]byte)))
184
c.Assert(err, jc.ErrorIsNil)
185
cfg, err := unit.Deserialize(strings.NewReader(content))
186
c.Assert(err, jc.ErrorIsNil)
187
c.Check(cfg, jc.SameContents, expected)
190
c.Check(callPerm, gc.Equals, perm)
193
func (s *initSystemSuite) TestListServices(c *gc.C) {
194
s.addService("jujud-machine-0", "active")
195
s.addService("something-else", "error")
196
s.addService("jujud-unit-wordpress-0", "active")
197
s.addService("another", "inactive")
200
names, err := systemd.ListServices()
201
c.Assert(err, jc.ErrorIsNil)
203
c.Check(names, jc.SameContents, []string{
206
"jujud-unit-wordpress-0",
209
s.stub.CheckCallNames(c, "RunCommand")
212
func (s *initSystemSuite) TestListServicesEmpty(c *gc.C) {
215
names, err := systemd.ListServices()
216
c.Assert(err, jc.ErrorIsNil)
218
c.Check(names, gc.HasLen, 0)
219
s.stub.CheckCallNames(c, "RunCommand")
222
func (s *initSystemSuite) TestNewService(c *gc.C) {
223
service := s.newService(c)
224
c.Check(service, jc.DeepEquals, &systemd.Service{
225
Service: common.Service{
229
ConfName: s.name + ".service",
230
UnitName: s.name + ".service",
231
Dirname: fmt.Sprintf("%s/init/%s", s.dataDir, s.name),
233
s.stub.CheckCalls(c, nil)
236
func (s *initSystemSuite) TestNewServiceLogfile(c *gc.C) {
237
s.conf.Logfile = "/var/log/juju/machine-0.log"
238
service := s.newService(c)
240
user, group := systemd.SyslogUserGroup()
241
dirname := fmt.Sprintf("%s/init/%s", s.dataDir, s.name)
246
touch '/var/log/juju/machine-0.log'
247
chown `[1:] + user + `:` + group + ` '/var/log/juju/machine-0.log'
248
chmod 0600 '/var/log/juju/machine-0.log'
249
exec >> '/var/log/juju/machine-0.log'
253
` + jujud + " machine-0"
254
c.Check(service, jc.DeepEquals, &systemd.Service{
255
Service: common.Service{
259
ExecStart: dirname + "/exec-start.sh",
260
Logfile: "/var/log/juju/machine-0.log",
263
UnitName: s.name + ".service",
264
ConfName: s.name + ".service",
266
Script: []byte(script),
268
// This gives us a more readable output if they aren't equal.
269
c.Check(string(service.Script), gc.Equals, script)
270
c.Check(strings.Split(string(service.Script), "\n"), jc.DeepEquals, strings.Split(script, "\n"))
273
func (s *initSystemSuite) TestNewServiceEmptyConf(c *gc.C) {
274
service, err := systemd.NewService(s.name, common.Conf{}, s.dataDir)
275
c.Assert(err, jc.ErrorIsNil)
277
c.Check(service, jc.DeepEquals, &systemd.Service{
278
Service: common.Service{
281
ConfName: s.name + ".service",
282
UnitName: s.name + ".service",
283
Dirname: fmt.Sprintf("%s/init/%s", s.dataDir, s.name),
285
s.stub.CheckCalls(c, nil)
288
func (s *initSystemSuite) TestNewServiceBasic(c *gc.C) {
289
s.conf.ExecStart = "/path/to/some/other/command"
290
svc := s.newService(c)
292
c.Check(svc, jc.DeepEquals, &systemd.Service{
293
Service: common.Service{
297
ConfName: s.name + ".service",
298
UnitName: s.name + ".service",
299
Dirname: fmt.Sprintf("%s/init/%s", s.dataDir, s.name),
301
s.stub.CheckCalls(c, nil)
304
func (s *initSystemSuite) TestNewServiceExtraScript(c *gc.C) {
305
s.conf.ExtraScript = "'/path/to/another/command'"
306
svc := s.newService(c)
308
dirname := fmt.Sprintf("%s/init/%s", s.dataDir, s.name)
312
'/path/to/another/command'
313
`[1:] + jujud + " machine-0"
314
c.Check(svc, jc.DeepEquals, &systemd.Service{
315
Service: common.Service{
319
ExecStart: dirname + "/exec-start.sh",
322
UnitName: s.name + ".service",
323
ConfName: s.name + ".service",
325
Script: []byte(script),
327
// This gives us a more readable output if they aren't equal.
328
c.Check(string(svc.Script), gc.Equals, script)
329
s.stub.CheckCalls(c, nil)
332
func (s *initSystemSuite) TestNewServiceMultiline(c *gc.C) {
333
s.conf.ExecStart = "a\nb\nc"
334
svc := s.newService(c)
336
dirname := fmt.Sprintf("%s/init/%s", s.dataDir, s.name)
343
c.Check(svc, jc.DeepEquals, &systemd.Service{
344
Service: common.Service{
348
ExecStart: dirname + "/exec-start.sh",
351
UnitName: s.name + ".service",
352
ConfName: s.name + ".service",
354
Script: []byte(script),
356
// This gives us a more readable output if they aren't equal.
357
c.Check(string(svc.Script), gc.Equals, script)
358
s.stub.CheckCalls(c, nil)
361
func (s *initSystemSuite) TestInstalledTrue(c *gc.C) {
362
s.addService("jujud-machine-0", "active")
363
s.addService("something-else", "error")
364
s.addService("juju-mongod", "active")
367
installed, err := s.service.Installed()
368
c.Assert(err, jc.ErrorIsNil)
370
c.Check(installed, jc.IsTrue)
371
s.stub.CheckCallNames(c, "RunCommand")
374
func (s *initSystemSuite) TestInstalledFalse(c *gc.C) {
375
s.addService("something-else", "error")
378
installed, err := s.service.Installed()
379
c.Assert(err, jc.ErrorIsNil)
381
c.Check(installed, jc.IsFalse)
382
s.stub.CheckCallNames(c, "RunCommand")
385
func (s *initSystemSuite) TestInstalledError(c *gc.C) {
386
s.addService("jujud-machine-0", "active")
387
s.addService("something-else", "error")
388
s.addService("juju-mongod", "active")
390
failure := errors.New("<failed>")
391
s.stub.SetErrors(failure)
393
installed, err := s.service.Installed()
394
c.Assert(errors.Cause(err), gc.Equals, failure)
396
c.Check(installed, jc.IsFalse)
397
s.stub.CheckCallNames(c, "RunCommand")
400
func (s *initSystemSuite) TestExistsTrue(c *gc.C) {
403
exists, err := s.service.Exists()
404
c.Assert(err, jc.ErrorIsNil)
406
c.Check(exists, jc.IsTrue)
407
s.stub.CheckCallNames(c, "RunCommand")
410
func (s *initSystemSuite) TestExistsFalse(c *gc.C) {
411
// We force the systemd API to return a slightly different conf.
412
// In this case we simply set Conf.Env, which s.conf does not set.
413
// This causes Service.Exists to return false.
414
s.setConf(c, common.Conf{
416
ExecStart: s.conf.ExecStart,
417
Env: map[string]string{"a": "b"},
420
exists, err := s.service.Exists()
421
c.Assert(err, jc.ErrorIsNil)
423
c.Check(exists, jc.IsFalse)
424
s.stub.CheckCallNames(c, "RunCommand")
427
func (s *initSystemSuite) TestExistsError(c *gc.C) {
428
failure := errors.New("<failed>")
429
s.stub.SetErrors(failure)
431
exists, err := s.service.Exists()
432
c.Assert(errors.Cause(err), gc.Equals, failure)
434
c.Check(exists, jc.IsFalse)
435
s.stub.CheckCallNames(c, "RunCommand")
438
func (s *initSystemSuite) TestExistsEmptyConf(c *gc.C) {
439
s.service.Service.Conf = common.Conf{}
441
_, err := s.service.Exists()
443
c.Check(err, gc.ErrorMatches, `.*no conf expected.*`)
444
s.stub.CheckCalls(c, nil)
447
func (s *initSystemSuite) TestRunningTrue(c *gc.C) {
448
s.addService("jujud-machine-0", "active")
449
s.addService("something-else", "error")
450
s.addService("juju-mongod", "active")
452
running, err := s.service.Running()
453
c.Assert(err, jc.ErrorIsNil)
455
c.Check(running, jc.IsTrue)
456
s.stub.CheckCallNames(c, "ListUnits", "Close")
459
func (s *initSystemSuite) TestRunningFalse(c *gc.C) {
460
s.addService("jujud-machine-0", "inactive")
461
s.addService("something-else", "error")
462
s.addService("juju-mongod", "active")
464
running, err := s.service.Running()
465
c.Assert(err, jc.ErrorIsNil)
467
c.Check(running, jc.IsFalse)
468
s.stub.CheckCallNames(c, "ListUnits", "Close")
471
func (s *initSystemSuite) TestRunningNotEnabled(c *gc.C) {
472
s.addService("something-else", "active")
474
running, err := s.service.Running()
475
c.Assert(err, jc.ErrorIsNil)
477
c.Check(running, jc.IsFalse)
478
s.stub.CheckCallNames(c, "ListUnits", "Close")
481
func (s *initSystemSuite) TestRunningError(c *gc.C) {
482
s.addService("jujud-machine-0", "active")
483
s.addService("something-else", "error")
484
s.addService("juju-mongod", "active")
485
failure := errors.New("<failed>")
486
s.stub.SetErrors(failure)
488
running, err := s.service.Running()
489
c.Assert(errors.Cause(err), gc.Equals, failure)
491
c.Check(running, jc.IsFalse)
492
s.stub.CheckCallNames(c, "ListUnits", "Close")
495
func (s *initSystemSuite) TestStart(c *gc.C) {
496
s.addService("jujud-machine-0", "inactive")
500
err := s.service.Start()
501
c.Assert(err, jc.ErrorIsNil)
503
s.stub.CheckCalls(c, []testing.StubCall{{
504
FuncName: "RunCommand",
509
FuncName: "ListUnits",
513
FuncName: "StartUnit",
517
(chan<- string)(s.ch),
524
func (s *initSystemSuite) TestStartAlreadyRunning(c *gc.C) {
525
s.addService("jujud-machine-0", "active")
526
s.ch <- "done" // just in case
529
err := s.service.Start()
530
c.Assert(err, jc.ErrorIsNil)
532
s.stub.CheckCallNames(c,
539
func (s *initSystemSuite) TestStartNotInstalled(c *gc.C) {
540
s.ch <- "done" // just in case
542
err := s.service.Start()
544
c.Check(err, jc.Satisfies, errors.IsNotFound)
545
s.stub.CheckCallNames(c, "RunCommand")
548
func (s *initSystemSuite) TestStop(c *gc.C) {
549
s.addService("jujud-machine-0", "active")
552
err := s.service.Stop()
553
c.Assert(err, jc.ErrorIsNil)
555
s.stub.CheckCalls(c, []testing.StubCall{{
556
FuncName: "ListUnits",
560
FuncName: "StopUnit",
564
(chan<- string)(s.ch),
571
func (s *initSystemSuite) TestStopNotRunning(c *gc.C) {
572
s.addService("jujud-machine-0", "inactive")
573
s.ch <- "done" // just in case
575
err := s.service.Stop()
576
c.Assert(err, jc.ErrorIsNil)
578
s.stub.CheckCallNames(c, "ListUnits", "Close")
581
func (s *initSystemSuite) TestStopNotInstalled(c *gc.C) {
582
s.ch <- "done" // just in case
584
err := s.service.Stop()
585
c.Assert(err, jc.ErrorIsNil)
587
s.stub.CheckCallNames(c, "ListUnits", "Close")
590
func (s *initSystemSuite) TestRemove(c *gc.C) {
591
s.addService("jujud-machine-0", "inactive")
594
err := s.service.Remove()
595
c.Assert(err, jc.ErrorIsNil)
597
s.stub.CheckCalls(c, []testing.StubCall{{
598
FuncName: "RunCommand",
603
FuncName: "DisableUnitFiles",
605
[]string{s.name + ".service"},
611
FuncName: "RemoveAll",
613
fmt.Sprintf("%s/init/%s", s.dataDir, s.name),
620
func (s *initSystemSuite) TestRemoveNotInstalled(c *gc.C) {
621
err := s.service.Remove()
622
c.Assert(err, jc.ErrorIsNil)
624
s.stub.CheckCallNames(c, "RunCommand")
627
func (s *initSystemSuite) TestInstall(c *gc.C) {
628
err := s.service.Install()
629
c.Assert(err, jc.ErrorIsNil)
631
dirname := fmt.Sprintf("%s/init/%s", s.dataDir, s.name)
632
filename := fmt.Sprintf("%s/%s.service", dirname, s.name)
633
createFileOutput := s.stub.Calls()[2].Args[1] // gross
634
s.stub.CheckCalls(c, []testing.StubCall{{
635
FuncName: "RunCommand",
640
FuncName: "MkdirAll",
645
FuncName: "CreateFile",
648
// The contents of the file will always pass this test. We are
649
// testing the sequence of commands. The output of CreateFile
650
// is tested by tests that call checkCreateFileCall.
655
FuncName: "LinkUnitFiles",
664
FuncName: "EnableUnitFiles",
673
s.checkCreateFileCall(c, 2, filename, s.newConfStr(s.name), 0644)
676
func (s *initSystemSuite) TestInstallAlreadyInstalled(c *gc.C) {
677
s.addService("jujud-machine-0", "inactive")
681
err := s.service.Install()
682
c.Assert(err, jc.ErrorIsNil)
684
s.stub.CheckCallNames(c,
690
func (s *initSystemSuite) TestInstallZombie(c *gc.C) {
691
s.addService("jujud-machine-0", "active")
693
// We force the systemd API to return a slightly different conf.
694
// In this case we simply set a different Env value between the
695
// conf we are installing and the conf returned by the systemd API.
696
// This causes Service.Exists to return false.
699
ExecStart: s.conf.ExecStart,
700
Env: map[string]string{"a": "b"},
707
service, err := systemd.NewService(s.name, conf, s.dataDir)
708
c.Assert(err, jc.ErrorIsNil)
709
err = service.Install()
710
c.Assert(err, jc.ErrorIsNil)
712
s.stub.CheckCallNames(c,
731
filename := fmt.Sprintf("%s/init/%s/%s.service", s.dataDir, s.name, s.name)
732
content := s.newConfStrEnv(s.name, `"a=c"`)
733
s.checkCreateFileCall(c, 12, filename, content, 0644)
736
func (s *initSystemSuite) TestInstallMultiline(c *gc.C) {
737
scriptPath := fmt.Sprintf("%s/init/%s/exec-start.sh", s.dataDir, s.name)
739
s.service.Service.Conf.ExecStart = scriptPath
740
s.service.Script = []byte(cmd)
742
err := s.service.Install()
743
c.Assert(err, jc.ErrorIsNil)
745
s.stub.CheckCallNames(c,
755
s.checkCreateFileCall(c, 2, scriptPath, cmd, 0755)
756
filename := fmt.Sprintf("%s/init/%s/%s.service", s.dataDir, s.name, s.name)
757
content := s.newConfStrCmd(s.name, scriptPath)
758
s.checkCreateFileCall(c, 3, filename, content, 0644)
761
func (s *initSystemSuite) TestInstallEmptyConf(c *gc.C) {
762
s.service.Service.Conf = common.Conf{}
764
err := s.service.Install()
766
c.Check(err, gc.ErrorMatches, `.*missing conf.*`)
767
s.stub.CheckCalls(c, nil)
770
func (s *initSystemSuite) TestInstallCommands(c *gc.C) {
771
name := "jujud-machine-0"
772
commands, err := s.service.InstallCommands()
773
c.Assert(err, jc.ErrorIsNil)
775
test := systemdtesting.WriteConfTest{
778
Expected: s.newConfStr(name),
780
test.CheckCommands(c, commands)
783
func (s *initSystemSuite) TestInstallCommandsLogfile(c *gc.C) {
784
name := "jujud-machine-0"
785
s.conf.Logfile = "/var/log/juju/machine-0.log"
786
service := s.newService(c)
787
commands, err := service.InstallCommands()
788
c.Assert(err, jc.ErrorIsNil)
790
user, group := systemd.SyslogUserGroup()
791
test := systemdtesting.WriteConfTest{
794
Expected: strings.Replace(
796
"ExecStart=/var/lib/juju/bin/jujud machine-0",
797
"ExecStart=/var/lib/juju/init/jujud-machine-0/exec-start.sh",
801
touch '/var/log/juju/machine-0.log'
802
chown `[1:] + user + `:` + group + ` '/var/log/juju/machine-0.log'
803
chmod 0600 '/var/log/juju/machine-0.log'
804
exec >> '/var/log/juju/machine-0.log'
808
` + jujud + " machine-0",
810
test.CheckCommands(c, commands)
813
func (s *initSystemSuite) TestInstallCommandsShutdown(c *gc.C) {
814
name := "juju-shutdown-job"
815
conf, err := service.ShutdownAfterConf("cloud-final")
816
c.Assert(err, jc.ErrorIsNil)
817
svc, err := systemd.NewService(name, conf, s.dataDir)
818
c.Assert(err, jc.ErrorIsNil)
819
commands, err := svc.InstallCommands()
820
c.Assert(err, jc.ErrorIsNil)
822
test := systemdtesting.WriteConfTest{
827
Description=juju shutdown job
830
After=systemd-user-sessions.service
834
ExecStart=/sbin/shutdown -h now
835
ExecStopPost=/bin/systemctl disable juju-shutdown-job.service
838
WantedBy=multi-user.target
841
test.CheckCommands(c, commands)
844
func (s *initSystemSuite) TestInstallCommandsEmptyConf(c *gc.C) {
845
s.service.Service.Conf = common.Conf{}
847
_, err := s.service.InstallCommands()
849
c.Check(err, gc.ErrorMatches, `.*missing conf.*`)
850
s.stub.CheckCalls(c, nil)
853
func (s *initSystemSuite) TestStartCommands(c *gc.C) {
854
commands, err := s.service.StartCommands()
855
c.Assert(err, jc.ErrorIsNil)
857
c.Check(commands, jc.DeepEquals, []string{
858
"/bin/systemctl start jujud-machine-0.service",