github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 service %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 var installStartRetryAttempts = utils.AttemptStrategy{ 199 Total: 1 * time.Second, 200 Delay: 250 * time.Millisecond, 201 } 202 203 // InstallAndStart installs the provided service and tries starting it. 204 // The first few Start failures are ignored. 205 func InstallAndStart(svc ServiceActions) error { 206 if err := svc.Install(); err != nil { 207 return errors.Trace(err) 208 } 209 210 // For various reasons the init system may take a short time to 211 // realise that the service has been installed. 212 var err error 213 for attempt := installStartRetryAttempts.Start(); attempt.Next(); { 214 if err != nil { 215 logger.Errorf("retrying start request (%v)", errors.Cause(err)) 216 } 217 218 if err = svc.Start(); err == nil { 219 break 220 } 221 } 222 return errors.Trace(err) 223 } 224 225 // discoverService is patched out during some tests. 226 var discoverService = func(name string) (Service, error) { 227 return DiscoverService(name, common.Conf{}) 228 } 229 230 // TODO(ericsnow) Add one-off helpers for Start and Stop too? 231 232 // Restart restarts the named service. 233 func Restart(name string) error { 234 svc, err := discoverService(name) 235 if err != nil { 236 return errors.Annotatef(err, "failed to find service %q", name) 237 } 238 if err := restart(svc); err != nil { 239 return errors.Annotatef(err, "failed to restart service %q", name) 240 } 241 return nil 242 } 243 244 func restart(svc Service) error { 245 // Use the Restart method, if there is one. 246 if svc, ok := svc.(RestartableService); ok { 247 if err := svc.Restart(); err != nil { 248 return errors.Trace(err) 249 } 250 return nil 251 } 252 253 // Otherwise explicitly stop and start the service. 254 if err := svc.Stop(); err != nil { 255 return errors.Trace(err) 256 } 257 if err := svc.Start(); err != nil { 258 return errors.Trace(err) 259 } 260 return nil 261 }