Skip to content

Commit

Permalink
wrappers: introduce timer services
Browse files Browse the repository at this point in the history
Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
  • Loading branch information
bboozzoo committed Feb 16, 2018
1 parent 59616e7 commit 54323d0
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 5 deletions.
1 change: 1 addition & 0 deletions wrappers/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
var (
// services
GenerateSnapServiceFile = generateSnapServiceFile
GenerateSnapTimerFile = generateSnapTimerFile

// desktop
SanitizeDesktopFile = sanitizeDesktopFile
Expand Down
91 changes: 86 additions & 5 deletions wrappers/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,16 @@ func stopService(sysd systemd.Systemd, app *snap.AppInfo, inter interacter) erro
serviceName := app.ServiceName()
tout := serviceStopTimeout(app)

socketErrors := []error{}
stopErrors := []error{}
for _, socket := range app.Sockets {
if err := sysd.Stop(filepath.Base(socket.File()), tout); err != nil {
socketErrors = append(socketErrors, err)
stopErrors = append(stopErrors, err)
}
}

if app.Timer != nil {
if err := sysd.Stop(filepath.Base(app.Timer.File()), tout); err != nil {
stopErrors = append(stopErrors, err)
}
}

Expand All @@ -84,8 +90,8 @@ func stopService(sysd systemd.Systemd, app *snap.AppInfo, inter interacter) erro

}

if len(socketErrors) > 0 {
return socketErrors[0]
if len(stopErrors) > 0 {
return stopErrors[0]
}

return nil
Expand Down Expand Up @@ -115,9 +121,15 @@ func StartServices(apps []*snap.AppInfo, inter interacter) (err error) {
inter.Notify(fmt.Sprintf("While trying to disable previously enabled socket service %q: %v", socketService, e))
}
}
if app.Timer != nil {
timerService := filepath.Base(app.Timer.File())
if e := sysd.Disable(timerService); e != nil {
inter.Notify(fmt.Sprintf("While trying to disable previously enabled timer service %q: %v", timerService, e))
}
}
}(app)

if len(app.Sockets) == 0 {
if len(app.Sockets) == 0 && app.Timer == nil {
services = append(services, app.ServiceName())
}

Expand All @@ -133,6 +145,18 @@ func StartServices(apps []*snap.AppInfo, inter interacter) (err error) {
}
}

if app.Timer != nil {
timerService := filepath.Base(app.Timer.File())
// enable the timer
if err := sysd.Enable(timerService); err != nil {
return err
}

if err := sysd.Start(timerService); err != nil {
return err
}
}

}

