github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/service/service.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package service
     5  
     6  import (
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/utils"
    13  	"github.com/juju/utils/series"
    14  
    15  	"github.com/juju/juju/juju/paths"
    16  	"github.com/juju/juju/service/common"
    17  	"github.com/juju/juju/service/systemd"
    18  	"github.com/juju/juju/service/upstart"
    19  	"github.com/juju/juju/service/windows"
    20  )
    21  
    22  var (
    23  	logger = loggo.GetLogger("juju.service")
    24  )
    25  
    26  // These are the names of the init systems regognized by juju.
    27  const (
    28  	InitSystemSystemd = "systemd"
    29  	InitSystemUpstart = "upstart"
    30  	InitSystemWindows = "windows"
    31  )
    32  
    33  // linuxInitSystems lists the names of the init systems that juju might
    34  // find on a linux host.
    35  var linuxInitSystems = []string{
    36  	InitSystemSystemd,
    37  	InitSystemUpstart,
    38  }
    39  
    40  // ServiceActions represents the actions that may be requested for
    41  // an init system service.
    42  type ServiceActions interface {
    43  	// Start will try to start the service.
    44  	Start() error
    45  
    46  	// Stop will try to stop the service.
    47  	Stop() error
    48  
    49  	// Install installs a service.
    50  	Install() error
    51  
    52  	// Remove will remove the service.
    53  	Remove() error
    54  }
    55  
    56  // Service represents a service in the init system running on a host.
    57  type Service interface {
    58  	ServiceActions
    59  
    60  	// Name returns the service's name.
    61  	Name() string
    62  
    63  	// Conf returns the service's conf data.
    64  	Conf() common.Conf
    65  
    66  	// Running returns a boolean value that denotes
    67  	// whether or not the service is running.
    68  	Running() (bool, error)
    69  
    70  	// Exists returns whether the service configuration exists in the
    71  	// init directory with the same content that this Service would have
    72  	// if installed.
    73  	Exists() (bool, error)
    74  
    75  	// Installed will return a boolean value that denotes
    76  	// whether or not the service is installed.
    77  	Installed() (bool, error)
    78  
    79  	// TODO(ericsnow) Move all the commands into a separate interface.
    80  
    81  	// InstallCommands returns the list of commands to run on a
    82  	// (remote) host to install the service.
    83  	InstallCommands() ([]string, error)
    84  
    85  	// StartCommands returns the list of commands to run on a
    86  	// (remote) host to start the service.
    87  	StartCommands() ([]string, error)
    88  }
    89  
    90  // RestartableService is a service that directly supports restarting.
    91  type RestartableService interface {
    92  	// Restart restarts the service.
    93  	Restart() error
    94  }
    95  
    96  // TODO(ericsnow) bug #1426458
    97  // Eliminate the need to pass an empty conf for most service methods
    98  // and several helper functions.
    99  
   100  // NewService returns a new Service based on the provided info.
   101  func NewService(name string, conf common.Conf, series string) (Service, error) {
   102  	if name == "" {
   103  		return nil, errors.New("missing name")
   104  	}
   105  
   106  	initSystem, err := versionInitSystem(series)
   107  	if err != nil {
   108  		return nil, errors.Trace(err)
   109  	}
   110  	return newService(name, conf, initSystem, series)
   111  }
   112  
   113  func newService(name string, conf common.Conf, initSystem, series string) (Service, error) {
   114  	switch initSystem {
   115  	case InitSystemWindows:
   116  		svc, err := windows.NewService(name, conf)
   117  		if err != nil {
   118  			return nil, errors.Annotatef(err, "failed to wrap service %q", name)
   119  		}
   120  		return svc, nil
   121  	case InitSystemUpstart:
   122  		return upstart.NewService(name, conf), nil
   123  	case InitSystemSystemd:
   124  		dataDir, err := paths.DataDir(series)
   125  		if err != nil {
   126  			return nil, errors.Annotatef(err, "failed to find juju data dir for application %q", name)
   127  		}
   128  
   129  		svc, err := systemd.NewService(name, conf, dataDir)
   130  		if err != nil {
   131  			return nil, errors.Annotatef(err, "failed to wrap service %q", name)
   132  		}
   133  		return svc, nil
   134  	default:
   135  		return nil, errors.NotFoundf("init system %q", initSystem)
   136  	}
   137  }
   138  
   139  // ListServices lists all installed services on the running system
   140  func ListServices() ([]string, error) {
   141  	initName, err := VersionInitSystem(series.HostSeries())
   142  	if err != nil {
   143  		return nil, errors.Trace(err)
   144  	}
   145  
   146  	switch initName {
   147  	case InitSystemWindows:
   148  		services, err := windows.ListServices()
   149  		if err != nil {
   150  			return nil, errors.Annotatef(err, "failed to list %s services", initName)
   151  		}
   152  		return services, nil
   153  	case InitSystemUpstart:
   154  		services, err := upstart.ListServices()
   155  		if err != nil {
   156  			return nil, errors.Annotatef(err, "failed to list %s services", initName)
   157  		}
   158  		return services, nil
   159  	case InitSystemSystemd:
   160  		services, err := systemd.ListServices()
   161  		if err != nil {
   162  			return nil, errors.Annotatef(err, "failed to list %s services", initName)
   163  		}
   164  		return services, nil
   165  	default:
   166  		return nil, errors.NotFoundf("init system %q", initName)
   167  	}
   168  }
   169  
   170  // ListServicesScript returns the commands that should be run to get
   171  // a list of service names on a host.
   172  func ListServicesScript() string {
   173  	commands := []string{
   174  		"init_system=$(" + DiscoverInitSystemScript() + ")",
   175  		// If the init system is not identified then the script will
   176  		// "exit 1". This is correct since the script should fail if no
   177  		// init system can be identified.
   178  		newShellSelectCommand("init_system", "exit 1", listServicesCommand),
   179  	}
   180  	return strings.Join(commands, "\n")
   181  }
   182  
   183  func listServicesCommand(initSystem string) (string, bool) {
   184  	switch initSystem {
   185  	case InitSystemWindows:
   186  		return windows.ListCommand(), true
   187  	case InitSystemUpstart:
   188  		return upstart.ListCommand(), true
   189  	case InitSystemSystemd:
   190  		return systemd.ListCommand(), true
   191  	default:
   192  		return "", false
   193  	}
   194  }
   195  
   196  // installStartRetryAttempts defines how much InstallAndStart retries
   197  // upon Start failures.
   198  //
   199  // TODO(katco): 2016-08-09: lp:1611427
   200  var installStartRetryAttempts = utils.AttemptStrategy{
   201  	Total: 1 * time.Second,
   202  	Delay: 250 * time.Millisecond,
   203  }
   204  
   205  // InstallAndStart installs the provided service and tries starting it.
   206  // The first few Start failures are ignored.
   207  func InstallAndStart(svc ServiceActions) error {
   208  	logger.Infof("Installing and starting service %+v", svc)
   209  	if err := svc.Install(); err != nil {
   210  		return errors.Trace(err)
   211  	}
   212  
   213  	// For various reasons the init system may take a short time to
   214  	// realise that the service has been installed.
   215  	var err error
   216  	for attempt := installStartRetryAttempts.Start(); attempt.Next(); {
   217  		if err != nil {
   218  			logger.Errorf("retrying start request (%v)", errors.Cause(err))
   219  		}
   220  
   221  		if err = svc.Start(); err == nil {
   222  			break
   223  		}
   224  	}
   225  	return errors.Trace(err)
   226  }
   227  
   228  // discoverService is patched out during some tests.
   229  var discoverService = func(name string) (Service, error) {
   230  	return DiscoverService(name, common.Conf{})
   231  }
   232  
   233  // TODO(ericsnow) Add one-off helpers for Start and Stop too?
   234  
   235  // Restart restarts the named service.
   236  func Restart(name string) error {
   237  	svc, err := discoverService(name)
   238  	if err != nil {
   239  		return errors.Annotatef(err, "failed to find service %q", name)
   240  	}
   241  	if err := restart(svc); err != nil {
   242  		return errors.Annotatef(err, "failed to restart service %q", name)
   243  	}
   244  	return nil
   245  }
   246  
   247  func restart(svc Service) error {
   248  	// Use the Restart method, if there is one.
   249  	if svc, ok := svc.(RestartableService); ok {
   250  		if err := svc.Restart(); err != nil {
   251  			return errors.Trace(err)
   252  		}
   253  		return nil
   254  	}
   255  
   256  	// Otherwise explicitly stop and start the service.
   257  	if err := svc.Stop(); err != nil {
   258  		return errors.Trace(err)
   259  	}
   260  	if err := svc.Start(); err != nil {
   261  		return errors.Trace(err)
   262  	}
   263  	return nil
   264  }