github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 }