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 }