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 }