github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/os/series"
    14  	"github.com/juju/utils"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/service/common"
    18  	"github.com/juju/juju/service/snap"
    19  	"github.com/juju/juju/service/systemd"
    20  	"github.com/juju/juju/service/upstart"
    21  	"github.com/juju/juju/service/windows"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.service")
    25  
    26  // These are the names of the init systems recognized by juju.
    27  const (
    28  	InitSystemSystemd = "systemd"
    29  	InitSystemUpstart = "upstart"
    30  	InitSystemWindows = "windows"
    31  	InitSystemSnap    = "snap"
    32  )
    33  
    34  // linuxInitSystems lists the names of the init systems that juju might
    35  // find on a linux host.
    36  var linuxInitSystems = []string{
    37  	InitSystemSystemd,
    38  	InitSystemUpstart,
    39  	// InitSystemSnap is not part of this list, so that
    40  	// the discovery machinery can't select snap over systemd
    41  }
    42  
    43  // ServiceActions represents the actions that may be requested for
    44  // an init system service.
    45  type ServiceActions interface {
    46  	// Start will try to start the service.
    47  	Start() error
    48  
    49  	// Stop will try to stop the service.
    50  	Stop() error
    51  
    52  	// Install installs a service.
    53  	Install() error
    54  
    55  	// Remove will remove the service.
    56  	Remove() error
    57  }
    58  
    59  // Service represents a service in the init system running on a host.
    60  type Service interface {
    61  	ServiceActions
    62  
    63  	// Name returns the service's name.
    64  	Name() string
    65  
    66  	// Conf returns the service's conf data.
    67  	Conf() common.Conf
    68  
    69  	// Running returns a boolean value that denotes
    70  	// whether or not the service is running.
    71  	Running() (bool, error)
    72  
    73  	// Exists returns whether the service configuration exists in the
    74  	// init directory with the same content that this Service would have
    75  	// if installed.
    76  	Exists() (bool, error)
    77  
    78  	// Installed will return a boolean value that denotes
    79  	// whether or not the service is installed.
    80  	Installed() (bool, error)
    81  
    82  	// TODO(ericsnow) Move all the commands into a separate interface.
    83  
    84  	// InstallCommands returns the list of commands to run on a
    85  	// (remote) host to install the service.
    86  	InstallCommands() ([]string, error)
    87  
    88  	// StartCommands returns the list of commands to run on a
    89  	// (remote) host to start the service.
    90  	StartCommands() ([]string, error)
    91  }
    92  
    93  // ConfigureableService performs tasks that need to occur between the software
    94  // has been installed and when has started
    95  type ConfigureableService interface {
    96  	// Configure performs any necessary configuration steps
    97  	Configure() error
    98  
    99  	// ReConfigureDuringRestart indicates whether Configure
   100  	// should be called during a restart
   101  	ReConfigureDuringRestart() bool
   102  }
   103  
   104  // RestartableService is a service that directly supports restarting.
   105  type RestartableService interface {
   106  	// Restart restarts the service.
   107  	Restart() error
   108  }
   109  
   110  type UpgradableService interface {
   111  	// WriteService writes the service conf data. If the service is
   112  	// running, WriteService adds links to allow for manual and automatic
   113  	// starting of the service.
   114  	WriteService() error
   115  }
   116  
   117  // TODO(ericsnow) bug #1426458
   118  // Eliminate the need to pass an empty conf for most service methods
   119  // and several helper functions.
   120  
   121  // NewService returns a new Service based on the provided info.
   122  var NewService = func(name string, conf common.Conf, series string) (Service, error) {
   123  	if name == "" {
   124  		return nil, errors.New("missing name")
   125  	}
   126  
   127  	initSystem, err := versionInitSystem(series)
   128  	if err != nil {
   129  		return nil, errors.Trace(err)
   130  	}
   131  	return newService(name, conf, initSystem, series)
   132  }
   133  
   134  // this needs to be stubbed out in some tests
   135  func newService(name string, conf common.Conf, initSystem, series string) (Service, error) {
   136  	var svc Service
   137  	var err error
   138  
   139  	switch initSystem {
   140  	case InitSystemWindows:
   141  		svc, err = windows.NewService(name, conf)
   142  	case InitSystemUpstart:
   143  		svc, err = upstart.NewService(name, conf), nil
   144  	case InitSystemSystemd:
   145  		svc, err = systemd.NewServiceWithDefaults(name, conf)
   146  	case InitSystemSnap:
   147  		svc, err = snap.NewServiceFromName(name, conf)
   148  	default:
   149  		return nil, errors.NotFoundf("init system %q", initSystem)
   150  	}
   151  
   152  	if err != nil {
   153  		return nil, errors.Annotatef(err, "failed to wrap service %q", name)
   154  	}
   155  	return svc, nil
   156  }
   157  
   158  // ListServices lists all installed services on the running system
   159  var ListServices = func() ([]string, error) {
   160  	hostSeries, err := series.HostSeries()
   161  	if err != nil {
   162  		return nil, errors.Trace(err)
   163  	}
   164  	initName, err := VersionInitSystem(hostSeries)
   165  	if err != nil {
   166  		return nil, errors.Trace(err)
   167  	}
   168  	var services []string
   169  	switch initName {
   170  	case InitSystemWindows:
   171  		services, err = windows.ListServices()
   172  	case InitSystemSnap:
   173  		services, err = snap.ListServices()
   174  	case InitSystemUpstart:
   175  		services, err = upstart.ListServices()
   176  	case InitSystemSystemd:
   177  		services, err = systemd.ListServices()
   178  	default:
   179  		return nil, errors.NotFoundf("init system %q", initName)
   180  	}
   181  	if err != nil {
   182  		return nil, errors.Annotatef(err, "failed to list %s services", initName)
   183  	}
   184  	return services, nil
   185  }
   186  
   187  // ListServicesScript returns the commands that should be run to get
   188  // a list of service names on a host.
   189  func ListServicesScript() string {
   190  	commands := []string{
   191  		"init_system=$(" + DiscoverInitSystemScript() + ")",
   192  		// If the init system is not identified then the script will
   193  		// "exit 1". This is correct since the script should fail if no
   194  		// init system can be identified.
   195  		newShellSelectCommand("init_system", "exit 1", listServicesCommand),
   196  	}
   197  	return strings.Join(commands, "\n")
   198  }
   199  
   200  func listServicesCommand(initSystem string) (string, bool) {
   201  	switch initSystem {
   202  	case InitSystemWindows:
   203  		return windows.ListCommand(), true
   204  	case InitSystemUpstart:
   205  		return upstart.ListCommand(), true
   206  	case InitSystemSystemd:
   207  		return systemd.ListCommand(), true
   208  	case InitSystemSnap:
   209  		return snap.ListCommand(), true
   210  	default:
   211  		return "", false
   212  	}
   213  }
   214  
   215  // installStartRetryAttempts defines how much InstallAndStart retries
   216  // upon Start failures.
   217  //
   218  // TODO(katco): 2016-08-09: lp:1611427
   219  var installStartRetryAttempts = utils.AttemptStrategy{
   220  	Total: 1 * time.Second,
   221  	Delay: 250 * time.Millisecond,
   222  }
   223  
   224  // InstallAndStart installs the provided service and tries starting it.
   225  // The first few Start failures are ignored.
   226  func InstallAndStart(svc ServiceActions) error {
   227  	logger.Infof("Installing and starting service %+v", svc)
   228  	if err := svc.Install(); err != nil {
   229  		return errors.Trace(err)
   230  	}
   231  
   232  	// For various reasons the init system may take a short time to
   233  	// realise that the service has been installed.
   234  	var err error
   235  	for attempt := installStartRetryAttempts.Start(); attempt.Next(); {
   236  		if err != nil {
   237  			logger.Errorf("retrying start request (%v)", errors.Cause(err))
   238  		}
   239  		// we attempt restart if the service is running in case daemon parameters
   240  		// have changed, if its not running a regular start will happen.
   241  		if err = ManuallyRestart(svc); err == nil {
   242  			logger.Debugf("started %v", svc)
   243  			break
   244  		}
   245  	}
   246  	return errors.Trace(err)
   247  }
   248  
   249  // discoverService is patched out during some tests.
   250  var discoverService = func(name string) (Service, error) {
   251  	return DiscoverService(name, common.Conf{})
   252  }
   253  
   254  // TODO(ericsnow) Add one-off helpers for Start and Stop too?
   255  
   256  // Restart restarts the named service.
   257  func Restart(name string) error {
   258  	svc, err := discoverService(name)
   259  	if err != nil {
   260  		return errors.Annotatef(err, "failed to find service %q", name)
   261  	}
   262  	if err := ManuallyRestart(svc); err != nil {
   263  		return errors.Annotatef(err, "failed to restart service %q", name)
   264  	}
   265  	return nil
   266  }
   267  
   268  // ManuallyRestart restarts the service by applying
   269  // its Restart method or by falling back to calling Stop and Start
   270  func ManuallyRestart(svc ServiceActions) error {
   271  	// TODO(tsm): fix service.upstart behaviour to match other implementations
   272  	// if restartableService, ok := svc.(RestartableService); ok {
   273  	// 	if err := restartableService.Restart(); err != nil {
   274  	// 		return errors.Trace(err)
   275  	// 	}
   276  	// 	return nil
   277  	// }
   278  
   279  	if err := svc.Stop(); err != nil {
   280  		logger.Errorf("could not stop service: %v", err)
   281  	}
   282  	configureableService, ok := svc.(ConfigureableService)
   283  	if ok && configureableService.ReConfigureDuringRestart() {
   284  		if err := configureableService.Configure(); err != nil {
   285  			return errors.Trace(err)
   286  		}
   287  		return nil
   288  	}
   289  	if err := svc.Start(); err != nil {
   290  		return errors.Trace(err)
   291  	}
   292  	return nil
   293  
   294  }
   295  
   296  // FindUnitServiceNames accepts a collection of service names as managed by the
   297  // local init system. Any that are identified as being for unit agents are
   298  // returned, keyed on the unit name.
   299  func FindUnitServiceNames(svcNames []string) map[string]string {
   300  	svcMatcher := regexp.MustCompile("^(jujud-.*unit-([a-z0-9-]+)-([0-9]+))$")
   301  	unitServices := make(map[string]string)
   302  	for _, svc := range svcNames {
   303  		if groups := svcMatcher.FindStringSubmatch(svc); len(groups) > 0 {
   304  			unitName := groups[2] + "/" + groups[3]
   305  			if names.IsValidUnit(unitName) {
   306  				unitServices[unitName] = groups[1]
   307  			}
   308  		}
   309  	}
   310  	return unitServices
   311  }