github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/juju/clock"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/names/v5"
    15  	"github.com/juju/retry"
    16  
    17  	"github.com/juju/juju/service/common"
    18  	"github.com/juju/juju/service/systemd"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.service")
    22  
    23  const (
    24  	InitSystemSystemd = "systemd"
    25  )
    26  
    27  // ServiceActions represents the actions that may be requested for
    28  // an init system service.
    29  type ServiceActions interface {
    30  	// Start will try to start the service.
    31  	Start() error
    32  
    33  	// Stop will try to stop the service.
    34  	Stop() error
    35  
    36  	// Install installs a service.
    37  	Install() error
    38  
    39  	// Remove will remove the service.
    40  	Remove() error
    41  }
    42  
    43  // Service represents a service in the init system running on a host.
    44  type Service interface {
    45  	ServiceActions
    46  
    47  	// Name returns the service's name.
    48  	Name() string
    49  
    50  	// Conf returns the service's conf data.
    51  	Conf() common.Conf
    52  
    53  	// Running returns a boolean value that denotes
    54  	// whether or not the service is running.
    55  	Running() (bool, error)
    56  
    57  	// Exists returns whether the service configuration exists in the
    58  	// init directory with the same content that this Service would have
    59  	// if installed.
    60  	Exists() (bool, error)
    61  
    62  	// Installed will return a boolean value that denotes
    63  	// whether or not the service is installed.
    64  	Installed() (bool, error)
    65  
    66  	// TODO(ericsnow) Move all the commands into a separate interface.
    67  
    68  	// InstallCommands returns the list of commands to run on a
    69  	// (remote) host to install the service.
    70  	InstallCommands() ([]string, error)
    71  
    72  	// StartCommands returns the list of commands to run on a
    73  	// (remote) host to start the service.
    74  	StartCommands() ([]string, error)
    75  
    76  	// WriteService writes the service conf data. If the service is
    77  	// running, WriteService adds links to allow for manual and automatic
    78  	// starting of the service.
    79  	WriteService() error
    80  }
    81  
    82  // RestartableService is a service that directly supports restarting.
    83  type RestartableService interface {
    84  	// Restart restarts the service.
    85  	Restart() error
    86  }
    87  
    88  // NewService returns a new Service based on the provided info.
    89  var NewService = func(name string, conf common.Conf) (Service, error) {
    90  	if name == "" {
    91  		return nil, errors.New("missing name")
    92  	}
    93  	return systemd.NewServiceWithDefaults(name, conf)
    94  }
    95  
    96  // NewServiceReference returns a reference to an existing Service
    97  // and can be used to start/stop the service.
    98  func NewServiceReference(name string) (Service, error) {
    99  	return NewService(name, common.Conf{})
   100  }
   101  
   102  // ListServices lists all installed services on the running system
   103  var ListServices = func() ([]string, error) {
   104  	services, err := systemd.ListServices()
   105  	return services, errors.Annotatef(err, "failed to list systemd services")
   106  }
   107  
   108  // ListServicesScript returns the commands that should be run to get
   109  // a list of service names on a host.
   110  func ListServicesScript() string {
   111  	return systemd.ListCommand()
   112  }
   113  
   114  // installStartRetryAttempts defines how much InstallAndStart retries
   115  // upon Start failures.
   116  var installStartRetryStrategy = retry.CallArgs{
   117  	Clock:       clock.WallClock,
   118  	MaxDuration: 1 * time.Second,
   119  	Delay:       250 * time.Millisecond,
   120  }
   121  
   122  // InstallAndStart installs the provided service and tries starting it.
   123  // The first few Start failures are ignored.
   124  func InstallAndStart(svc ServiceActions) error {
   125  	service, ok := svc.(Service)
   126  	if !ok {
   127  		return errors.Errorf("specified service has no name %+v", svc)
   128  	}
   129  	logger.Infof("Installing and starting service %s", service.Name())
   130  	logger.Debugf("Installing and starting service %+v", svc)
   131  
   132  	if err := svc.Install(); err != nil {
   133  		return errors.Trace(err)
   134  	}
   135  
   136  	// For various reasons the init system may take a short time to
   137  	// realise that the service has been installed.
   138  	retryStrategy := installStartRetryStrategy
   139  	retryStrategy.Func = func() error { return manuallyRestart(svc) }
   140  	retryStrategy.NotifyFunc = func(lastError error, _ int) {
   141  		logger.Errorf("retrying start request (%v)", errors.Cause(lastError))
   142  	}
   143  	err := retry.Call(retryStrategy)
   144  	if err != nil {
   145  		err = retry.LastError(err)
   146  		return errors.Trace(err)
   147  	}
   148  	return nil
   149  }
   150  
   151  // Restart restarts the named service.
   152  func Restart(name string) error {
   153  	svc, err := NewServiceReference(name)
   154  	if err != nil {
   155  		return errors.Annotatef(err, "failed to find service %q", name)
   156  	}
   157  	if err := manuallyRestart(svc); err != nil {
   158  		return errors.Annotatef(err, "failed to restart service %q", name)
   159  	}
   160  	return nil
   161  }
   162  
   163  func manuallyRestart(svc ServiceActions) error {
   164  	if err := svc.Stop(); err != nil {
   165  		logger.Errorf("could not stop service: %v", err)
   166  	}
   167  	if err := svc.Start(); err != nil {
   168  		return errors.Trace(err)
   169  	}
   170  	return nil
   171  }
   172  
   173  // FindAgents finds all the agents available on the machine.
   174  func FindAgents(dataDir string) (string, []string, []string, error) {
   175  	var (
   176  		machineAgent  string
   177  		unitAgents    []string
   178  		errAgentNames []string
   179  	)
   180  
   181  	agentDir := filepath.Join(dataDir, "agents")
   182  
   183  	entries, err := os.ReadDir(agentDir)
   184  	if err != nil {
   185  		return "", nil, nil, errors.Annotate(err, "reading agents dir")
   186  	}
   187  	for _, info := range entries {
   188  		name := info.Name()
   189  		tag, err := names.ParseTag(name)
   190  		if err != nil {
   191  			continue
   192  		}
   193  		switch tag.Kind() {
   194  		case names.MachineTagKind:
   195  			machineAgent = name
   196  		case names.UnitTagKind:
   197  			unitAgents = append(unitAgents, name)
   198  		default:
   199  			errAgentNames = append(errAgentNames, name)
   200  			logger.Infof("%s is not of type Machine nor Unit, ignoring", name)
   201  		}
   202  	}
   203  	return machineAgent, unitAgents, errAgentNames, nil
   204  }