Merge lp:~chipaca/snappy/service into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by John Lenton on 2015-07-23
Status: Merged
Approved by: John Lenton on 2015-07-24
Approved revision: 602
Merged at revision: 609
Proposed branch: lp:~chipaca/snappy/service
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Prerequisite: lp:~chipaca/snappy/serviceYaml
Diff against target: 633 lines (+541/-1)
8 files modified
cmd/snappy/cmd_service.go (+139/-0)
helpers/winsize.go (+48/-0)
po/snappy.pot (+20/-1)
snappy/errors.go (+3/-0)
snappy/service.go (+150/-0)
snappy/service_test.go (+142/-0)
systemd/systemd.go (+29/-0)
systemd/systemd_test.go (+10/-0)
To merge this branch: bzr merge lp:~chipaca/snappy/service
Reviewer Review Type Date Requested Status
Sergio Schvezov Approve on 2015-07-27
Michael Vogt 2015-07-23 Approve on 2015-07-24
Review via email: mp+265658@code.launchpad.net

This proposal supersedes a proposal from 2015-07-22.

Commit Message

Services command, iteration 1.

Description of the Change

For next iteration: apparmor & seccomp output in status.

To post a comment you must log in.
Michael Vogt (mvo) wrote :

Nice work! Thanks for this.

One nit I noticed while doing a quick test on a fresh snappy system:
"""
$ sudo ./snappy service status
snappy service not found
"""

Some more comments inline but nothing important, this is fine to land once the above is a bit more user friendly.

Michael Vogt (mvo) wrote :

Yay!

review: Approve
Snappy Tarmac (snappydevtarmac) wrote :

Attempt to merge into lp:snappy failed due to conflicts:

text conflict in po/snappy.pot

Snappy Tarmac (snappydevtarmac) wrote :
Download full text (3.3 KiB)

The attempt to merge lp:~chipaca/snappy/service into lp:snappy failed. Below is the output from the failed tests.

Checking docs
Checking formatting
Installing godeps
Install golint
Obtaining dependencies
update github.com/gosexy/gettext failed; trying to fetch newer version
update github.com/mvo5/goconfigparser failed; trying to fetch newer version
github.com/gosexy/gettext now at 98b7b91596d20b96909e6b60d57411547dd9959c
update github.com/blakesmith/ar failed; trying to fetch newer version
github.com/mvo5/goconfigparser now at 26426272dda20cc76aa1fa44286dc743d2972fe8
update github.com/cheggaaa/pb failed; trying to fetch newer version
github.com/blakesmith/ar now at c9a977dd0cc1392b023382c7bfa5a22af8d3b730
github.com/cheggaaa/pb now at e8c7cc515bfde3e267957a3b110080ceed51354e
update github.com/jessevdk/go-flags failed; trying to fetch newer version
update github.com/mvo5/uboot-go failed; trying to fetch newer version
github.com/jessevdk/go-flags now at 15347ef417a300349807983f15af9e65cd2e1b3a
update golang.org/x/crypto failed; trying to fetch newer version
github.com/mvo5/uboot-go now at 361f6ebcbb54f389d15dc9faefa000e996ba3e37
update gopkg.in/check.v1 failed; trying to fetch newer version
golang.org/x/crypto now at 60052bd85f2d91293457e8811b0cf26b773de469
update gopkg.in/yaml.v2 failed; trying to fetch newer version
gopkg.in/check.v1 now at 64131543e7896d5bcc6bd5a76287eb75ea96c673
gopkg.in/yaml.v2 now at 49c95bdc21843256fb6c4e0d370a05f24a0bf213
Building

# we always run in a fresh dir in tarmac
export GOPATH=$(mktemp -d)
trap 'rm -rf "$GOPATH"' EXIT

# this is a hack, but not sure tarmac is golang friendly
mkdir -p $GOPATH/src/launchpad.net/snappy
cp -a . $GOPATH/src/launchpad.net/snappy/
cd $GOPATH/src/launchpad.net/snappy

