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 }