Merge lp:~mvo/snappy/15.04-systemd-sockets into lp:~snappy-dev/snappy/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
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)

Subscribers

People subscribed via source and target branches