./run-checks
./run-checks: 2: ./run-checks: 1: not found
./run-checks: 2: ./run-checks: 3803: not found
./run-checks: 2: ./run-checks: 0c: not found

if which goctest >/dev/null; then
    goctest="goctest"
else
    goctest="go test"
fi

echo Checking docs
./mdlint.py docs/*.md

echo Checking formatting
fmt=$(gofmt -l .)

if [ -n "$fmt" ]; then
    echo "Formatting wrong in following files"
    echo "$fmt"
    exit 1
fi

echo Installing godeps
go get launchpad.net/godeps
export PATH=$PATH:$GOPATH/bin

echo Install golint
go get github.com/golang/lint/golint
export PATH=$PATH:$GOPATH/bin

echo Obtaining dependencies
godeps -u dependencies.tsv

echo Building
go build -v launchpad.net/snappy/...
github.com/blakesmith/ar
launchpad.net/snappy/logger
github.com/jessevdk/go-flags
launchpad.net/snappy/helpers
golang.org/x/crypto/ssh/terminal
github.com/gosexy/gettext
launchpad.net/snappy/clickdeb
launchpad.net/snappy/pkg
launchpad.net/snappy/priv
github.com/cheggaaa/pb
launchpad.net/snappy/progress
launchpad.net/snappy/i18n
github.com/mvo5/goconfigparser
gopkg.in/yaml.v2
launchpad.net/snappy/oauth
github.com/mvo5/uboot-go/uenv
launchpad.net/snappy/policy
launchpad.net/snappy/release
launchpad.net/snappy/systemd
launchpad.net/snappy/i18n/xgettext-go
launchpad.net/snappy/coreconfig
launchpad.net/snappy/partition
launchpad.net/snappy/provisioning
launchpad.net/snappy/snappy
launchpad.net/snappy/cmd/snappy
# launchpad.net/snappy/c...

Read more...

John Lenton (chipaca) wrote :

I must have a brown paper bag around here somewhere...

lp:~chipaca/snappy/service updated on 2015-07-24
602. By John Lenton on 2015-07-24

this is the brown paper back edit

Sergio Schvezov (sergiusens) wrote :

\o/

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'cmd/snappy/cmd_service.go'
2--- cmd/snappy/cmd_service.go 1970-01-01 00:00:00 +0000
3+++ cmd/snappy/cmd_service.go 2015-07-24 15:24:16 +0000
4@@ -0,0 +1,139 @@
5+// -*- Mode: Go; indent-tabs-mode: t -*-
6+
7+/*
8+ * Copyright (C) 2014-2015 Canonical Ltd
9+ *
10+ * This program is free software: you can redistribute it and/or modify
11+ * it under the terms of the GNU General Public License version 3 as
12+ * published by the Free Software Foundation.
13+ *
14+ * This program is distributed in the hope that it will be useful,
15+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+ * GNU General Public License for more details.
18+ *
19+ * You should have received a copy of the GNU General Public License
20+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
21+ *
22+ */
23+
24+package main
25+
26+import (
27+ "fmt"
28+ "os"
29+ "text/tabwriter"
30+
31+ "launchpad.net/snappy/helpers"
32+ "launchpad.net/snappy/i18n"
33+ "launchpad.net/snappy/logger"
34+ "launchpad.net/snappy/progress"
35+ "launchpad.net/snappy/snappy"
36+)
37+
38+type cmdService struct {
39+ Status svcStatus `command:"status"`
40+ Start svcStart `command:"start"`
41+ Stop svcStop `command:"stop"`
42+ Restart svcRestart `command:"restart"`
43+}
44+type svcBase struct {
45+ Args struct {
46+ Snap string `positional-arg-name:"snap"`
47+ Service string `positional-arg-name:"service"`
48+ } `positional-args:"yes"`
49+}
50+type svcStatus struct{ svcBase }
51+type svcStart struct{ svcBase }
52+type svcStop struct{ svcBase }
53+type svcRestart struct{ svcBase }
54+
55+func init() {
56+ _, err := parser.AddCommand("service",
57+ i18n.G("Query and modify snappy services"),
58+ i18n.G("Query and modify snappy services of locally-installed packages"),
59+ &cmdService{})
60+
61+ if err != nil {
62+ logger.Panicf("Unable to service: %v", err)
63+ }
64+
65+}
66+
67+const (
68+ doStatus = iota
69+ doStart
70+ doStop
71+ doRestart
72+)
73+
74+func (s *svcBase) doExecute(cmd int) ([]string, error) {
75+ actor, err := snappy.FindServices(s.Args.Snap, s.Args.Service, progress.MakeProgressBar())
76+ if err != nil {
77+ return nil, err
78+ }
79+
80+ switch cmd {
81+ case doStatus:
82+ return actor.Status()
83+ case doStart:
84+ return nil, actor.Start()
85+ case doStop:
86+ return nil, actor.Stop()
87+ case doRestart:
88+ return nil, actor.Restart()
89+ default:
90+ panic("can't happen")
91+ }
92+}
93+
94+func (s *svcStatus) Execute(args []string) error {
95+ stati, err := s.doExecute(doStatus)
96+ if err != nil {
97+ return err
98+ }
99+
100+ w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
101+
102+ ws, _ := helpers.GetTermWinsize()
103+ rows := int(ws.Row) - 2
104+
105+ header := i18n.G("Snap\tService\tState")
106+
107+ for i, status := range stati {
108+ // print a header every $rows rows if rows is bigger
109+ // than 10; otherwise, just the once. 10 is arbitrary,
110+ // but the thinking is that on the one hand you don't
111+ // want to be using up too much space with headers on
112+ // really small terminals and on the other rows might
113+ // actually be negative if you're not on a tty.
114+ if i%rows == 0 && (i == 0 || rows > 10) {
115+ fmt.Fprintln(w, header)
116+ }
117+ fmt.Fprintln(w, status)
118+ }
119+ w.Flush()
120+
121+ return err
122+}
123+
124+func (s *svcStart) Execute(args []string) error {
125+ return withMutex(func() error {
126+ _, err := s.doExecute(doStart)
127+ return err
128+ })
129+}
130+
131+func (s *svcStop) Execute(args []string) error {
132+ return withMutex(func() error {
133+ _, err := s.doExecute(doStop)
134+ return err
135+ })
136+}
137+
138+func (s *svcRestart) Execute(args []string) error {
139+ return withMutex(func() error {
140+ _, err := s.doExecute(doRestart)
141+ return err
142+ })
143+}
144
145=== added file 'helpers/winsize.go'
146--- helpers/winsize.go 1970-01-01 00:00:00 +0000
147+++ helpers/winsize.go 2015-07-24 15:24:16 +0000
148@@ -0,0 +1,48 @@
149+// -*- Mode: Go; indent-tabs-mode: t -*-
150+
151+/*
152+ * Copyright (C) 2014-2015 Canonical Ltd
153+ *
154+ * This program is free software: you can redistribute it and/or modify
155+ * it under the terms of the GNU General Public License version 3 as
156+ * published by the Free Software Foundation.
157+ *
158+ * This program is distributed in the hope that it will be useful,
159+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
160+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
161+ * GNU General Public License for more details.
162+ *
163+ * You should have received a copy of the GNU General Public License
164+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
165+ *
166+ */
167+
168+package helpers
169+
170+import (
171+ "syscall"
172+ "unsafe"
173+)
174+
175+// Winsize is from tty_ioctl(4)
176+type Winsize struct {
177+ Row uint16
178+ Col uint16
179+ xpixel uint16 // unused
180+ Ypixel uint16 // unused
181+}
182+
183+// GetTermWinsize performs the TIOCGWINSZ ioctl on stdout
184+func GetTermWinsize() (*Winsize, error) {
185+ ws := &Winsize{}
186+ x, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
187+
188+ if int(x) == -1 {
189+ // returning ws on error lets people that don't care
190+ // about the error get on with querying the struct
191+ // (which will be empty on error).
192+ return ws, errno
193+ }
194+
195+ return ws, nil
196+}
197
198=== modified file 'po/snappy.pot'
199--- po/snappy.pot 2015-07-24 12:08:00 +0000
200+++ po/snappy.pot 2015-07-24 15:24:16 +0000
201@@ -7,7 +7,7 @@
202 msgid ""
203 msgstr "Project-Id-Version: snappy\n"
204 "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n"
205- "POT-Creation-Date: 2015-07-24 14:07+0200\n"
206+ "POT-Creation-Date: 2015-07-24 16:16+0100\n"
207 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
208 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
209 "Language-Team: LANGUAGE <LL@li.org>\n"
210@@ -155,6 +155,12 @@
211 msgid "Purging %s\n"
212 msgstr ""
213
214+msgid "Query and modify snappy services"
215+msgstr ""
216+
217+msgid "Query and modify snappy services of locally-installed packages"
218+msgstr ""
219+
220 msgid "Query the store for available packages"
221 msgstr ""
222
223@@ -226,6 +232,9 @@
224 msgid "Show channel information and expand all fields"
225 msgstr ""
226
227+msgid "Snap\tService\tState"
228+msgstr ""
229+
230 msgid "Specify an alternate output directory for the resulting package"
231 msgstr ""
232
233@@ -324,6 +333,16 @@
234 msgid "snappy autopilot triggered a reboot to boot into an up to date system -- temprorarily disable the reboot by running 'sudo shutdown -c'"
235 msgstr ""
236
237+#. TRANSLATORS: the first %s is the package name, the second is the service name; the %v is the error
238+#, c-format
239+msgid "unable to start %s's service %s: %v"
240+msgstr ""
241+
242+#. TRANSLATORS: the first %s is the package name, the second is the service name; the %v is the error
243+#, c-format
244+msgid "unable to stop %s's service %s: %v"
245+msgstr ""
246+
247 #. TRANSLATORS: the %s is a date
248 #, c-format
249 msgid "updated: %s\n"
250
251=== modified file 'snappy/errors.go'
252--- snappy/errors.go 2015-06-30 12:30:02 +0000
253+++ snappy/errors.go 2015-07-24 15:24:16 +0000
254@@ -32,6 +32,9 @@
255 // ErrPackageNotFound is returned when a snap can not be found
256 ErrPackageNotFound = errors.New("snappy package not found")
257
258+ // ErrServiceNotFound is returned when a service can not be found
259+ ErrServiceNotFound = errors.New("snappy service not found")
260+
261 // ErrNeedRoot is returned when a command needs root privs but
262 // the caller is not root
263 ErrNeedRoot = errors.New("this command requires root access. Please re-run using 'sudo'")
264
265=== added file 'snappy/service.go'
266--- snappy/service.go 1970-01-01 00:00:00 +0000
267+++ snappy/service.go 2015-07-24 15:24:16 +0000
268@@ -0,0 +1,150 @@
269+// -*- Mode: Go; indent-tabs-mode: t -*-
270+
271+/*
272+ * Copyright (C) 2014-2015 Canonical Ltd
273+ *
274+ * This program is free software: you can redistribute it and/or modify
275+ * it under the terms of the GNU General Public License version 3 as
276+ * published by the Free Software Foundation.
277+ *
278+ * This program is distributed in the hope that it will be useful,
279+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
280+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
281+ * GNU General Public License for more details.
282+ *
283+ * You should have received a copy of the GNU General Public License
284+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
285+ *
286+ */
287+
288+package snappy
289+
290+import (
291+ "fmt"
292+ "path/filepath"
293+ "time"
294+
295+ "launchpad.net/snappy/i18n"
296+ "launchpad.net/snappy/progress"
297+ "launchpad.net/snappy/systemd"
298+)
299+
300+type svcT struct {
301+ m *packageYaml
302+ svc *ServiceYaml
303+}
304+
305+// A ServiceActor collects the services found by FindServices and lets
306+// you perform differnt actions (start, stop, etc) on them.
307+type ServiceActor struct {
308+ svcs []*svcT
309+ pb progress.Meter
310+ sysd systemd.Systemd
311+}
312+
313+// FindServices finds all matching services (empty string matches all)
314+// and lets you perform different actions (start, stop, etc) on them.
315+//
316+// If a snap is specified and no matching snaps are found,
317+// ErrPackageNotFound is returned. If a snap is specified and the
318+// matching snaps has no matching services, ErrServiceNotFound is
319+// returned.
320+//
321+// If no snap is specified, an empty result is not an error.
322+func FindServices(snapName string, serviceName string, pb progress.Meter) (*ServiceActor, error) {
323+ var svcs []*svcT
324+
325+ repo := NewMetaLocalRepository()
326+ installed, _ := repo.Installed()
327+
328+ foundSnap := false
329+ for _, part := range installed {
330+ snap, ok := part.(*SnapPart)
331+ if !ok {
332+ // can't happen
333+ continue
334+ }
335+ if snapName != "" && snapName != snap.Name() {
336+ continue
337+ }
338+ foundSnap = true
339+
340+ yamls := snap.ServiceYamls()
341+ for i := range yamls {
342+ if serviceName != "" && serviceName != yamls[i].Name {
343+ continue
344+ }
345+ s := &svcT{
346+ m: snap.m,
347+ svc: &yamls[i],
348+ }
349+ svcs = append(svcs, s)
350+ }
351+ }
352+ if snapName != "" {
353+ if !foundSnap {
354+ return nil, ErrPackageNotFound
355+ }
356+ if len(svcs) == 0 {
357+ return nil, ErrServiceNotFound
358+ }
359+ }
360+
361+ return &ServiceActor{
362+ svcs: svcs,
363+ pb: pb,
364+ sysd: systemd.New(globalRootDir, pb),
365+ }, nil
366+}
367+
368+// Status of all the found services.
369+func (actor *ServiceActor) Status() ([]string, error) {
370+ var stati []string
371+ for _, svc := range actor.svcs {
372+ svcname := filepath.Base(generateServiceFileName(svc.m, *svc.svc))
373+ status, err := actor.sysd.Status(svcname)
374+ if err != nil {
375+ return nil, err
376+ }
377+ status = fmt.Sprintf("%s\t%s\t%s", svc.m.Name, svc.svc.Name, status)
378+ stati = append(stati, status)
379+ }
380+
381+ return stati, nil
382+}
383+
384+// Start all the found services.
385+func (actor *ServiceActor) Start() error {
386+ for _, svc := range actor.svcs {
387+ svcname := filepath.Base(generateServiceFileName(svc.m, *svc.svc))
388+ if err := actor.sysd.Start(svcname); err != nil {
389+ // TRANSLATORS: the first %s is the package name, the second is the service name; the %v is the error
390+ return fmt.Errorf(i18n.G("unable to start %s's service %s: %v"), svc.m.Name, svc.svc.Name, err)
391+ }
392+ }
393+
394+ return nil
395+}
396+
397+// Stop all the found services.
398+func (actor *ServiceActor) Stop() error {
399+ for _, svc := range actor.svcs {
400+ svcname := filepath.Base(generateServiceFileName(svc.m, *svc.svc))
401+ if err := actor.sysd.Stop(svcname, time.Duration(svc.svc.StopTimeout)); err != nil {
402+ // TRANSLATORS: the first %s is the package name, the second is the service name; the %v is the error
403+ return fmt.Errorf(i18n.G("unable to stop %s's service %s: %v"), svc.m.Name, svc.svc.Name, err)
404+ }
405+ }
406+
407+ return nil
408+}
409+
410+// Restart all the found services.
411+func (actor *ServiceActor) Restart() error {
412+ err := actor.Stop()
413+ if err != nil {
414+ return err
415+ }
416+
417+ return actor.Start()
418+}
419
420=== added file 'snappy/service_test.go'
421--- snappy/service_test.go 1970-01-01 00:00:00 +0000
422+++ snappy/service_test.go 2015-07-24 15:24:16 +0000
423@@ -0,0 +1,142 @@
424+// -*- Mode: Go; indent-tabs-mode: t -*-
425+
426+/*
427+ * Copyright (C) 2014-2015 Canonical Ltd
428+ *
429+ * This program is free software: you can redistribute it and/or modify
430+ * it under the terms of the GNU General Public License version 3 as
431+ * published by the Free Software Foundation.
432+ *
433+ * This program is distributed in the hope that it will be useful,
434+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
435+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
436+ * GNU General Public License for more details.
437+ *
438+ * You should have received a copy of the GNU General Public License
439+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
440+ *
441+ */
442+
443+package snappy
444+
445+import (
446+ "errors"
447+
448+ . "gopkg.in/check.v1"
449+
450+ "launchpad.net/snappy/progress"
451+ "launchpad.net/snappy/systemd"
452+)
453+
454+type ServiceActorSuite struct {
455+ i int
456+ argses [][]string
457+ outs [][]byte
458+ errors []error
459+ pb progress.Meter
460+}
461+
462+var _ = Suite(&ServiceActorSuite{})
463+
464+// borrowed from systemd_test
465+func (s *ServiceActorSuite) myRun(args ...string) (out []byte, err error) {
466+ s.argses = append(s.argses, args)
467+ if s.i < len(s.outs) {
468+ out = s.outs[s.i]
469+ }
470+ if s.i < len(s.errors) {
471+ err = s.errors[s.i]
472+ }
473+ s.i++
474+ return out, err
475+}
476+
477+func (s *ServiceActorSuite) SetUpTest(c *C) {
478+ SetRootDir(c.MkDir())
479+ systemd.SystemctlCmd = s.myRun
480+ makeInstalledMockSnap(globalRootDir, "")
481+ s.i = 0
482+ s.argses = nil
483+ s.errors = nil
484+ s.outs = nil
485+ s.pb = &MockProgressMeter{}
486+}
487+
488+func (s *ServiceActorSuite) TestFindServicesNoPackages(c *C) {
489+ _, err := FindServices("notfound", "", s.pb)
490+ c.Check(err, Equals, ErrPackageNotFound)
491+}
492+
493+func (s *ServiceActorSuite) TestFindServicesNoPackagesNoPattern(c *C) {
494+ // tricky way of hiding the installed package ;)
495+ SetRootDir(c.MkDir())
496+ actor, err := FindServices("", "", s.pb)
497+ c.Check(err, IsNil)
498+ c.Assert(actor, NotNil)
499+ c.Check(actor.svcs, HasLen, 0)
500+}
501+
502+func (s *ServiceActorSuite) TestFindServicesNoServices(c *C) {
503+ _, err := FindServices("hello-app", "notfound", s.pb)
504+ c.Check(err, Equals, ErrServiceNotFound)
505+}
506+
507+func (s *ServiceActorSuite) TestFindServicesFindsServices(c *C) {
508+ actor, err := FindServices("", "", s.pb)
509+ c.Assert(err, IsNil)
510+ c.Assert(actor, NotNil)
511+ c.Check(actor.svcs, HasLen, 1)
512+
513+ s.outs = [][]byte{
514+ nil, // for the "stop"
515+ []byte("ActiveState=inactive\n"),
516+ nil, // for the "start"
517+ nil, // for the "stop"
518+ []byte("ActiveState=inactive\n"),
519+ nil, // for the "start"
520+ []byte("Id=x\nLoadState=loaded\nActiveState=active\nSubState=running\n"), // status
521+ }
522+ s.errors = []error{
523+ nil, nil, // stop & check
524+ nil, // start
525+ nil, nil, nil, // restart (== stop & start)
526+ nil, // status
527+ &systemd.Timeout{}, // flag
528+ }
529+
530+ c.Check(actor.Stop(), IsNil)
531+ c.Check(actor.Start(), IsNil)
532+ c.Check(actor.Restart(), IsNil)
533+ status, err := actor.Status()
534+ c.Check(err, IsNil)
535+ c.Assert(status, HasLen, 1)
536+ c.Check(status[0], Equals, "hello-app\tsvc1\tloaded; active (running)")
537+}
538+
539+func (s *ServiceActorSuite) TestFindServicesReportsErrors(c *C) {
540+ actor, err := FindServices("", "", s.pb)
541+ c.Assert(err, IsNil)
542+ c.Assert(actor, NotNil)
543+ c.Check(actor.svcs, HasLen, 1)
544+
545+ anError := errors.New("error")
546+
547+ s.outs = [][]byte{
548+ nil,
549+ nil,
550+ nil,
551+ nil,
552+ }
553+ s.errors = []error{
554+ anError, // stop
555+ anError, // start
556+ anError, // restart
557+ anError, // status
558+ }
559+
560+ c.Check(actor.Stop(), NotNil)
561+ c.Check(actor.Start(), NotNil)
562+ c.Check(actor.Restart(), NotNil)
563+ _, err = actor.Status()
564+ c.Check(err, NotNil)
565+}
566
567=== modified file 'systemd/systemd.go'
568--- systemd/systemd.go 2015-06-05 18:29:52 +0000
569+++ systemd/systemd.go 2015-07-24 15:24:16 +0000
570@@ -68,6 +68,7 @@
571 Kill(service, signal string) error
572 Restart(service string, timeout time.Duration) error
573 GenServiceFile(desc *ServiceDescription) string
574+ Status(service string) (string, error)
575 }
576
577 // ServiceDescription describes a snappy systemd service
578@@ -140,6 +141,34 @@
579 return err
580 }
581
582+var statusregex = regexp.MustCompile(`(?m)^(?:(.*?)=(.*))?$`)
583+
584+func (s *systemd) Status(serviceName string) (string, error) {
585+ bs, err := SystemctlCmd("show", "--property=Id,LoadState,ActiveState,SubState", serviceName)
586+ if err != nil {
587+ return "", err
588+ }
589+
590+ load, active, sub := "", "", ""
591+
592+ for _, bs := range statusregex.FindAllSubmatch(bs, -1) {
593+ if len(bs[0]) > 0 {
594+ k := string(bs[1])
595+ v := string(bs[2])
596+ switch k {
597+ case "LoadState":
598+ load = v
599+ case "ActiveState":
600+ active = v
601+ case "SubState":
602+ sub = v
603+ }
604+ }
605+ }
606+
607+ return fmt.Sprintf("%s; %s (%s)", load, active, sub), nil
608+}
609+
610 // Stop the given service, and wait until it has stopped.
611 func (s *systemd) Stop(serviceName string, timeout time.Duration) error {
612 if _, err := SystemctlCmd("stop", serviceName); err != nil {
613
614=== modified file 'systemd/systemd_test.go'
615--- systemd/systemd_test.go 2015-06-05 18:29:52 +0000
616+++ systemd/systemd_test.go 2015-07-24 15:24:16 +0000
617@@ -111,6 +111,16 @@
618 c.Check(s.argses[1], DeepEquals, s.argses[3])
619 }
620
621+func (s *SystemdTestSuite) TestStatus(c *C) {
622+ s.outs = [][]byte{
623+ []byte("Id=Thing\nLoadState=LoadState\nActiveState=ActiveState\nSubState=SubState\n"),
624+ }
625+ s.errors = []error{nil}
626+ out, err := New("", s.rep).Status("foo")
627+ c.Assert(err, IsNil)
628+ c.Check(out, Equals, "LoadState; ActiveState (SubState)")
629+}
630+
631 func (s *SystemdTestSuite) TestStopTimeout(c *C) {
632 oldSteps := stopSteps
633 oldDelay := stopDelay

Subscribers

People subscribed via source and target branches