github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/service/systemd/service.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package systemd 5 6 import ( 7 "io/ioutil" 8 "os" 9 "path" 10 "reflect" 11 "strings" 12 13 "github.com/coreos/go-systemd/dbus" 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/utils/shell" 17 18 "github.com/juju/juju/service/common" 19 ) 20 21 var ( 22 logger = loggo.GetLogger("juju.service.systemd") 23 24 renderer = shell.BashRenderer{} 25 cmds = commands{renderer, executable} 26 ) 27 28 // IsRunning returns whether or not systemd is the local init system. 29 func IsRunning() (bool, error) { 30 if _, err := os.Stat("/run/systemd/system"); err == nil { 31 return true, nil 32 } else if os.IsNotExist(err) { 33 return false, nil 34 } else { 35 return false, errors.Trace(err) 36 } 37 } 38 39 // ListServices returns the list of installed service names. 40 func ListServices() ([]string, error) { 41 // TODO(ericsnow) conn.ListUnits misses some inactive units, so we 42 // would need conn.ListUnitFiles. Such a method has been requested. 43 // (see https://github.com/coreos/go-systemd/issues/76). In the 44 // meantime we use systemctl at the shell to list the services. 45 // Once that is addressed upstread we can just call listServices here. 46 names, err := Cmdline{}.ListAll() 47 if err != nil { 48 return nil, errors.Trace(err) 49 } 50 return names, nil 51 } 52 53 func listServices() ([]string, error) { 54 conn, err := newConn() 55 if err != nil { 56 return nil, errors.Trace(err) 57 } 58 defer conn.Close() 59 60 units, err := conn.ListUnits() 61 if err != nil { 62 return nil, errors.Trace(err) 63 } 64 65 var services []string 66 for _, unit := range units { 67 name := unit.Name 68 if !strings.HasSuffix(name, ".service") { 69 continue 70 } 71 name = strings.TrimSuffix(name, ".service") 72 services = append(services, name) 73 } 74 return services, nil 75 } 76 77 // ListCommand returns a command that will list the services on a host. 78 func ListCommand() string { 79 return cmds.listAll() 80 } 81 82 // Service provides visibility into and control over a systemd service. 83 type Service struct { 84 common.Service 85 86 ConfName string 87 UnitName string 88 Dirname string 89 Script []byte 90 } 91 92 // NewService returns a new value that implements Service for systemd. 93 func NewService(name string, conf common.Conf, dataDir string) (*Service, error) { 94 confName := name + ".service" 95 var volName string 96 if conf.ExecStart != "" { 97 volName = renderer.VolumeName(common.Unquote(strings.Fields(conf.ExecStart)[0])) 98 } 99 dirname := volName + renderer.Join(dataDir, "init", name) 100 101 service := &Service{ 102 Service: common.Service{ 103 Name: name, 104 // Conf is set in setConf. 105 }, 106 ConfName: confName, 107 UnitName: confName, 108 Dirname: dirname, 109 } 110 111 if err := service.setConf(conf); err != nil { 112 return nil, errors.Trace(err) 113 } 114 115 return service, nil 116 } 117 118 // dbusAPI exposes all the systemd API methods needed by juju. 119 type dbusAPI interface { 120 Close() 121 ListUnits() ([]dbus.UnitStatus, error) 122 StartUnit(string, string, chan<- string) (int, error) 123 StopUnit(string, string, chan<- string) (int, error) 124 LinkUnitFiles([]string, bool, bool) ([]dbus.LinkUnitFileChange, error) 125 EnableUnitFiles([]string, bool, bool) (bool, []dbus.EnableUnitFileChange, error) 126 DisableUnitFiles([]string, bool) ([]dbus.DisableUnitFileChange, error) 127 GetUnitProperties(string) (map[string]interface{}, error) 128 GetUnitTypeProperties(string, string) (map[string]interface{}, error) 129 Reload() error 130 } 131 132 var newConn = func() (dbusAPI, error) { 133 return dbus.New() 134 } 135 136 var newChan = func() chan string { 137 return make(chan string) 138 } 139 140 func (s *Service) errorf(err error, msg string, args ...interface{}) error { 141 msg += " for service %q" 142 args = append(args, s.Service.Name) 143 if err == nil { 144 err = errors.Errorf(msg, args...) 145 } else { 146 err = errors.Annotatef(err, msg, args...) 147 } 148 err.(*errors.Err).SetLocation(1) 149 logger.Errorf("%v", err) 150 logger.Debugf("stack trace:\n%s", errors.ErrorStack(err)) 151 return err 152 } 153 154 // Name implements service.Service. 155 func (s Service) Name() string { 156 return s.Service.Name 157 } 158 159 // Conf implements service.Service. 160 func (s Service) Conf() common.Conf { 161 return s.Service.Conf 162 } 163 164 func (s *Service) serialize() ([]byte, error) { 165 data, err := serialize(s.UnitName, s.Service.Conf, renderer) 166 if err != nil { 167 return nil, s.errorf(err, "failed to serialize conf") 168 } 169 return data, nil 170 } 171 172 func (s *Service) deserialize(data []byte) (common.Conf, error) { 173 conf, err := deserialize(data, renderer) 174 if err != nil { 175 return conf, s.errorf(err, "failed to deserialize conf") 176 } 177 return conf, nil 178 } 179 180 func (s *Service) validate(conf common.Conf) error { 181 if err := validate(s.Service.Name, conf, &renderer); err != nil { 182 return s.errorf(err, "invalid conf") 183 } 184 return nil 185 } 186 187 func (s *Service) normalize(conf common.Conf) (common.Conf, []byte) { 188 scriptPath := renderer.ScriptFilename("exec-start", s.Dirname) 189 return normalize(s.Service.Name, conf, scriptPath, &renderer) 190 } 191 192 func (s *Service) setConf(conf common.Conf) error { 193 if conf.IsZero() { 194 s.Service.Conf = conf 195 return nil 196 } 197 198 normalConf, data := s.normalize(conf) 199 if err := s.validate(normalConf); err != nil { 200 return errors.Trace(err) 201 } 202 203 s.Script = data 204 s.Service.Conf = normalConf 205 return nil 206 } 207 208 // Installed implements Service. 209 func (s *Service) Installed() (bool, error) { 210 names, err := ListServices() 211 if err != nil { 212 return false, s.errorf(err, "failed to list services") 213 } 214 for _, name := range names { 215 if name == s.Service.Name { 216 return true, nil 217 } 218 } 219 return false, nil 220 } 221 222 // Exists implements Service. 223 func (s *Service) Exists() (bool, error) { 224 if s.NoConf() { 225 return false, s.errorf(nil, "no conf expected") 226 } 227 228 same, err := s.check() 229 if err != nil { 230 return false, errors.Trace(err) 231 } 232 return same, nil 233 } 234 235 func (s *Service) check() (bool, error) { 236 conf, err := s.readConf() 237 if err != nil { 238 return false, errors.Trace(err) 239 } 240 normalConf, _ := s.normalize(s.Service.Conf) 241 return reflect.DeepEqual(normalConf, conf), nil 242 } 243 244 func (s *Service) readConf() (common.Conf, error) { 245 var conf common.Conf 246 247 data, err := Cmdline{}.conf(s.Service.Name, s.Dirname) 248 if err != nil { 249 return conf, s.errorf(err, "failed to read conf from systemd") 250 } 251 252 conf, err = s.deserialize(data) 253 if err != nil { 254 return conf, errors.Trace(err) 255 } 256 return conf, nil 257 } 258 259 func (s Service) newConn() (dbusAPI, error) { 260 conn, err := newConn() 261 if err != nil { 262 logger.Errorf("failed to connect to dbus for service %q: %v", s.Service.Name, err) 263 } 264 return conn, err 265 } 266 267 // Running implements Service. 268 func (s *Service) Running() (bool, error) { 269 conn, err := s.newConn() 270 if err != nil { 271 return false, errors.Trace(err) 272 } 273 defer conn.Close() 274 275 units, err := conn.ListUnits() 276 if err != nil { 277 return false, s.errorf(err, "failed to query services from dbus") 278 } 279 280 for _, unit := range units { 281 if unit.Name == s.UnitName { 282 running := unit.LoadState == "loaded" && unit.ActiveState == "active" 283 return running, nil 284 } 285 } 286 return false, nil 287 } 288 289 // Start implements Service. 290 func (s *Service) Start() error { 291 err := s.start() 292 if errors.IsAlreadyExists(err) { 293 logger.Debugf("service %q already running", s.Name()) 294 return nil 295 } else if err != nil { 296 logger.Errorf("service %q failed to start: %v", s.Name(), err) 297 return err 298 } 299 logger.Debugf("service %q successfully started", s.Name()) 300 return nil 301 } 302 303 func (s *Service) start() error { 304 installed, err := s.Installed() 305 if err != nil { 306 return errors.Trace(err) 307 } 308 if !installed { 309 return errors.NotFoundf("service " + s.Service.Name) 310 } 311 running, err := s.Running() 312 if err != nil { 313 return errors.Trace(err) 314 } 315 if running { 316 return errors.AlreadyExistsf("running service %s", s.Service.Name) 317 } 318 319 conn, err := s.newConn() 320 if err != nil { 321 return errors.Trace(err) 322 } 323 defer conn.Close() 324 325 statusCh := newChan() 326 _, err = conn.StartUnit(s.UnitName, "fail", statusCh) 327 if err != nil { 328 return s.errorf(err, "dbus start request failed") 329 } 330 331 if err := s.wait("start", statusCh); err != nil { 332 return errors.Trace(err) 333 } 334 335 return nil 336 } 337 338 func (s *Service) wait(op string, statusCh chan string) error { 339 status := <-statusCh 340 341 // TODO(ericsnow) Other status values *may* be okay. See: 342 // https://godoc.org/github.com/coreos/go-systemd/dbus#Conn.StartUnit 343 if status != "done" { 344 return s.errorf(nil, "failed to %s (API status %q)", op, status) 345 } 346 return nil 347 } 348 349 // Stop implements Service. 350 func (s *Service) Stop() error { 351 err := s.stop() 352 if errors.IsNotFound(err) { 353 logger.Debugf("service %q not running", s.Name()) 354 return nil 355 } else if err != nil { 356 logger.Errorf("service %q failed to stop: %v", s.Name(), err) 357 return err 358 } 359 logger.Debugf("service %q successfully stopped", s.Name()) 360 return nil 361 } 362 363 func (s *Service) stop() error { 364 running, err := s.Running() 365 if err != nil { 366 return errors.Trace(err) 367 } 368 if !running { 369 return errors.NotFoundf("running service %s", s.Service.Name) 370 } 371 372 conn, err := s.newConn() 373 if err != nil { 374 return errors.Trace(err) 375 } 376 defer conn.Close() 377 378 statusCh := newChan() 379 _, err = conn.StopUnit(s.UnitName, "fail", statusCh) 380 if err != nil { 381 return s.errorf(err, "dbus stop request failed") 382 } 383 384 if err := s.wait("stop", statusCh); err != nil { 385 return errors.Trace(err) 386 } 387 388 return err 389 } 390 391 // Remove implements Service. 392 func (s *Service) Remove() error { 393 err := s.remove() 394 if errors.IsNotFound(err) { 395 logger.Debugf("service %q not installed", s.Name()) 396 return nil 397 } else if err != nil { 398 logger.Errorf("failed to remove service %q: %v", s.Name(), err) 399 return err 400 } 401 logger.Debugf("service %q successfully removed", s.Name()) 402 return nil 403 } 404 405 func (s *Service) remove() error { 406 installed, err := s.Installed() 407 if err != nil { 408 return errors.Trace(err) 409 } 410 if !installed { 411 return errors.NotFoundf("service %s", s.Service.Name) 412 } 413 414 conn, err := s.newConn() 415 if err != nil { 416 return errors.Trace(err) 417 } 418 defer conn.Close() 419 420 runtime := false 421 _, err = conn.DisableUnitFiles([]string{s.UnitName}, runtime) 422 if err != nil { 423 return s.errorf(err, "dbus disable request failed") 424 } 425 426 if err := conn.Reload(); err != nil { 427 return s.errorf(err, "dbus post-disable daemon reload request failed") 428 } 429 430 if err := removeAll(s.Dirname); err != nil { 431 return s.errorf(err, "failed to delete juju-managed conf dir") 432 } 433 434 return nil 435 } 436 437 var removeAll = func(name string) error { 438 return os.RemoveAll(name) 439 } 440 441 // Install implements Service. 442 func (s *Service) Install() error { 443 if s.NoConf() { 444 return s.errorf(nil, "missing conf") 445 } 446 447 err := s.install() 448 if errors.IsAlreadyExists(err) { 449 logger.Debugf("service %q already installed", s.Name()) 450 return nil 451 } else if err != nil { 452 logger.Errorf("failed to install service %q: %v", s.Name(), err) 453 return err 454 } 455 logger.Debugf("service %q successfully installed", s.Name()) 456 return nil 457 } 458 459 func (s *Service) install() error { 460 installed, err := s.Installed() 461 if err != nil { 462 return errors.Trace(err) 463 } 464 if installed { 465 same, err := s.check() 466 if err != nil { 467 return errors.Trace(err) 468 } 469 if same { 470 return errors.AlreadyExistsf("service %s", s.Service.Name) 471 } 472 // An old copy is already running so stop it first. 473 if err := s.Stop(); err != nil { 474 return errors.Annotate(err, "systemd: could not stop old service") 475 } 476 if err := s.Remove(); err != nil { 477 return errors.Annotate(err, "systemd: could not remove old service") 478 } 479 } 480 481 filename, err := s.writeConf() 482 if err != nil { 483 return errors.Trace(err) 484 } 485 486 conn, err := s.newConn() 487 if err != nil { 488 return errors.Trace(err) 489 } 490 defer conn.Close() 491 492 runtime, force := false, true 493 _, err = conn.LinkUnitFiles([]string{filename}, runtime, force) 494 if err != nil { 495 return s.errorf(err, "dbus link request failed") 496 } 497 498 if err := conn.Reload(); err != nil { 499 return s.errorf(err, "dbus post-link daemon reload request failed") 500 } 501 502 _, _, err = conn.EnableUnitFiles([]string{filename}, runtime, force) 503 if err != nil { 504 return s.errorf(err, "dbus enable request failed") 505 } 506 return nil 507 } 508 509 func (s *Service) writeConf() (string, error) { 510 data, err := s.serialize() 511 if err != nil { 512 return "", errors.Trace(err) 513 } 514 515 if err := mkdirAll(s.Dirname); err != nil { 516 return "", s.errorf(err, "failed to create juju-managed service dir %q", s.Dirname) 517 } 518 filename := path.Join(s.Dirname, s.ConfName) 519 520 if s.Script != nil { 521 scriptPath := renderer.ScriptFilename("exec-start", s.Dirname) 522 if scriptPath != s.Service.Conf.ExecStart { 523 err := errors.Errorf("wrong script path: expected %q, got %q", scriptPath, s.Service.Conf.ExecStart) 524 return filename, s.errorf(err, "failed to write script at %q", scriptPath) 525 } 526 // TODO(ericsnow) Use the renderer here for the perms. 527 if err := createFile(scriptPath, s.Script, 0755); err != nil { 528 return filename, s.errorf(err, "failed to write script at %q", scriptPath) 529 } 530 } 531 532 if err := createFile(filename, data, 0644); err != nil { 533 return filename, s.errorf(err, "failed to write conf file %q", filename) 534 } 535 536 return filename, nil 537 } 538 539 var mkdirAll = func(dirname string) error { 540 return os.MkdirAll(dirname, 0755) 541 } 542 543 var createFile = func(filename string, data []byte, perm os.FileMode) error { 544 return ioutil.WriteFile(filename, data, perm) 545 } 546 547 // InstallCommands implements Service. 548 func (s *Service) InstallCommands() ([]string, error) { 549 if s.NoConf() { 550 return nil, s.errorf(nil, "missing conf") 551 } 552 553 name := s.Name() 554 dirname := s.Dirname 555 556 data, err := s.serialize() 557 if err != nil { 558 return nil, errors.Trace(err) 559 } 560 561 cmdList := []string{ 562 cmds.mkdirs(dirname), 563 } 564 if s.Script != nil { 565 scriptName := renderer.Base(renderer.ScriptFilename("exec-start", "")) 566 cmdList = append(cmdList, []string{ 567 // TODO(ericsnow) Use the renderer here. 568 cmds.writeFile(scriptName, dirname, s.Script), 569 cmds.chmod(scriptName, dirname, 0755), 570 }...) 571 } 572 cmdList = append(cmdList, []string{ 573 cmds.writeConf(name, dirname, data), 574 cmds.link(name, dirname), 575 cmds.reload(), 576 cmds.enableLinked(name, dirname), 577 }...) 578 return cmdList, nil 579 } 580 581 // StartCommands implements Service. 582 func (s *Service) StartCommands() ([]string, error) { 583 name := s.Name() 584 cmdList := []string{ 585 cmds.start(name), 586 } 587 return cmdList, nil 588 }