Merge lp:~mvo/snappy/15.04-systemd-sockets into lp:~snappy-dev/snappy/15.04-deprecated
- 15.04-systemd-sockets
- Merge into 15.04-deprecated
Proposed by
Michael Vogt
Status: | Merged |
---|---|
Approved by: | John Lenton |
Approved revision: | 469 |
Merged at revision: | 470 |
Proposed branch: | lp:~mvo/snappy/15.04-systemd-sockets |
Merge into: | lp:~snappy-dev/snappy/15.04-deprecated |
Diff against target: |
386 lines (+218/-35) 5 files modified
docs/meta.md (+4/-0) snappy/click.go (+73/-15) snappy/click_test.go (+58/-3) snappy/snapp.go (+7/-0) systemd/systemd.go (+76/-17) |
To merge this branch: | bzr merge lp:~mvo/snappy/15.04-systemd-sockets |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Lenton (community) | Approve | ||
Review via email: mp+270360@code.launchpad.net |
Commit message
Add support for systemd socket files.
Description of the change
Backport of the branch that adds systemd socket file generation which is important for docker.
To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'docs/meta.md' |
2 | --- docs/meta.md 2015-05-29 13:43:15 +0000 |
3 | +++ docs/meta.md 2015-09-08 06:48:07 +0000 |
4 | @@ -70,6 +70,10 @@ |
5 | * `bus-name`: (optional) message bus connection name for the service. |
6 | May only be specified for snaps of 'type: framework' (see above). See |
7 | frameworks.md for details. |
8 | + * `socket`: (optional) Set to "true" is the service is socket activated |
9 | + * `listen-stream`: (optional) The full path of the stream socket |
10 | + * `socket-user`: (optional) The user that owns the stream socket |
11 | + * `socket-group`: (optional) The group that own the stream socket |
12 | |
13 | * `binaries`: the binaries (executables) that the snap provides |
14 | * `name`: (required) the name of the binary, the user will be able to |
15 | |
16 | === modified file 'snappy/click.go' |
17 | --- snappy/click.go 2015-09-01 17:46:30 +0000 |
18 | +++ snappy/click.go 2015-09-08 06:48:07 +0000 |
19 | @@ -510,22 +510,46 @@ |
20 | return "", err |
21 | } |
22 | |
23 | + socketFileName := "" |
24 | + if service.Socket { |
25 | + socketFileName = filepath.Base(generateSocketFileName(m, service)) |
26 | + } |
27 | + |
28 | return systemd.New(globalRootDir, nil).GenServiceFile( |
29 | &systemd.ServiceDescription{ |
30 | - AppName: m.Name, |
31 | - ServiceName: service.Name, |
32 | - Version: m.Version, |
33 | - Description: service.Description, |
34 | - AppPath: baseDir, |
35 | - Start: service.Start, |
36 | - Stop: service.Stop, |
37 | - PostStop: service.PostStop, |
38 | - StopTimeout: time.Duration(service.StopTimeout), |
39 | - AaProfile: aaProfile, |
40 | - IsFramework: m.Type == SnapTypeFramework, |
41 | - IsNetworked: service.Ports != nil && len(service.Ports.External) > 0, |
42 | - BusName: service.BusName, |
43 | - UdevAppName: udevPartName, |
44 | + AppName: m.Name, |
45 | + ServiceName: service.Name, |
46 | + Version: m.Version, |
47 | + Description: service.Description, |
48 | + AppPath: baseDir, |
49 | + Start: service.Start, |
50 | + Stop: service.Stop, |
51 | + PostStop: service.PostStop, |
52 | + StopTimeout: time.Duration(service.StopTimeout), |
53 | + AaProfile: aaProfile, |
54 | + IsFramework: m.Type == SnapTypeFramework, |
55 | + IsNetworked: service.Ports != nil && len(service.Ports.External) > 0, |
56 | + BusName: service.BusName, |
57 | + UdevAppName: udevPartName, |
58 | + Socket: service.Socket, |
59 | + SocketFileName: socketFileName, |
60 | + }), nil |
61 | +} |
62 | + |
63 | +func generateSnapSocketFile(service Service, baseDir string, aaProfile string, m *packageYaml) (string, error) { |
64 | + if err := verifyServiceYaml(service); err != nil { |
65 | + return "", err |
66 | + } |
67 | + |
68 | + serviceFileName := filepath.Base(generateServiceFileName(m, service)) |
69 | + |
70 | + return systemd.New(globalRootDir, nil).GenSocketFile( |
71 | + &systemd.ServiceDescription{ |
72 | + ServiceFileName: serviceFileName, |
73 | + ListenStream: service.ListenStream, |
74 | + SocketMode: service.SocketMode, |
75 | + SocketUser: service.SocketUser, |
76 | + SocketGroup: service.SocketGroup, |
77 | }), nil |
78 | } |
79 | |
80 | @@ -537,6 +561,10 @@ |
81 | return filepath.Join(snapBusPolicyDir, fmt.Sprintf("%s_%s_%s.conf", m.Name, service.Name, m.Version)) |
82 | } |
83 | |
84 | +func generateSocketFileName(m *packageYaml, service Service) string { |
85 | + return filepath.Join(snapServicesDir, fmt.Sprintf("%s_%s_%s.socket", m.Name, service.Name, m.Version)) |
86 | +} |
87 | + |
88 | // takes a directory and removes the global root, this is needed |
89 | // when the SetRoot option is used and we need to generate |
90 | // content for the "Services" and "Binaries" section |
91 | @@ -575,6 +603,7 @@ |
92 | // is in the service file when the SetRoot() option |
93 | // is used |
94 | realBaseDir := stripGlobalRootDir(baseDir) |
95 | + // Generate service file |
96 | content, err := generateSnapServicesFile(service, realBaseDir, aaProfile, m) |
97 | if err != nil { |
98 | return err |
99 | @@ -584,7 +613,18 @@ |
100 | if err := ioutil.WriteFile(serviceFilename, []byte(content), 0644); err != nil { |
101 | return err |
102 | } |
103 | - |
104 | + // Generate systemd socket file if needed |
105 | + if service.Socket { |
106 | + content, err := generateSnapSocketFile(service, realBaseDir, aaProfile, m) |
107 | + if err != nil { |
108 | + return err |
109 | + } |
110 | + socketFilename := generateSocketFileName(m, service) |
111 | + os.MkdirAll(filepath.Dir(socketFilename), 0755) |
112 | + if err := ioutil.WriteFile(socketFilename, []byte(content), 0644); err != nil { |
113 | + return err |
114 | + } |
115 | + } |
116 | // If necessary, generate the DBus policy file so the framework |
117 | // service is allowed to start |
118 | if m.Type == SnapTypeFramework && service.BusName != "" { |
119 | @@ -621,6 +661,20 @@ |
120 | return err |
121 | } |
122 | } |
123 | + |
124 | + if service.Socket { |
125 | + socketName := filepath.Base(generateSocketFileName(m, service)) |
126 | + // we always enable the socket even in inhibit hooks |
127 | + if err := sysd.Enable(socketName); err != nil { |
128 | + return err |
129 | + } |
130 | + |
131 | + if !inhibitHooks { |
132 | + if err := sysd.Start(socketName); err != nil { |
133 | + return err |
134 | + } |
135 | + } |
136 | + } |
137 | } |
138 | |
139 | return nil |
140 | @@ -652,6 +706,10 @@ |
141 | log.Printf("Warning: failed to remove service file for %s: %v", serviceName, err) |
142 | } |
143 | |
144 | + if err := os.Remove(generateSocketFileName(m, service)); err != nil && !os.IsNotExist(err) { |
145 | + log.Printf("Failed to remove socket file for %q: %v", serviceName, err) |
146 | + } |
147 | + |
148 | // Also remove DBus system policy file |
149 | if err := os.Remove(generateBusPolicyFileName(m, service)); err != nil && !os.IsNotExist(err) { |
150 | log.Printf("Warning: failed to remove bus policy file for service %s: %v", serviceName, err) |
151 | |
152 | === modified file 'snappy/click_test.go' |
153 | --- snappy/click_test.go 2015-09-01 17:46:30 +0000 |
154 | +++ snappy/click_test.go 2015-09-08 06:48:07 +0000 |
155 | @@ -1200,9 +1200,10 @@ |
156 | [Install] |
157 | WantedBy=multi-user.target |
158 | ` |
159 | - expectedServiceAppWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "After=ubuntu-snappy.frameworks.target\nRequires=ubuntu-snappy.frameworks.target", ".canonical", "canonical", "\n", helpers.UbuntuArchitecture()) |
160 | - expectedNetAppWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "After=ubuntu-snappy.frameworks.target\nRequires=ubuntu-snappy.frameworks.target\nAfter=snappy-wait4network.service\nRequires=snappy-wait4network.service", ".canonical", "canonical", "\n", helpers.UbuntuArchitecture()) |
161 | - expectedServiceFmkWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "Before=ubuntu-snappy.frameworks.target\nAfter=ubuntu-snappy.frameworks-pre.target\nRequires=ubuntu-snappy.frameworks-pre.target", "", "", "BusName=foo.bar.baz\nType=dbus", helpers.UbuntuArchitecture()) |
162 | + expectedServiceAppWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "After=ubuntu-snappy.frameworks.target\nRequires=ubuntu-snappy.frameworks.target", ".canonical", "canonical", "\n", helpers.UbuntuArchitecture()) |
163 | + expectedNetAppWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "After=ubuntu-snappy.frameworks.target\nRequires=ubuntu-snappy.frameworks.target\nAfter=snappy-wait4network.service\nRequires=snappy-wait4network.service", ".canonical", "canonical", "\n", helpers.UbuntuArchitecture()) |
164 | + expectedServiceFmkWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "Before=ubuntu-snappy.frameworks.target\nAfter=ubuntu-snappy.frameworks-pre.target\nRequires=ubuntu-snappy.frameworks-pre.target", "", "", "BusName=foo.bar.baz\nType=dbus", helpers.UbuntuArchitecture()) |
165 | + expectedSocketUsingWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "After=ubuntu-snappy.frameworks.target xkcd-webserver_xkcd-webserver_0.3.4.socket\nRequires=ubuntu-snappy.frameworks.target xkcd-webserver_xkcd-webserver_0.3.4.socket", ".canonical", "canonical", "\n", helpers.UbuntuArchitecture()) |
166 | ) |
167 | |
168 | func (s *SnapTestSuite) TestSnappyGenerateSnapServiceAppWrapper(c *C) { |
169 | @@ -1521,3 +1522,57 @@ |
170 | exitCode: 1, |
171 | }) |
172 | } |
173 | + |
174 | +func (s *SnapTestSuite) TestSnappyGenerateSnapSocket(c *C) { |
175 | + service := Service{Name: "xkcd-webserver", |
176 | + Start: "bin/foo start", |
177 | + Description: "meep", |
178 | + Socket: true, |
179 | + ListenStream: "/var/run/docker.sock", |
180 | + SocketMode: "0660", |
181 | + SocketUser: "root", |
182 | + SocketGroup: "adm", |
183 | + } |
184 | + pkgPath := "/apps/xkcd-webserver.canonical/0.3.4/" |
185 | + aaProfile := "xkcd-webserver.canonical_xkcd-webserver_0.3.4" |
186 | + m := packageYaml{ |
187 | + Name: "xkcd-webserver", |
188 | + Version: "0.3.4"} |
189 | + |
190 | + content, err := generateSnapSocketFile(service, pkgPath, aaProfile, &m) |
191 | + c.Assert(err, IsNil) |
192 | + c.Assert(content, Equals, `[Unit] |
193 | +Description= Socket Unit File |
194 | +PartOf=xkcd-webserver_xkcd-webserver_0.3.4.service |
195 | +X-Snappy=yes |
196 | + |
197 | +[Socket] |
198 | +ListenStream=/var/run/docker.sock |
199 | +SocketMode=0660 |
200 | +SocketUSer=root |
201 | +SocketGroup=adm |
202 | + |
203 | +[Install] |
204 | +WantedBy=sockets.target |
205 | +`) |
206 | +} |
207 | + |
208 | +func (s *SnapTestSuite) TestSnappyGenerateSnapServiceWithSockte(c *C) { |
209 | + service := Service{ |
210 | + Name: "xkcd-webserver", |
211 | + Start: "bin/foo start", |
212 | + Stop: "bin/foo stop", |
213 | + PostStop: "bin/foo post-stop", |
214 | + StopTimeout: DefaultTimeout, |
215 | + Description: "A fun webserver", |
216 | + Socket: true, |
217 | + } |
218 | + pkgPath := "/apps/xkcd-webserver.canonical/0.3.4/" |
219 | + aaProfile := "xkcd-webserver.canonical_xkcd-webserver_0.3.4" |
220 | + m := packageYaml{Name: "xkcd-webserver", |
221 | + Version: "0.3.4"} |
222 | + |
223 | + generatedWrapper, err := generateSnapServicesFile(service, pkgPath, aaProfile, &m) |
224 | + c.Assert(err, IsNil) |
225 | + c.Assert(generatedWrapper, Equals, expectedSocketUsingWrapper) |
226 | +} |
227 | |
228 | === modified file 'snappy/snapp.go' |
229 | --- snappy/snapp.go 2015-07-07 02:35:22 +0000 |
230 | +++ snappy/snapp.go 2015-09-08 06:48:07 +0000 |
231 | @@ -146,6 +146,13 @@ |
232 | StopTimeout Timeout `yaml:"stop-timeout,omitempty" json:"stop-timeout,omitempty"` |
233 | BusName string `yaml:"bus-name,omitempty" json:"bus-name,omitempty"` |
234 | |
235 | + // set to yes if we need to create a systemd socket for this service |
236 | + Socket bool `yaml:"socket,omitempty" json:"socket,omitempty"` |
237 | + ListenStream string `yaml:"listen-stream,omitempty" json:"listen-stream,omitempty"` |
238 | + SocketMode string `yaml:"socket-mode,omitempty" json:"socket-mode,omitempty"` |
239 | + SocketUser string `yaml:"socket-user,omitempty" json:"socket-user,omitempty"` |
240 | + SocketGroup string `yaml:"socket-group,omitempty" json:"socket-group,omitempty"` |
241 | + |
242 | // must be a pointer so that it can be "nil" and omitempty works |
243 | Ports *Ports `yaml:"ports,omitempty" json:"ports,omitempty"` |
244 | |
245 | |
246 | === modified file 'systemd/systemd.go' |
247 | --- systemd/systemd.go 2015-09-01 17:46:30 +0000 |
248 | +++ systemd/systemd.go 2015-09-08 06:48:07 +0000 |
249 | @@ -66,30 +66,41 @@ |
250 | Kill(service, signal string) error |
251 | Restart(service string, timeout time.Duration) error |
252 | GenServiceFile(desc *ServiceDescription) string |
253 | + GenSocketFile(desc *ServiceDescription) string |
254 | } |
255 | |
256 | // ServiceDescription describes a snappy systemd service |
257 | type ServiceDescription struct { |
258 | - AppName string |
259 | - ServiceName string |
260 | - Version string |
261 | - Description string |
262 | - AppPath string |
263 | - Start string |
264 | - Stop string |
265 | - PostStop string |
266 | - StopTimeout time.Duration |
267 | - AaProfile string |
268 | - IsFramework bool |
269 | - IsNetworked bool |
270 | - BusName string |
271 | - UdevAppName string |
272 | + AppName string |
273 | + ServiceName string |
274 | + Version string |
275 | + Description string |
276 | + AppPath string |
277 | + Start string |
278 | + Stop string |
279 | + PostStop string |
280 | + StopTimeout time.Duration |
281 | + AaProfile string |
282 | + IsFramework bool |
283 | + IsNetworked bool |
284 | + BusName string |
285 | + UdevAppName string |
286 | + Socket bool |
287 | + SocketFileName string |
288 | + ListenStream string |
289 | + SocketMode string |
290 | + SocketUser string |
291 | + SocketGroup string |
292 | + ServiceFileName string |
293 | } |
294 | |
295 | const ( |
296 | // the default target for systemd units that we generate |
297 | servicesSystemdTarget = "multi-user.target" |
298 | |
299 | + // the default target for systemd units that we generate |
300 | + socketsSystemdTarget = "sockets.target" |
301 | + |
302 | // the location to put system services |
303 | snapServicesDir = "/etc/systemd/system" |
304 | ) |
305 | @@ -173,9 +184,9 @@ |
306 | serviceTemplate := `[Unit] |
307 | Description={{.Description}} |
308 | {{if .IsFramework}}Before=ubuntu-snappy.frameworks.target |
309 | -After=ubuntu-snappy.frameworks-pre.target |
310 | -Requires=ubuntu-snappy.frameworks-pre.target{{else}}After=ubuntu-snappy.frameworks.target |
311 | -Requires=ubuntu-snappy.frameworks.target{{end}}{{if .IsNetworked}} |
312 | +After=ubuntu-snappy.frameworks-pre.target{{ if .Socket }} {{.SocketFileName}}{{end}} |
313 | +Requires=ubuntu-snappy.frameworks-pre.target{{ if .Socket }} {{.SocketFileName}}{{end}}{{else}}After=ubuntu-snappy.frameworks.target{{ if .Socket }} {{.SocketFileName}}{{end}} |
314 | +Requires=ubuntu-snappy.frameworks.target{{ if .Socket }} {{.SocketFileName}}{{end}}{{end}}{{if .IsNetworked}} |
315 | After=snappy-wait4network.service |
316 | Requires=snappy-wait4network.service{{end}} |
317 | X-Snappy=yes |
318 | @@ -213,6 +224,7 @@ |
319 | AppArch string |
320 | Home string |
321 | EnvVars string |
322 | + SocketFileName string |
323 | }{ |
324 | *desc, |
325 | filepath.Join(desc.AppPath, desc.Start), |
326 | @@ -224,6 +236,7 @@ |
327 | helpers.UbuntuArchitecture(), |
328 | "%h", |
329 | "", |
330 | + desc.SocketFileName, |
331 | } |
332 | allVars := helpers.GetBasicSnapEnvVars(wrapperData) |
333 | allVars = append(allVars, helpers.GetUserSnapEnvVars(wrapperData)...) |
334 | @@ -239,6 +252,52 @@ |
335 | return templateOut.String() |
336 | } |
337 | |
338 | +func (s *systemd) GenSocketFile(desc *ServiceDescription) string { |
339 | + serviceTemplate := `[Unit] |
340 | +Description={{.Description}} Socket Unit File |
341 | +PartOf={{.ServiceFileName}} |
342 | +X-Snappy=yes |
343 | + |
344 | +[Socket] |
345 | +ListenStream={{.ListenStream}} |
346 | +{{if .SocketMode}}SocketMode={{.SocketMode}}{{end}} |
347 | +{{if .SocketUser}}SocketUSer={{.SocketUser}}{{end}} |
348 | +{{if .SocketGroup}}SocketGroup={{.SocketGroup}}{{end}} |
349 | + |
350 | +[Install] |
351 | +WantedBy={{.SocketSystemdTarget}} |
352 | +` |
353 | + var templateOut bytes.Buffer |
354 | + t := template.Must(template.New("wrapper").Parse(serviceTemplate)) |
355 | + |
356 | + wrapperData := struct { |
357 | + // the service description |
358 | + ServiceDescription |
359 | + // and some composed values |
360 | + ServiceFileName, |
361 | + ListenStream string |
362 | + SocketMode string |
363 | + SocketUser string |
364 | + SocketGroup string |
365 | + SocketSystemdTarget string |
366 | + }{ |
367 | + *desc, |
368 | + desc.ServiceFileName, |
369 | + desc.ListenStream, |
370 | + desc.SocketMode, |
371 | + desc.SocketUser, |
372 | + desc.SocketGroup, |
373 | + socketsSystemdTarget, |
374 | + } |
375 | + |
376 | + if err := t.Execute(&templateOut, wrapperData); err != nil { |
377 | + // this can never happen, except we forget a variable |
378 | + logger.LogAndPanic(err) |
379 | + } |
380 | + |
381 | + return templateOut.String() |
382 | +} |
383 | + |
384 | // Kill all processes of the unit with the given signal |
385 | func (s *systemd) Kill(serviceName, signal string) error { |
386 | _, err := SystemctlCmd("kill", serviceName, "-s", signal) |