if len(services) > 0 {
Expand Down Expand Up @@ -200,6 +224,17 @@ func AddSnapServices(s *snap.Info, inter interacter) (err error) {
written = append(written, path)
}

if app.Timer != nil {
content := generateSnapTimerFile(app)
path := app.Timer.File()
os.MkdirAll(filepath.Dir(path), 0755)
if err := osutil.AtomicWriteFile(path, content, 0644, 0); err != nil {
return err
}
written = append(written, path)
continue
}

svcName := app.ServiceName()
if err := sysd.Enable(svcName); err != nil {
return err
Expand Down Expand Up @@ -459,6 +494,52 @@ func renderListenStream(socket *snap.SocketInfo) string {
return strings.Replace(listenStream, "$SNAP_COMMON", snap.CommonDataDir(), -1)
}

func generateSnapTimerFile(app *snap.AppInfo) []byte {
timerTemplate := `[Unit]
# Auto-generated, DO NOT EDIT
Description=Timer {{.TimerName}} for snap application {{.App.Snap.Name}}.{{.App.Name}}
Requires={{.MountUnit}}
After={{.MountUnit}}
X-Snappy=yes
[Timer]
Unit={{.ServiceFileName}}
{{ range .Schedules }}OnCalendar={{ . }}
{{- end }}
[Install]
WantedBy={{.TimersTarget}}
`
var templateOut bytes.Buffer
t := template.Must(template.New("timer-wrapper").Parse(timerTemplate))

schedules, _ := generateOnCalendarSchedules(app.Timer.Timer)

wrapperData := struct {
App *snap.AppInfo
ServiceFileName string
TimersTarget string
TimerName string
MountUnit string
Schedules []string
}{
App: app,
ServiceFileName: filepath.Base(app.ServiceFile()),
TimersTarget: systemd.TimersTarget,
TimerName: app.Name,
MountUnit: filepath.Base(systemd.MountUnitPath(app.Snap.MountDir())),
Schedules: schedules,
}

if err := t.Execute(&templateOut, wrapperData); err != nil {
// this can never happen, except we forget a variable
logger.Panicf("Unable to execute template: %v", err)
}

return templateOut.Bytes()

}

func makeAbbrevWeekdays(start time.Weekday, end time.Weekday) []string {
out := make([]string, 0, 7)
for w := start; w%7 != (end + 1); w++ {
Expand Down
83 changes: 83 additions & 0 deletions wrappers/services_gen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,89 @@ WantedBy=multi-user.target
c.Assert(string(generatedWrapper), Equals, expectedService)
}

func (s *servicesWrapperGenSuite) TestServiceTimerUnit(c *C) {
const expectedServiceFmt = `[Unit]
# Auto-generated, DO NOT EDIT
Description=Timer app for snap application snap.app
Requires=%s-snap-44.mount
After=%s-snap-44.mount
X-Snappy=yes
[Timer]
Unit=snap.snap.app.service
OnCalendar=*-*-* 10:00
[Install]
WantedBy=timers.target
`

expectedService := fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix)
service := &snap.AppInfo{
Snap: &snap.Info{
SuggestedName: "snap",
Version: "0.3.4",
SideInfo: snap.SideInfo{Revision: snap.R(44)},
},
Name: "app",
Command: "bin/foo start",
Daemon: "simple",
StopTimeout: timeout.DefaultTimeout,
Timer: &snap.TimerInfo{
Timer: "10:00-12:00",
},
}
service.Timer.App = service

generatedWrapper := wrappers.GenerateSnapTimerFile(service)

c.Logf("timer: \n%v\n", string(generatedWrapper))
c.Assert(string(generatedWrapper), Equals, expectedService)
}

func (s *servicesWrapperGenSuite) TestServiceTimerServiceUnit(c *C) {
const expectedServiceFmt = `[Unit]
# Auto-generated, DO NOT EDIT
Description=Service for snap application snap.app
Requires=%s-snap-44.mount
Wants=network-online.target
After=%s-snap-44.mount network-online.target
X-Snappy=yes
[Service]
ExecStart=/usr/bin/snap run --timer="10:00-12:00,,mon,23:00~01:00/2" snap.app
SyslogIdentifier=snap.app
Restart=%s
WorkingDirectory=/var/snap/snap/44
TimeoutStopSec=30
Type=%s
[Install]
WantedBy=multi-user.target
`

expectedService := fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix, "on-failure", "simple")
service := &snap.AppInfo{
Snap: &snap.Info{
SuggestedName: "snap",
Version: "0.3.4",
SideInfo: snap.SideInfo{Revision: snap.R(44)},
},
Name: "app",
Command: "bin/foo start",
Daemon: "simple",
StopTimeout: timeout.DefaultTimeout,
Timer: &snap.TimerInfo{
Timer: "10:00-12:00,,mon,23:00~01:00/2",
},
}

generatedWrapper, err := wrappers.GenerateSnapServiceFile(service)
c.Assert(err, IsNil)

c.Logf("service: \n%v\n", string(generatedWrapper))
c.Assert(string(generatedWrapper), Equals, expectedService)
}

func (s *servicesWrapperGenSuite) TestTimerGenerateSchedules(c *C) {
systemdAnalyzePath, _ := exec.LookPath("systemd-analyze")

Expand Down

0 comments on commit 54323d0

Please sign in to comment.