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
=== modified file 'docs/meta.md'
--- docs/meta.md 2015-05-29 13:43:15 +0000
+++ docs/meta.md 2015-09-08 06:48:07 +0000
@@ -70,6 +70,10 @@
70 * `bus-name`: (optional) message bus connection name for the service.70 * `bus-name`: (optional) message bus connection name for the service.
71 May only be specified for snaps of 'type: framework' (see above). See71 May only be specified for snaps of 'type: framework' (see above). See
72 frameworks.md for details.72 frameworks.md for details.
73 * `socket`: (optional) Set to "true" is the service is socket activated
74 * `listen-stream`: (optional) The full path of the stream socket
75 * `socket-user`: (optional) The user that owns the stream socket
76 * `socket-group`: (optional) The group that own the stream socket
7377
74* `binaries`: the binaries (executables) that the snap provides78* `binaries`: the binaries (executables) that the snap provides
75 * `name`: (required) the name of the binary, the user will be able to79 * `name`: (required) the name of the binary, the user will be able to
7680
=== modified file 'snappy/click.go'
--- snappy/click.go 2015-09-01 17:46:30 +0000
+++ snappy/click.go 2015-09-08 06:48:07 +0000
@@ -510,22 +510,46 @@
510 return "", err510 return "", err
511 }511 }
512512
513 socketFileName := ""
514 if service.Socket {
515 socketFileName = filepath.Base(generateSocketFileName(m, service))
516 }
517
513 return systemd.New(globalRootDir, nil).GenServiceFile(518 return systemd.New(globalRootDir, nil).GenServiceFile(
514 &systemd.ServiceDescription{519 &systemd.ServiceDescription{
515 AppName: m.Name,520 AppName: m.Name,
516 ServiceName: service.Name,521 ServiceName: service.Name,
517 Version: m.Version,522 Version: m.Version,
518 Description: service.Description,523 Description: service.Description,
519 AppPath: baseDir,524 AppPath: baseDir,
520 Start: service.Start,525 Start: service.Start,
521 Stop: service.Stop,526 Stop: service.Stop,
522 PostStop: service.PostStop,527 PostStop: service.PostStop,
523 StopTimeout: time.Duration(service.StopTimeout),528 StopTimeout: time.Duration(service.StopTimeout),
524 AaProfile: aaProfile,529 AaProfile: aaProfile,
525 IsFramework: m.Type == SnapTypeFramework,530 IsFramework: m.Type == SnapTypeFramework,
526 IsNetworked: service.Ports != nil && len(service.Ports.External) > 0,531 IsNetworked: service.Ports != nil && len(service.Ports.External) > 0,
527 BusName: service.BusName,532 BusName: service.BusName,
528 UdevAppName: udevPartName,533 UdevAppName: udevPartName,
534 Socket: service.Socket,
535 SocketFileName: socketFileName,
536 }), nil
537}
538
539func generateSnapSocketFile(service Service, baseDir string, aaProfile string, m *packageYaml) (string, error) {
540 if err := verifyServiceYaml(service); err != nil {
541 return "", err
542 }
543
544 serviceFileName := filepath.Base(generateServiceFileName(m, service))
545
546 return systemd.New(globalRootDir, nil).GenSocketFile(
547 &systemd.ServiceDescription{
548 ServiceFileName: serviceFileName,
549 ListenStream: service.ListenStream,
550 SocketMode: service.SocketMode,
551 SocketUser: service.SocketUser,
552 SocketGroup: service.SocketGroup,
529 }), nil553 }), nil
530}554}
531555
@@ -537,6 +561,10 @@
537 return filepath.Join(snapBusPolicyDir, fmt.Sprintf("%s_%s_%s.conf", m.Name, service.Name, m.Version))561 return filepath.Join(snapBusPolicyDir, fmt.Sprintf("%s_%s_%s.conf", m.Name, service.Name, m.Version))
538}562}
539563
564func generateSocketFileName(m *packageYaml, service Service) string {
565 return filepath.Join(snapServicesDir, fmt.Sprintf("%s_%s_%s.socket", m.Name, service.Name, m.Version))
566}
567
540// takes a directory and removes the global root, this is needed568// takes a directory and removes the global root, this is needed
541// when the SetRoot option is used and we need to generate569// when the SetRoot option is used and we need to generate
542// content for the "Services" and "Binaries" section570// content for the "Services" and "Binaries" section
@@ -575,6 +603,7 @@
575 // is in the service file when the SetRoot() option603 // is in the service file when the SetRoot() option
576 // is used604 // is used
577 realBaseDir := stripGlobalRootDir(baseDir)605 realBaseDir := stripGlobalRootDir(baseDir)
606 // Generate service file
578 content, err := generateSnapServicesFile(service, realBaseDir, aaProfile, m)607 content, err := generateSnapServicesFile(service, realBaseDir, aaProfile, m)
579 if err != nil {608 if err != nil {
580 return err609 return err
@@ -584,7 +613,18 @@
584 if err := ioutil.WriteFile(serviceFilename, []byte(content), 0644); err != nil {613 if err := ioutil.WriteFile(serviceFilename, []byte(content), 0644); err != nil {
585 return err614 return err
586 }615 }
587616 // Generate systemd socket file if needed
617 if service.Socket {
618 content, err := generateSnapSocketFile(service, realBaseDir, aaProfile, m)
619 if err != nil {
620 return err
621 }
622 socketFilename := generateSocketFileName(m, service)
623 os.MkdirAll(filepath.Dir(socketFilename), 0755)
624 if err := ioutil.WriteFile(socketFilename, []byte(content), 0644); err != nil {
625 return err
626 }
627 }
588 // If necessary, generate the DBus policy file so the framework628 // If necessary, generate the DBus policy file so the framework
589 // service is allowed to start629 // service is allowed to start
590 if m.Type == SnapTypeFramework && service.BusName != "" {630 if m.Type == SnapTypeFramework && service.BusName != "" {
@@ -621,6 +661,20 @@
621 return err661 return err
622 }662 }
623 }663 }
664
665 if service.Socket {
666 socketName := filepath.Base(generateSocketFileName(m, service))
667 // we always enable the socket even in inhibit hooks
668 if err := sysd.Enable(socketName); err != nil {
669 return err
670 }
671
672 if !inhibitHooks {
673 if err := sysd.Start(socketName); err != nil {
674 return err
675 }
676 }
677 }
624 }678 }
625679
626 return nil680 return nil
@@ -652,6 +706,10 @@
652 log.Printf("Warning: failed to remove service file for %s: %v", serviceName, err)706 log.Printf("Warning: failed to remove service file for %s: %v", serviceName, err)
653 }707 }
654708
709 if err := os.Remove(generateSocketFileName(m, service)); err != nil && !os.IsNotExist(err) {
710 log.Printf("Failed to remove socket file for %q: %v", serviceName, err)
711 }
712
655 // Also remove DBus system policy file713 // Also remove DBus system policy file
656 if err := os.Remove(generateBusPolicyFileName(m, service)); err != nil && !os.IsNotExist(err) {714 if err := os.Remove(generateBusPolicyFileName(m, service)); err != nil && !os.IsNotExist(err) {
657 log.Printf("Warning: failed to remove bus policy file for service %s: %v", serviceName, err)715 log.Printf("Warning: failed to remove bus policy file for service %s: %v", serviceName, err)
658716
=== modified file 'snappy/click_test.go'
--- snappy/click_test.go 2015-09-01 17:46:30 +0000
+++ snappy/click_test.go 2015-09-08 06:48:07 +0000
@@ -1200,9 +1200,10 @@
1200[Install]1200[Install]
1201WantedBy=multi-user.target1201WantedBy=multi-user.target
1202`1202`
1203 expectedServiceAppWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "After=ubuntu-snappy.frameworks.target\nRequires=ubuntu-snappy.frameworks.target", ".canonical", "canonical", "\n", helpers.UbuntuArchitecture())1203 expectedServiceAppWrapper = fmt.Sprintf(expectedServiceWrapperFmt, "After=ubuntu-snappy.frameworks.target\nRequires=ubuntu-snappy.frameworks.target", ".canonical", "canonical", "\n", helpers.UbuntuArchitecture())
1204 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())1204 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())
1205 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())1205 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())
1206 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())
1206)1207)
12071208
1208func (s *SnapTestSuite) TestSnappyGenerateSnapServiceAppWrapper(c *C) {1209func (s *SnapTestSuite) TestSnappyGenerateSnapServiceAppWrapper(c *C) {
@@ -1521,3 +1522,57 @@
1521 exitCode: 1,1522 exitCode: 1,
1522 })1523 })
1523}1524}
1525
1526func (s *SnapTestSuite) TestSnappyGenerateSnapSocket(c *C) {
1527 service := Service{Name: "xkcd-webserver",
1528 Start: "bin/foo start",
1529 Description: "meep",
1530 Socket: true,
1531 ListenStream: "/var/run/docker.sock",
1532 SocketMode: "0660",
1533 SocketUser: "root",
1534 SocketGroup: "adm",
1535 }
1536 pkgPath := "/apps/xkcd-webserver.canonical/0.3.4/"
1537 aaProfile := "xkcd-webserver.canonical_xkcd-webserver_0.3.4"
1538 m := packageYaml{
1539 Name: "xkcd-webserver",
1540 Version: "0.3.4"}
1541
1542 content, err := generateSnapSocketFile(service, pkgPath, aaProfile, &m)
1543 c.Assert(err, IsNil)
1544 c.Assert(content, Equals, `[Unit]
1545Description= Socket Unit File
1546PartOf=xkcd-webserver_xkcd-webserver_0.3.4.service
1547X-Snappy=yes
1548
1549[Socket]
1550ListenStream=/var/run/docker.sock
1551SocketMode=0660
1552SocketUSer=root
1553SocketGroup=adm
1554
1555[Install]
1556WantedBy=sockets.target
1557`)
1558}
1559
1560func (s *SnapTestSuite) TestSnappyGenerateSnapServiceWithSockte(c *C) {
1561 service := Service{
1562 Name: "xkcd-webserver",
1563 Start: "bin/foo start",
1564 Stop: "bin/foo stop",
1565 PostStop: "bin/foo post-stop",
1566 StopTimeout: DefaultTimeout,
1567 Description: "A fun webserver",
1568 Socket: true,
1569 }
1570 pkgPath := "/apps/xkcd-webserver.canonical/0.3.4/"
1571 aaProfile := "xkcd-webserver.canonical_xkcd-webserver_0.3.4"
1572 m := packageYaml{Name: "xkcd-webserver",
1573 Version: "0.3.4"}
1574
1575 generatedWrapper, err := generateSnapServicesFile(service, pkgPath, aaProfile, &m)
1576 c.Assert(err, IsNil)
1577 c.Assert(generatedWrapper, Equals, expectedSocketUsingWrapper)
1578}
15241579
=== modified file 'snappy/snapp.go'
--- snappy/snapp.go 2015-07-07 02:35:22 +0000
+++ snappy/snapp.go 2015-09-08 06:48:07 +0000
@@ -146,6 +146,13 @@
146 StopTimeout Timeout `yaml:"stop-timeout,omitempty" json:"stop-timeout,omitempty"`146 StopTimeout Timeout `yaml:"stop-timeout,omitempty" json:"stop-timeout,omitempty"`
147 BusName string `yaml:"bus-name,omitempty" json:"bus-name,omitempty"`147 BusName string `yaml:"bus-name,omitempty" json:"bus-name,omitempty"`
148148
149 // set to yes if we need to create a systemd socket for this service
150 Socket bool `yaml:"socket,omitempty" json:"socket,omitempty"`
151 ListenStream string `yaml:"listen-stream,omitempty" json:"listen-stream,omitempty"`
152 SocketMode string `yaml:"socket-mode,omitempty" json:"socket-mode,omitempty"`
153 SocketUser string `yaml:"socket-user,omitempty" json:"socket-user,omitempty"`
154 SocketGroup string `yaml:"socket-group,omitempty" json:"socket-group,omitempty"`
155
149 // must be a pointer so that it can be "nil" and omitempty works156 // must be a pointer so that it can be "nil" and omitempty works
150 Ports *Ports `yaml:"ports,omitempty" json:"ports,omitempty"`157 Ports *Ports `yaml:"ports,omitempty" json:"ports,omitempty"`
151158
152159
=== modified file 'systemd/systemd.go'
--- systemd/systemd.go 2015-09-01 17:46:30 +0000
+++ systemd/systemd.go 2015-09-08 06:48:07 +0000
@@ -66,30 +66,41 @@
66 Kill(service, signal string) error66 Kill(service, signal string) error
67 Restart(service string, timeout time.Duration) error67 Restart(service string, timeout time.Duration) error
68 GenServiceFile(desc *ServiceDescription) string68 GenServiceFile(desc *ServiceDescription) string
69 GenSocketFile(desc *ServiceDescription) string
69}70}
7071
71// ServiceDescription describes a snappy systemd service72// ServiceDescription describes a snappy systemd service
72type ServiceDescription struct {73type ServiceDescription struct {
73 AppName string74 AppName string
74 ServiceName string75 ServiceName string
75 Version string76 Version string
76 Description string77 Description string
77 AppPath string78 AppPath string
78 Start string79 Start string
79 Stop string80 Stop string
80 PostStop string81 PostStop string
81 StopTimeout time.Duration82 StopTimeout time.Duration
82 AaProfile string83 AaProfile string
83 IsFramework bool84 IsFramework bool
84 IsNetworked bool85 IsNetworked bool
85 BusName string86 BusName string
86 UdevAppName string87 UdevAppName string
88 Socket bool
89 SocketFileName string
90 ListenStream string
91 SocketMode string
92 SocketUser string
93 SocketGroup string
94 ServiceFileName string
87}95}
8896
89const (97const (
90 // the default target for systemd units that we generate98 // the default target for systemd units that we generate
91 servicesSystemdTarget = "multi-user.target"99 servicesSystemdTarget = "multi-user.target"
92100
101 // the default target for systemd units that we generate
102 socketsSystemdTarget = "sockets.target"
103
93 // the location to put system services104 // the location to put system services
94 snapServicesDir = "/etc/systemd/system"105 snapServicesDir = "/etc/systemd/system"
95)106)
@@ -173,9 +184,9 @@
173 serviceTemplate := `[Unit]184 serviceTemplate := `[Unit]
174Description={{.Description}}185Description={{.Description}}
175{{if .IsFramework}}Before=ubuntu-snappy.frameworks.target186{{if .IsFramework}}Before=ubuntu-snappy.frameworks.target
176After=ubuntu-snappy.frameworks-pre.target187After=ubuntu-snappy.frameworks-pre.target{{ if .Socket }} {{.SocketFileName}}{{end}}
177Requires=ubuntu-snappy.frameworks-pre.target{{else}}After=ubuntu-snappy.frameworks.target188Requires=ubuntu-snappy.frameworks-pre.target{{ if .Socket }} {{.SocketFileName}}{{end}}{{else}}After=ubuntu-snappy.frameworks.target{{ if .Socket }} {{.SocketFileName}}{{end}}
178Requires=ubuntu-snappy.frameworks.target{{end}}{{if .IsNetworked}}189Requires=ubuntu-snappy.frameworks.target{{ if .Socket }} {{.SocketFileName}}{{end}}{{end}}{{if .IsNetworked}}
179After=snappy-wait4network.service190After=snappy-wait4network.service
180Requires=snappy-wait4network.service{{end}}191Requires=snappy-wait4network.service{{end}}
181X-Snappy=yes192X-Snappy=yes
@@ -213,6 +224,7 @@
213 AppArch string224 AppArch string
214 Home string225 Home string
215 EnvVars string226 EnvVars string
227 SocketFileName string
216 }{228 }{
217 *desc,229 *desc,
218 filepath.Join(desc.AppPath, desc.Start),230 filepath.Join(desc.AppPath, desc.Start),
@@ -224,6 +236,7 @@
224 helpers.UbuntuArchitecture(),236 helpers.UbuntuArchitecture(),
225 "%h",237 "%h",
226 "",238 "",
239 desc.SocketFileName,
227 }240 }
228 allVars := helpers.GetBasicSnapEnvVars(wrapperData)241 allVars := helpers.GetBasicSnapEnvVars(wrapperData)
229 allVars = append(allVars, helpers.GetUserSnapEnvVars(wrapperData)...)242 allVars = append(allVars, helpers.GetUserSnapEnvVars(wrapperData)...)
@@ -239,6 +252,52 @@
239 return templateOut.String()252 return templateOut.String()
240}253}
241254
255func (s *systemd) GenSocketFile(desc *ServiceDescription) string {
256 serviceTemplate := `[Unit]
257Description={{.Description}} Socket Unit File
258PartOf={{.ServiceFileName}}
259X-Snappy=yes
260
261[Socket]
262ListenStream={{.ListenStream}}
263{{if .SocketMode}}SocketMode={{.SocketMode}}{{end}}
264{{if .SocketUser}}SocketUSer={{.SocketUser}}{{end}}
265{{if .SocketGroup}}SocketGroup={{.SocketGroup}}{{end}}
266
267[Install]
268WantedBy={{.SocketSystemdTarget}}
269`
270 var templateOut bytes.Buffer
271 t := template.Must(template.New("wrapper").Parse(serviceTemplate))
272
273 wrapperData := struct {
274 // the service description
275 ServiceDescription
276 // and some composed values
277 ServiceFileName,
278 ListenStream string
279 SocketMode string
280 SocketUser string
281 SocketGroup string
282 SocketSystemdTarget string
283 }{
284 *desc,
285 desc.ServiceFileName,
286 desc.ListenStream,
287 desc.SocketMode,
288 desc.SocketUser,
289 desc.SocketGroup,
290 socketsSystemdTarget,
291 }
292
293 if err := t.Execute(&templateOut, wrapperData); err != nil {
294 // this can never happen, except we forget a variable
295 logger.LogAndPanic(err)
296 }
297
298 return templateOut.String()
299}
300
242// Kill all processes of the unit with the given signal301// Kill all processes of the unit with the given signal
243func (s *systemd) Kill(serviceName, signal string) error {302func (s *systemd) Kill(serviceName, signal string) error {
244 _, err := SystemctlCmd("kill", serviceName, "-s", signal)303 _, err := SystemctlCmd("kill", serviceName, "-s", signal)

Subscribers

People subscribed via source and target branches