Merge lp:~thumper/juju-core/uniter-run-commands into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 2146
Proposed branch: lp:~thumper/juju-core/uniter-run-commands
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~thumper/juju-core/uniter-context-run-commands
Diff against target: 247 lines (+175/-0)
2 files modified
worker/uniter/uniter.go (+48/-0)
worker/uniter/uniter_test.go (+127/-0)
To merge this branch: bzr merge lp:~thumper/juju-core/uniter-run-commands
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+198830@code.launchpad.net

Commit message

Implement the uniter parts of juju-run

Add a method to the uniter that has the run listener.
The uniter gets the hook lock, sets up a jujuc server
and calls the commands using the hook context run commands
method.

https://codereview.appspot.com/41470043/

Description of the change

Implement the uniter parts of juju-run

Add a method to the uniter that has the run listener.
The uniter gets the hook lock, sets up a jujuc server
and calls the commands using the hook context run commands
method.

https://codereview.appspot.com/41470043/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Reviewers: mp+198830_code.launchpad.net,

Message:
Please take a look.

Description:
Implement the uniter parts of juju-run

Add a method to the uniter that has the run listener.
The uniter gets the hook lock, sets up a jujuc server
and calls the commands using the hook context run commands
method.

https://code.launchpad.net/~thumper/juju-core/uniter-run-commands/+merge/198830

Requires:
https://code.launchpad.net/~thumper/juju-core/uniter-context-run-commands/+merge/198671

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/41470043/

Affected files (+171, -0 lines):
   A [revision details]
   M worker/uniter/uniter.go
   M worker/uniter/uniter_test.go

Revision history for this message
Ian Booth (wallyworld) wrote :

LGTM with some tweaks

https://codereview.appspot.com/41470043/diff/1/worker/uniter/uniter_test.go
File worker/uniter/uniter_test.go (right):

