forked from juju/gocharm
/
service.go
236 lines (203 loc) · 5.86 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package hooktest
import (
"sync"
"gopkg.in/errgo.v1"
"github.com/mever/gocharm/charmbits/service"
"github.com/mever/gocharm/hook"
)
// NewServiceFunc returns a function that can be used to create
// new services and can be assigned to service.NewService
// to mock the OS-level service creator.
// When the service is started, the given Runner
// will be used to run it (via hook.Main).
//
// If the notify channel is not nil, it will be used to send
// events about services created with the function.
// It should be buffered with a size of at least 2.
func NewServiceFunc(r *Runner, notify chan ServiceEvent) func(service.OSServiceParams) service.OSService {
services := &osServices{
notify: notify,
runner: r,
installed: make(map[string]*installedOSService),
}
return func(p service.OSServiceParams) service.OSService {
return &osService{
params: p,
services: services,
}
}
}
type osServices struct {
notify chan ServiceEvent
runner *Runner
// mu guards the following fields and services.
mu sync.Mutex
installed map[string]*installedOSService
}
// osService provides a mock implementation of the
// service.OSService interface.
type osService struct {
params service.OSServiceParams
services *osServices
}
type installedOSService struct {
services *osServices
// params holds the parameters that the service
// was installed with.
params service.OSServiceParams
// The following fields are guarded by services.mu.
// When the service is running, cmd holds
// the running command.
cmd hook.Command
}
func (isvc *installedOSService) notify(kind ServiceEventKind, err error) {
if isvc.services.notify == nil {
return
}
if err != nil {
isvc.logf("hooktest: service event %v (err %v)", kind, err)
} else {
isvc.logf("hooktest: service event %v", kind)
}
isvc.services.notify <- ServiceEvent{
Kind: kind,
Params: isvc.params,
Error: err,
}
}
func (isvc *installedOSService) logf(f string, a ...interface{}) {
isvc.services.runner.Logger.Logf(f, a...)
}
// ServiceEvent represents an event on a service
// created by the function returned from NewServiceFunc.
type ServiceEvent struct {
// Kind holds the kind of the service event.
Kind ServiceEventKind
// Params holds the parameters used to create the service,
Params service.OSServiceParams
// Error holds an error returned from the service's
// command. It is valid only for ServiceEventError
// events.
Error error
}
// ServiceEventKind represents the kind of a ServiceEvent.
type ServiceEventKind int
// NOTE if you change any of the constants below, be
// sure to run go generate to keep the ServiceEventKind.String
// method up to date.
//go:generate stringer -type=ServiceEventKind
const (
_ ServiceEventKind = iota
// ServiceEventInstall happens when a service is installed.
ServiceEventInstall
// ServiceEventStart happens when a service is started.
ServiceEventStart
// ServiceEventError happens when a service's command
// terminates with an error.
ServiceEventError
// ServiceEventStop happens when a service is stopped.
ServiceEventStop
// ServiceEventRemove happens when a service is removed.
// The service will always be stopped first.
ServiceEventRemove
)
// installedService returns the installed service corresponding
// to svc, or nil if it is not installed.
// Must be called with svc.services.mu held.
func (svc *osService) installedService() *installedOSService {
return svc.services.installed[svc.params.Name]
}
// Install implements service.OSService.Install.
func (svc *osService) Install() error {
svc.install()
svc.Start()
return nil
}
func (svc *osService) install() {
svc.services.mu.Lock()
defer svc.services.mu.Unlock()
if isvc := svc.services.installed[svc.params.Name]; isvc != nil {
return
}
isvc := &installedOSService{
services: svc.services,
params: svc.params,
}
isvc.logf("hooktest: install service %s; exe: %s; args: %q", svc.params.Name, svc.params.Exe, svc.params.Args)
isvc.services.installed[svc.params.Name] = isvc
isvc.notify(ServiceEventInstall, nil)
}
// StopAndRemove implements service.OSService.StopAndRemove.
func (svc *osService) StopAndRemove() error {
svc.Stop()
svc.services.mu.Lock()
defer svc.services.mu.Unlock()
if isvc := svc.installedService(); isvc != nil {
delete(svc.services.installed, svc.params.Name)
isvc.notify(ServiceEventRemove, nil)
}
return nil
}
// Running implements service.OSService.Running.
func (svc *osService) Running() bool {
svc.services.mu.Lock()
defer svc.services.mu.Unlock()
isvc := svc.installedService()
return isvc != nil && isvc.cmd != nil
}
// Stop implements service.OSService.Stop.
func (svc *osService) Stop() error {
svc.services.mu.Lock()
defer svc.services.mu.Unlock()
isvc := svc.installedService()
if isvc == nil || isvc.cmd == nil {
return nil
}
isvc.cmd.Kill()
err := isvc.cmd.Wait()
if err != nil {
isvc.notify(ServiceEventError, err)
}
isvc.notify(ServiceEventStop, nil)
isvc.cmd = nil
return nil
}
// Start implements service.OSService.Start.
func (svc *osService) Start() error {
svc.services.mu.Lock()
defer svc.services.mu.Unlock()
isvc := svc.installedService()
if isvc == nil {
return errgo.Newf("service not installed")
}
if isvc.cmd != nil {
return nil
}
cmd, err := isvc.services.runner.RunCommand(svc.params.Args[0], svc.params.Args[1:])
if err != nil {
isvc.notify(ServiceEventError, errgo.Notef(err, "command start"))
return nil
}
isvc.notify(ServiceEventStart, nil)
if cmd == nil {
isvc.notify(ServiceEventStop, nil)
return nil
}
isvc.cmd = cmd
go func() {
err := cmd.Wait()
if err != nil {
isvc.notify(ServiceEventError, errgo.Notef(err, "command wait"))
}
isvc.services.mu.Lock()
defer isvc.services.mu.Unlock()
if isvc.cmd != cmd {
// The service has been stopped independently
// already. We need do nothing more.
return
}
isvc.notify(ServiceEventStop, nil)
isvc.cmd = nil
}()
return nil
}