Break Service interface into smaller chunks. Add Start and Stop methods.

This commit is contained in:
Daniel Theophanes
2012-10-22 23:44:42 -07:00
parent e339dcfdd0
commit 132c4b486b
5 changed files with 128 additions and 43 deletions
+28 -9
View File
@@ -6,11 +6,15 @@ import (
"os"
)
var localLog service.Logger
func main() {
var name = "GoServiceTest"
var displayName = "Go Service Test"
var desc = "This is a test Go service. It is designed to run well."
var ws, err = service.NewService(name, displayName, desc)
var s, err = service.NewService(name, displayName, desc)
localLog = s
if err != nil {
fmt.Printf("%s unable to start: %s", displayName, err)
@@ -22,41 +26,56 @@ func main() {
verb := os.Args[1]
switch verb {
case "install":
err = ws.Install()
err = s.Install()
if err != nil {
fmt.Printf("Failed to install: %s\n", err)
return
}
fmt.Printf("Service \"%s\" installed.\n", displayName)
case "remove":
err = ws.Remove()
err = s.Remove()
if err != nil {
fmt.Printf("Failed to remove: %s\n", err)
return
}
fmt.Printf("Service \"%s\" removed.\n", displayName)
case "run":
doWork()
case "start":
err = s.Start()
if err != nil {
fmt.Printf("Failed to start: %s\n", err)
return
}
fmt.Printf("Service \"%s\" started.\n", displayName)
case "stop":
err = s.Stop()
if err != nil {
fmt.Printf("Failed to stop: %s\n", err)
return
}
fmt.Printf("Service \"%s\" stopped.\n", displayName)
}
return
}
err = ws.Run(func() error {
err = s.Run(func() error {
// start
go doWork()
ws.LogInfo("I'm Running!")
return nil
}, func() error {
// stop
stopWork()
ws.LogInfo("I'm Stopping!")
return nil
})
if err != nil {
ws.LogError(err.Error())
s.LogError(err.Error())
}
}
func doWork() {
localLog.LogInfo("I'm Running!")
select {}
}
func stopWork() {
localLog.LogInfo("I'm Stopping!")
}
+32 -8
View File
@@ -1,5 +1,5 @@
// Package service provides a simple way to create a system service.
// Currently supports Windows and Linux/Upstart.
// Currently supports Windows, Linux/Upstart, OSX/Launchd.
package service
// Creates a new service. name is the internal name
@@ -12,14 +12,14 @@ func NewService(name, displayName, description string) (Service, error) {
// Represents a generic way to interact with the system's service.
type Service interface {
// Installs this service on the system. May return an
// error if this service is already installed.
Install() error
// Removes this service from the system. May return an
// error if this service is not already installed.
Remove() error
Installer
Controller
Runner
Logger
}
// A Generic way to stop and start a service.
type Runner interface {
// Call quickly after initial entry point. Does not return until
// service is ready to stop. onStart is called when the service is
// starting, returning an error will fail to start the service.
@@ -28,7 +28,31 @@ type Service interface {
// an error from Run.
// Both callbacks should return quickly and not block.
Run(onStart, onStop func() error) error
}
// Simple install and remove commands.
type Installer interface {
// Installs this service on the system. May return an
// error if this service is already installed.
Install() error
// Removes this service from the system. May return an
// error if this service is not already installed.
Remove() error
}
// A service that implements ServiceController is able to
// start and stop itself.
type Controller interface {
// Starts this service on the system.
Start() error
// Stops this service on the system.
Stop() error
}
// A service that implements ServiceLogger can perform simple system logging.
type Logger interface {
// Basic log functions in the context of the service.
LogError(format string, a ...interface{}) error
LogWarning(format string, a ...interface{}) error
+22 -25
View File
@@ -5,8 +5,8 @@ package service
#include <string.h>
int
GetExecPath(char* path) {
uint32_t size = 32*1024;
GetExecPath(char* path, int bufferSize) {
uint32_t size = bufferSize;
if (_NSGetExecutablePath(path, &size) == 0) {
// Despite Apple docs, size does NOT get set in call.
return strlen(path);
@@ -28,7 +28,8 @@ import (
"text/template"
)
// BUG(kardia): I have not confirmed this service works as intended on launchd.
const maxPathSize = 32 * 1024
func newService(name, displayName, description string) (s *darwinLaunchdService, err error) {
s = &darwinLaunchdService{
name: name,
@@ -83,30 +84,26 @@ func (s *darwinLaunchdService) Install() error {
path,
}
t := template.Must(template.New("upstartScript").Parse(upstartScript))
err = t.Execute(f, to)
if err != nil {
return err
}
cmd := exec.Command("launchctl", "load", confPath)
err = cmd.Run()
if err != nil {
return err
}
return nil
t := template.Must(template.New("launchdConfig").Parse(launchdConfig))
return t.Execute(f, to)
}
func (s *darwinLaunchdService) Remove() error {
s.Stop()
confPath := s.getServiceFilePath()
return os.Remove(confPath)
}
func (s *darwinLaunchdService) Start() error {
confPath := s.getServiceFilePath()
cmd := exec.Command("launchctl", "load", confPath)
return cmd.Run()
}
func (s *darwinLaunchdService) Stop() error {
confPath := s.getServiceFilePath()
cmd := exec.Command("launchctl", "unload", confPath)
err := cmd.Run()
// Don't worry about his error if not found.
os.Remove(confPath)
return err
return cmd.Run()
}
func (s *darwinLaunchdService) Run(onStart, onStop func() error) error {
@@ -137,8 +134,8 @@ func (s *darwinLaunchdService) LogInfo(format string, a ...interface{}) error {
}
func getExePath() (exePath string, err error) {
buffer := make([]byte, 32*1024)
size := C.GetExecPath((*C.char)(unsafe.Pointer(&buffer[0])))
buffer := make([]byte, maxPathSize)
size := C.GetExecPath((*C.char)(unsafe.Pointer(&buffer[0])), maxPathSize)
if size == 0 {
return "", errors.New("Unable to get exec path.")
}
@@ -147,7 +144,7 @@ func getExePath() (exePath string, err error) {
return ret, nil
}
var upstartScript = `<?xml version='1.0' encoding='UTF-8'?>
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
<plist version='1.0'>
+15 -1
View File
@@ -4,8 +4,10 @@ import (
"fmt"
"log/syslog"
"os"
"os/exec"
"os/signal"
"text/template"
"path/filepath"
)
func newService(name, displayName, description string) (s *linuxUpstartService, err error) {
@@ -87,6 +89,16 @@ func (s *linuxUpstartService) Run(onStart, onStop func() error) error {
return onStop()
}
func (s *linuxUpstartService) Start() error {
cmd := exec.Command("start", s.name)
return cmd.Run()
}
func (s *linuxUpstartService) Stop() error {
cmd := exec.Command("stop", s.name)
return cmd.Run()
}
func (s *linuxUpstartService) LogError(format string, a ...interface{}) error {
return s.logger.Err(fmt.Sprintf(format, a...))
}
@@ -98,7 +110,9 @@ func (s *linuxUpstartService) LogInfo(format string, a ...interface{}) error {
}
func getExePath() (exePath string, err error) {
return os.Readlink(`/proc/self/exe`)
exePath, err = os.Readlink(`/proc/self/exe`)
exePath = filepath.Clean(exePath)
return
}
var upstartScript = `# {{.Description}}
+31
View File
@@ -124,6 +124,37 @@ func (ws *windowsService) Run(onStart, onStop func() error) error {
return svc.Run(ws.name, ws)
}
func (ws *windowsService) Start() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
if err != nil {
return err
}
defer s.Close()
return s.Start([]string{})
}
func (ws *windowsService) Stop() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
if err != nil {
return err
}
defer s.Close()
_, err = s.Control(svc.Stop)
return err
}
func (ws *windowsService) LogError(format string, a ...interface{}) error {
if ws.logger == nil {
return nil