https://codereview.appspot.com/41470043/diff/1/worker/uniter/uniter_test.go#newcode853
worker/uniter/uniter_test.go:853: runCommands{fmt.Sprintf("echo juju run
${JUJU_UNIT_NAME} > %s", filepath.Join(testDir, "run.output"))},
There's a lot of duplicated filepath.Join(testDir, "run.output").
Can we not just create a generic file at the top of the method and reuse
that? Assuming it is cleaned up after each test which we can add if it
is not done yet.

https://codereview.appspot.com/41470043/

Revision history for this message
Tim Penhey (thumper) wrote :

Please take a look.

https://codereview.appspot.com/41470043/diff/1/worker/uniter/uniter_test.go
File worker/uniter/uniter_test.go (right):

https://codereview.appspot.com/41470043/diff/1/worker/uniter/uniter_test.go#newcode853
worker/uniter/uniter_test.go:853: runCommands{fmt.Sprintf("echo juju run
${JUJU_UNIT_NAME} > %s", filepath.Join(testDir, "run.output"))},
On 2013/12/13 00:47:04, wallyworld wrote:
> There's a lot of duplicated filepath.Join(testDir, "run.output").
> Can we not just create a generic file at the top of the method and
reuse that?
> Assuming it is cleaned up after each test which we can add if it is
not done
> yet.

Added some magic to reduce duplication.

https://codereview.appspot.com/41470043/

Revision history for this message
Go Bot (go-bot) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'worker/uniter/uniter.go'
2--- worker/uniter/uniter.go 2013-12-13 03:09:56 +0000
3+++ worker/uniter/uniter.go 2013-12-13 03:09:57 +0000
4@@ -32,6 +32,14 @@
5
6 var logger = loggo.GetLogger("juju.worker.uniter")
7
8+const (
9+ // These work fine for linux, but should we need to work with windows
10+ // workloads in the future, we'll need to move these into a file that is
11+ // compiled conditionally for different targets and use tcp (most likely).
12+ RunListenerNetType = "unix"
13+ RunListenerFile = "run.socket"
14+)
15+
16 // A UniterExecutionObserver gets the appropriate methods called when a hook
17 // is executed and either succeeds or fails. Missing hooks don't get reported
18 // in this way.
19@@ -65,6 +73,7 @@
20 sf *StateFile
21 rand *rand.Rand
22 hookLock *fslock.Lock
23+ runListener *RunListener
24
25 ranConfigChanged bool
26 // The execution observer is only used in tests at this stage. Should this
27@@ -91,6 +100,7 @@
28 if err = u.init(unitTag); err != nil {
29 return err
30 }
31+ defer u.runListener.Close()
32 logger.Infof("unit %q started", u.unit)
33
34 // Start filtering state change events for consumption by modes.
35@@ -168,6 +178,17 @@
36 if err != nil {
37 return err
38 }
39+
40+ runListenerSocketPath := filepath.Join(u.baseDir, RunListenerFile)
41+ logger.Debugf("starting juju-run listener on %s:%s", RunListenerNetType, runListenerSocketPath)
42+ u.runListener, err = NewRunListener(u, RunListenerNetType, runListenerSocketPath)
43+ if err != nil {
44+ return err
45+ }
46+ // The socket needs to have permissions 777 in order for other users to use it.
47+ if err := os.Chmod(runListenerSocketPath, 0777); err != nil {
48+ return err
49+ }
50 u.relationers = map[int]*Relationer{}
51 u.relationHooks = make(chan hook.Info)
52 u.charm = charm.NewGitDir(filepath.Join(u.baseDir, "charm"))
53@@ -343,6 +364,33 @@
54 return srv, socketPath, nil
55 }
56
57+// RunCommands executes the supplied commands in a hook context.
58+func (u *Uniter) RunCommands(commands string) (results *cmd.RemoteResponse, err error) {
59+ logger.Tracef("run commands: %s", commands)
60+ hctxId := fmt.Sprintf("%s:run-commands:%d", u.unit.Name(), u.rand.Int63())
61+ lockMessage := fmt.Sprintf("%s: running commands", u.unit.Name())
62+ if err = u.acquireHookLock(lockMessage); err != nil {
63+ return nil, err
64+ }
65+ defer u.hookLock.Unlock()
66+
67+ hctx, err := u.getHookContext(hctxId, -1, "")
68+ if err != nil {
69+ return nil, err
70+ }
71+ srv, socketPath, err := u.startJujucServer(hctx)
72+ if err != nil {
73+ return nil, err
74+ }
75+ defer srv.Close()
76+
77+ result, err := hctx.RunCommands(commands, u.charm.Path(), u.toolsDir, socketPath)
78+ if result != nil {
79+ logger.Tracef("run commands: rc=%v\nstdout:\n%sstderr:\n%s", result.Code, result.Stdout, result.Stderr)
80+ }
81+ return result, err
82+}
83+
84 func (u *Uniter) notifyHookInternal(hook string, hctx *HookContext, method func(string)) {
85 if r, ok := hctx.HookRelation(); ok {
86 remote, _ := hctx.RemoteUnitName()
87
88=== modified file 'worker/uniter/uniter_test.go'
89--- worker/uniter/uniter_test.go 2013-12-13 03:09:56 +0000
90+++ worker/uniter/uniter_test.go 2013-12-13 03:09:57 +0000
91@@ -10,11 +10,13 @@
92 "fmt"
93 "io"
94 "io/ioutil"
95+ "net/rpc"
96 "net/url"
97 "os"
98 "os/exec"
99 "path/filepath"
100 "strconv"
101+ "strings"
102 stdtesting "testing"
103 "time"
104
105@@ -23,6 +25,7 @@
106
107 "launchpad.net/juju-core/agent/tools"
108 "launchpad.net/juju-core/charm"
109+ "launchpad.net/juju-core/cmd"
110 "launchpad.net/juju-core/errors"
111 "launchpad.net/juju-core/juju/testing"
112 "launchpad.net/juju-core/state"
113@@ -841,6 +844,50 @@
114 s.runUniterTests(c, upgradeConflictsTests)
115 }
116
117+func (s *UniterSuite) TestRunCommand(c *gc.C) {
118+ testDir := c.MkDir()
119+ testFile := func(name string) string {
120+ return filepath.Join(testDir, name)
121+ }
122+ echoUnitNameToFile := func(name string) string {
123+ return fmt.Sprintf("echo juju run ${JUJU_UNIT_NAME} > %s", filepath.Join(testDir, name))
124+ }
125+ tests := []uniterTest{
126+ ut(
127+ "run commands: environment",
128+ quickStart{},
129+ runCommands{echoUnitNameToFile("run.output")},
130+ verifyFile{filepath.Join(testDir, "run.output"), "juju run u/0\n"},
131+ ), ut(
132+ "run commands: jujuc commands",
133+ quickStartRelation{},
134+ runCommands{
135+ fmt.Sprintf("owner-get tag > %s", testFile("jujuc.output")),
136+ fmt.Sprintf("unit-get private-address >> %s", testFile("jujuc.output")),
137+ fmt.Sprintf("unit-get public-address >> %s", testFile("jujuc.output")),
138+ },
139+ verifyFile{
140+ testFile("jujuc.output"),
141+ "user-admin\nprivate.dummy.address.example.com\npublic.dummy.address.example.com\n",
142+ },
143+ ), ut(
144+ "run commands: async using rpc client",
145+ quickStart{},
146+ asyncRunCommands{echoUnitNameToFile("run.output")},
147+ verifyFile{testFile("run.output"), "juju run u/0\n"},
148+ ), ut(
149+ "run commands: waits for lock",
150+ quickStart{},
151+ acquireHookSyncLock{},
152+ asyncRunCommands{echoUnitNameToFile("wait.output")},
153+ verifyNoFile{testFile("wait.output")},
154+ releaseHookSyncLock,
155+ verifyFile{testFile("wait.output"), "juju run u/0\n"},
156+ ),
157+ }
158+ s.runUniterTests(c, tests)
159+}
160+
161 var relationsTests = []uniterTest{
162 // Relations.
163 ut(
164@@ -1894,3 +1941,83 @@
165 lock := createHookLock(c, ctx.dataDir)
166 c.Assert(lock.IsLocked(), jc.IsTrue)
167 }}
168+
169+type runCommands []string
170+
171+func (cmds runCommands) step(c *gc.C, ctx *context) {
172+ commands := strings.Join(cmds, "\n")
173+ result, err := ctx.uniter.RunCommands(commands)
174+ c.Assert(err, gc.IsNil)
175+ c.Check(result.Code, gc.Equals, 0)
176+ c.Check(string(result.Stdout), gc.Equals, "")
177+ c.Check(string(result.Stderr), gc.Equals, "")
178+}
179+
180+type asyncRunCommands []string
181+
182+func (cmds asyncRunCommands) step(c *gc.C, ctx *context) {
183+ commands := strings.Join(cmds, "\n")
184+ socketPath := filepath.Join(ctx.path, uniter.RunListenerFile)
185+
186+ go func() {
187+ // make sure the socket exists
188+ client, err := rpc.Dial("unix", socketPath)
189+ c.Assert(err, gc.IsNil)
190+ defer client.Close()
191+
192+ var result cmd.RemoteResponse
193+ err = client.Call(uniter.JujuRunEndpoint, commands, &result)
194+ c.Assert(err, gc.IsNil)
195+ c.Check(result.Code, gc.Equals, 0)
196+ c.Check(string(result.Stdout), gc.Equals, "")
197+ c.Check(string(result.Stderr), gc.Equals, "")
198+ }()
199+}
200+
201+type verifyFile struct {
202+ filename string
203+ content string
204+}
205+
206+func (verify verifyFile) fileExists() bool {
207+ _, err := os.Stat(verify.filename)
208+ return err == nil
209+}
210+
211+func (verify verifyFile) checkContent(c *gc.C) {
212+ content, err := ioutil.ReadFile(verify.filename)
213+ c.Assert(err, gc.IsNil)
214+ c.Assert(string(content), gc.Equals, verify.content)
215+}
216+
217+func (verify verifyFile) step(c *gc.C, ctx *context) {
218+ if verify.fileExists() {
219+ verify.checkContent(c)
220+ return
221+ }
222+ c.Logf("waiting for file: %s", verify.filename)
223+ timeout := time.After(worstCase)
224+ for {
225+ select {
226+ case <-time.After(coretesting.ShortWait):
227+ if verify.fileExists() {
228+ verify.checkContent(c)
229+ return
230+ }
231+ case <-timeout:
232+ c.Fatalf("file does not exist")
233+ }
234+ }
235+}
236+
237+// verify that the file does not exist
238+type verifyNoFile struct {
239+ filename string
240+}
241+
242+func (verify verifyNoFile) step(c *gc.C, ctx *context) {
243+ c.Assert(verify.filename, jc.DoesNotExist)
244+ // Wait a short time and check again.
245+ time.Sleep(coretesting.ShortWait)
246+ c.Assert(verify.filename, jc.DoesNotExist)
247+}

Subscribers

People subscribed via source and target branches

to status/vote changes: