Merge lp:~fwereade/juju-core/jujud-integrate-cleaner-resumer into lp:~go-bot/juju-core/trunk

Proposed by William Reade
Status: Rejected
Rejected by: William Reade
Proposed branch: lp:~fwereade/juju-core/jujud-integrate-cleaner-resumer
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 680 lines (+279/-198)
6 files modified
cmd/jujud/agent.go (+8/-6)
cmd/jujud/agent_test.go (+189/-4)
cmd/jujud/machine.go (+11/-6)
cmd/jujud/machine_test.go (+67/-178)
cmd/jujud/unit.go (+3/-3)
environs/testing/storage.go (+1/-1)
To merge this branch: bzr merge lp:~fwereade/juju-core/jujud-integrate-cleaner-resumer
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+170907@code.launchpad.net

Description of the change

jujud: JobManageState runs cleaner, resumer

Nothing to see there... but I also got angry and tweaked Agent so I could
just test the damn tasks themselves and not worry about the side effects.
And then I needed to test RunAgentLoop as well to verify that it does the
right thing with the tasks it gets from a mocked Agent.

https://codereview.appspot.com/10439046/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

Reviewers: mp+170907_code.launchpad.net,

Message:
Please take a look.

Description:
jujud: JobManageState runs cleaner, resumer

Nothing to see there... but I also got angry and tweaked Agent so I
could
just test the damn tasks themselves and not worry about the side
effects.
And then I needed to test RunAgentLoop as well to verify that it does
the
right thing with the tasks it gets from a mocked Agent.

https://code.launchpad.net/~fwereade/juju-core/jujud-integrate-cleaner-resumer/+merge/170907

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M cmd/jujud/agent.go
   M cmd/jujud/agent_test.go
   M cmd/jujud/machine.go
   M cmd/jujud/machine_test.go
   M cmd/jujud/unit.go
   M environs/testing/storage.go

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (5.4 KiB)

Some questions about the testing infrastructure, but overall
LGTM

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent.go
File cmd/jujud/agent.go (left):

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent.go#oldcode130
cmd/jujud/agent.go:130: RunOnce(st *state.State, entity AgentState)
error
I do really like the rename of RunOnce to StartTasks.
As RunOnce can be interpreted as "only ever run this once", which
confuses it a bit with why you are calling it in a loop.

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent_test.go
File cmd/jujud/agent_test.go (right):

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent_test.go#newcode275
cmd/jujud/agent_test.go:275: conf.MachineNonce = state.BootstrapNonce
I thought all machines that aren't machine/0 were meant to have their
own Nonce's. Is this just to have *something*? (As in, it could just as
easily be a random string, as long as you set it and preserve it?)

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent_test.go#newcode430
cmd/jujud/agent_test.go:430: s.retryDelay, retryDelay = retryDelay,
time.Millisecond
I appreciate not having delays in the test suite.
I wonder about using a more obvious naming convention for package-global
variables that will be mutated in separate files.

Certainly when I read this, it looks like a function-local 'retryDelay'
variable. I realize there is no ':', and I can do the 'grep' to figure
out where 'retryDelay' is defined (somewhere in the cmd/jujud package).

Nothing to do with your proposed changes, just something about "where is
this variable coming from" that I find hard to read with our current
idioms. (In python you at least have to say 'global retryDelay' so you
know to go look for it somewhere else. And even then it would still be
in the same file.)

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent_test.go#newcode448
cmd/jujud/agent_test.go:448: }
Not specifically related to your patch, but "type Agent interface" has
no doc strings for me to understand what it is actually trying to do.
And AgentState is defined in 'upgrade.go' while 'Agent' is defined in
agent.go.

Anyway, the point I wanted to make was to think about Mock and what we
are trying to do. The concern is always that a Mock gets out of sync
with the real thing, and your tests just always keep passing because
Mock doesn't migrate.

I'm guessing go's "interface" helps us enough here, in that if Agent
changes its interface mockAgent will be updated to match (at least the
function signatures). Like in this case, you changed from RunOnce to
StartTasks, you do have to update the mock or the compiler will
complain.

In general, I love to see decoupling of the various bits of Juju and
make it easier to test just one layer. Though I generally prefer tested
Doubles (they act like the thing they are doubling) rather than
ultralightweight Mocks (they record what they were called with).

It also seems odd that you are able to mock out running 'heavyweight
tasks', but you still have to have a State object that connects to a
real mongodb running. It feels like if you are going to mock anything,
you would want to remove the State conn...

Read more...

Revision history for this message
William Reade (fwereade) wrote :

Conflicts irreparably with API changes to jujud

Revision history for this message
William Reade (fwereade) wrote :
Download full text (6.3 KiB)

Thanks for the review; dropping this due to collision.

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent_test.go
File cmd/jujud/agent_test.go (right):

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent_test.go#newcode275
cmd/jujud/agent_test.go:275: conf.MachineNonce = state.BootstrapNonce
On 2013/06/23 12:53:07, jameinel wrote:
> I thought all machines that aren't machine/0 were meant to have their
own
> Nonce's. Is this just to have *something*? (As in, it could just as
easily be a
> random string, as long as you set it and preserve it?)

Ha, yeah, I moved that method without reading it. Not sure why that's
there at all.

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent_test.go#newcode430
cmd/jujud/agent_test.go:430: s.retryDelay, retryDelay = retryDelay,
time.Millisecond
On 2013/06/23 12:53:07, jameinel wrote:
> I appreciate not having delays in the test suite.
> I wonder about using a more obvious naming convention for
package-global
> variables that will be mutated in separate files.

> Certainly when I read this, it looks like a function-local
'retryDelay'
> variable. I realize there is no ':', and I can do the 'grep' to figure
out where
> 'retryDelay' is defined (somewhere in the cmd/jujud package).

> Nothing to do with your proposed changes, just something about "where
is this
> variable coming from" that I find hard to read with our current
idioms. (In
> python you at least have to say 'global retryDelay' so you know to go
look for
> it somewhere else. And even then it would still be in the same file.)

I think we've got a bit of a general problem with code being pretty
randomly scattered across files in a package... that's not for want of
bikeshedding, though. It's worse here because we're testing in-package
and the usual restrictions don't apply.

https://codereview.appspot.com/10439046/diff/1/cmd/jujud/agent_test.go#newcode448
cmd/jujud/agent_test.go:448: }
On 2013/06/23 12:53:07, jameinel wrote:
> Not specifically related to your patch, but "type Agent interface" has
no doc
> strings for me to understand what it is actually trying to do. And
AgentState is
> defined in 'upgrade.go' while 'Agent' is defined in agent.go.

I hate spelunking through this package for exactly these reasons :).

> Anyway, the point I wanted to make was to think about Mock and what we
are
> trying to do. The concern is always that a Mock gets out of sync with
the real
> thing, and your tests just always keep passing because Mock doesn't
migrate.

> I'm guessing go's "interface" helps us enough here, in that if Agent
changes its
> interface mockAgent will be updated to match (at least the function
signatures).
> Like in this case, you changed from RunOnce to StartTasks, you do have
to update
> the mock or the compiler will complain.

Yeah; doesn't help with semantic changes, but it's handy all the same.

> In general, I love to see decoupling of the various bits of Juju and
make it
> easier to test just one layer. Though I generally prefer tested
Doubles (they
> act like the thing they are doubling) rather than ultralightweight
Mocks (they
> record what they were called with).

No theoretical argument, but I'm not...

Read more...

Unmerged revisions

1318. By William Reade

go fmt :/

1317. By William Reade

merge parent

1316. By William Reade

machine agent with JobManageState now runs Cleaner, Resumer; also, more unity testing for machine agent

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmd/jujud/agent.go'
--- cmd/jujud/agent.go 2013-05-31 08:33:34 +0000
+++ cmd/jujud/agent.go 2013-06-21 20:42:27 +0000
@@ -126,10 +126,10 @@
126}126}
127127
128type Agent interface {128type Agent interface {
129 Tag() string
129 Tomb() *tomb.Tomb130 Tomb() *tomb.Tomb
130 RunOnce(st *state.State, entity AgentState) error
131 Entity(st *state.State) (AgentState, error)131 Entity(st *state.State) (AgentState, error)
132 Tag() string132 StartTasks(st *state.State, entity AgentState) []task
133}133}
134134
135// runLoop repeatedly calls runOnce until it returns worker.ErrTerminateAgent135// runLoop repeatedly calls runOnce until it returns worker.ErrTerminateAgent
@@ -187,17 +187,19 @@
187}187}
188188
189// RunAgentLoop repeatedly connects to the state server189// RunAgentLoop repeatedly connects to the state server
190// and calls the agent's RunOnce method.190// and runs the agent's tasks.
191func RunAgentLoop(c *agent.Conf, a Agent) error {191func RunAgentLoop(c *agent.Conf, a Agent) error {
192 return runLoop(func() error {192 dying := a.Tomb().Dying()
193 body := func() error {
193 st, entity, err := openState(c, a)194 st, entity, err := openState(c, a)
194 if err != nil {195 if err != nil {
195 return err196 return err
196 }197 }
197 defer st.Close()198 defer st.Close()
198 // TODO(rog) connect to API.199 // TODO(rog) connect to API.
199 return a.RunOnce(st, entity)200 return runTasks(dying, a.StartTasks(st, entity)...)
200 }, a.Tomb().Dying())201 }
202 return runLoop(body, dying)
201}203}
202204
203// This mutex ensures that we can have two concurrent workers205// This mutex ensures that we can have two concurrent workers
204206
=== modified file 'cmd/jujud/agent_test.go'
--- cmd/jujud/agent_test.go 2013-05-31 08:33:34 +0000
+++ cmd/jujud/agent_test.go 2013-06-21 20:42:27 +0000
@@ -5,9 +5,16 @@
55
6import (6import (
7 "bytes"7 "bytes"
8 stderrors "errors"
8 "fmt"9 "fmt"
10 "net/http"
11 "os"
12 "path/filepath"
13 "time"
14
9 . "launchpad.net/gocheck"15 . "launchpad.net/gocheck"
10 "launchpad.net/juju-core/cmd"16 "launchpad.net/juju-core/cmd"
17 "launchpad.net/juju-core/constraints"
11 "launchpad.net/juju-core/environs/agent"18 "launchpad.net/juju-core/environs/agent"
12 "launchpad.net/juju-core/environs/tools"19 "launchpad.net/juju-core/environs/tools"
13 "launchpad.net/juju-core/errors"20 "launchpad.net/juju-core/errors"
@@ -17,10 +24,6 @@
17 "launchpad.net/juju-core/version"24 "launchpad.net/juju-core/version"
18 "launchpad.net/juju-core/worker"25 "launchpad.net/juju-core/worker"
19 "launchpad.net/tomb"26 "launchpad.net/tomb"
20 "net/http"
21 "os"
22 "path/filepath"
23 "time"
24)27)
2528
26var _ = Suite(&toolSuite{})29var _ = Suite(&toolSuite{})
@@ -258,6 +261,23 @@
258 testing.JujuConnSuite261 testing.JujuConnSuite
259}262}
260263
264// primeMachineAgent adds a new Machine to run the given jobs, and sets up the
265// machine agent's directory. It returns the new machine, the
266// agent's configuration and the tools currently running.
267func (s *agentSuite) primeMachineAgent(c *C, jobs ...state.MachineJob) (*state.Machine, *agent.Conf, *state.Tools) {
268 m, err := s.State.InjectMachine("series", constraints.Value{}, "ardbeg-0", jobs...)
269 c.Assert(err, IsNil)
270 err = m.SetMongoPassword("machine-password")
271 c.Assert(err, IsNil)
272 err = m.SetPassword("machine-api-password")
273 c.Assert(err, IsNil)
274 conf, tools := s.primeAgent(c, state.MachineTag(m.Id()), "machine-password")
275 conf.MachineNonce = state.BootstrapNonce
276 conf.Write()
277 c.Assert(err, IsNil)
278 return m, conf, tools
279}
280
261// primeAgent writes the configuration file and tools281// primeAgent writes the configuration file and tools
262// for an agent with the given entity name.282// for an agent with the given entity name.
263// It returns the agent's configuration and the current tools.283// It returns the agent's configuration and the current tools.
@@ -399,3 +419,168 @@
399 *c = *nc419 *c = *nc
400 return nil420 return nil
401}421}
422
423type RunAgentLoopSuite struct {
424 agentSuite
425 retryDelay time.Duration
426}
427
428func (s *RunAgentLoopSuite) SetUpTest(c *C) {
429 s.agentSuite.SetUpTest(c)
430 s.retryDelay, retryDelay = retryDelay, time.Millisecond
431}
432
433func (s *RunAgentLoopSuite) TearDownTest(c *C) {
434 retryDelay = s.retryDelay
435 s.agentSuite.TearDownTest(c)
436}
437
438var _ = Suite(&RunAgentLoopSuite{})
439
440// mockAgent allows us to inject our own task list into RunAgentLoop and thus
441// verify its behaviour without running real, heavyweight, tasks.
442type mockAgent struct {
443 c *C
444 conf *agent.Conf
445 tomb tomb.Tomb
446 entity AgentState
447 tasks func() []task
448}
449
450func (m *mockAgent) Tag() string {
451 return m.entity.Tag()
452}
453
454func (m *mockAgent) Tomb() *tomb.Tomb {
455 return &m.tomb
456}
457
458func (m *mockAgent) Entity(_ *state.State) (AgentState, error) {
459 return m.entity, nil
460}
461
462func (m *mockAgent) StartTasks(_ *state.State, entity AgentState) []task {
463 m.c.Assert(entity, Equals, m.entity)
464 return m.tasks()
465}
466
467func (s *RunAgentLoopSuite) startLoop(c *C, tasks func() []task) (*tomb.Tomb, <-chan error) {
468 machine, conf, _ := s.primeMachineAgent(c, state.JobHostUnits)
469 agent := &mockAgent{
470 conf: conf,
471 entity: machine,
472 tasks: tasks,
473 }
474 done := make(chan error)
475 go func() {
476 done <- RunAgentLoop(conf, agent)
477 }()
478 return agent.Tomb(), done
479}
480
481// errorTask returns a new error each time it's run (and then panics).
482type errorTask struct {
483 c *C
484 waitCount int
485 errs []error
486}
487
488func (t *errorTask) Stop() error {
489 err := t.errs[t.waitCount]
490 t.waitCount++
491 return err
492}
493
494func (t *errorTask) Wait() error {
495 return t.errs[t.waitCount]
496}
497
498func (t *errorTask) String() string {
499 return fmt.Sprintf("%#v", t)
500}
501
502func (s *RunAgentLoopSuite) TestRetryUntilErrTerminateAgent(c *C) {
503 errTask := &errorTask{c: c, errs: []error{
504 stderrors.New("abritrary"),
505 stderrors.New("different"),
506 nil,
507 stderrors.New("another"),
508 worker.ErrTerminateAgent,
509 }}
510 _, done := s.startLoop(c, func() []task {
511 return []task{errTask}
512 })
513 select {
514 case <-time.After(500 * time.Millisecond):
515 c.Fatalf("tasks not stopped")
516 case err := <-done:
517 c.Assert(err, IsNil)
518 }
519}
520
521func (s *RunAgentLoopSuite) TestRetryUntilUpgradedError(c *C) {
522 upgradeErr := &UpgradeReadyError{}
523 errTask := &errorTask{c: c, errs: []error{
524 stderrors.New("abritrary"),
525 stderrors.New("different"),
526 nil,
527 stderrors.New("another"),
528 upgradeErr,
529 }}
530 _, done := s.startLoop(c, func() []task {
531 return []task{errTask}
532 })
533 select {
534 case <-time.After(500 * time.Millisecond):
535 c.Fatalf("tasks not stopped")
536 case err := <-done:
537 c.Assert(err, Equals, upgradeErr)
538 }
539}
540
541// waitTask is a task that "runs" until stopped and returns its error.
542type waitTask struct {
543 tomb tomb.Tomb
544 err error
545}
546
547func (t *waitTask) Stop() error {
548 t.tomb.Kill(t.err)
549 t.tomb.Done()
550 return t.Wait()
551}
552
553func (t *waitTask) Wait() error {
554 return t.tomb.Wait()
555}
556
557func (t *waitTask) String() string {
558 return fmt.Sprintf("%#v", t)
559}
560
561func (s *RunAgentLoopSuite) TestRunUntilStopped(c *C) {
562 tomb, done := s.startLoop(c, func() []task {
563 return []task{&waitTask{}}
564 })
565 tomb.Kill(stderrors.New("external"))
566 select {
567 case <-time.After(500 * time.Millisecond):
568 c.Fatalf("task not stopped")
569 case err := <-done:
570 c.Assert(err, IsNil)
571 }
572}
573
574func (s *RunAgentLoopSuite) TestRunUntilStoppedError(c *C) {
575 tomb, done := s.startLoop(c, func() []task {
576 return []task{&waitTask{err: stderrors.New("don't stop me now")}}
577 })
578 tomb.Kill(stderrors.New("external"))
579 select {
580 case <-time.After(500 * time.Millisecond):
581 c.Fatalf("task not stopped")
582 case err := <-done:
583 // It's OK, it'll be logged.
584 c.Assert(err, IsNil)
585 }
586}
402587
=== modified file 'cmd/jujud/machine.go'
--- cmd/jujud/machine.go 2013-06-18 11:04:41 +0000
+++ cmd/jujud/machine.go 2013-06-21 20:42:27 +0000
@@ -5,6 +5,9 @@
55
6import (6import (
7 "fmt"7 "fmt"
8 "path/filepath"
9 "time"
10
8 "launchpad.net/gnuflag"11 "launchpad.net/gnuflag"
9 "launchpad.net/juju-core/charm"12 "launchpad.net/juju-core/charm"
10 "launchpad.net/juju-core/cmd"13 "launchpad.net/juju-core/cmd"
@@ -13,12 +16,12 @@
13 "launchpad.net/juju-core/state"16 "launchpad.net/juju-core/state"
14 "launchpad.net/juju-core/state/apiserver"17 "launchpad.net/juju-core/state/apiserver"
15 "launchpad.net/juju-core/worker"18 "launchpad.net/juju-core/worker"
19 "launchpad.net/juju-core/worker/cleaner"
16 "launchpad.net/juju-core/worker/firewaller"20 "launchpad.net/juju-core/worker/firewaller"
17 "launchpad.net/juju-core/worker/machiner"21 "launchpad.net/juju-core/worker/machiner"
18 "launchpad.net/juju-core/worker/provisioner"22 "launchpad.net/juju-core/worker/provisioner"
23 "launchpad.net/juju-core/worker/resumer"
19 "launchpad.net/tomb"24 "launchpad.net/tomb"
20 "path/filepath"
21 "time"
22)25)
2326
24var retryDelay = 3 * time.Second27var retryDelay = 3 * time.Second
@@ -105,7 +108,8 @@
105 return err108 return err
106}109}
107110
108func (a *MachineAgent) RunOnce(st *state.State, e AgentState) error {111// StartTasks returns all tasks the machine agent is expected to perform.
112func (a *MachineAgent) StartTasks(st *state.State, e AgentState) []task {
109 m := e.(*state.Machine)113 m := e.(*state.Machine)
110 log.Infof("jobs for machine agent: %v", m.Jobs())114 log.Infof("jobs for machine agent: %v", m.Jobs())
111 dataDir := a.Conf.DataDir115 dataDir := a.Conf.DataDir
@@ -123,13 +127,14 @@
123 provisioner.NewProvisioner(st, a.MachineId),127 provisioner.NewProvisioner(st, a.MachineId),
124 firewaller.NewFirewaller(st))128 firewaller.NewFirewaller(st))
125 case state.JobManageState:129 case state.JobManageState:
126 // Ignore because it's started independently.130 tasks = append(tasks,
127 continue131 cleaner.NewCleaner(st),
132 resumer.NewResumer(st))
128 default:133 default:
129 log.Warningf("ignoring unknown job %q", j)134 log.Warningf("ignoring unknown job %q", j)
130 }135 }
131 }136 }
132 return runTasks(a.tomb.Dying(), tasks...)137 return tasks
133}138}
134139
135func (a *MachineAgent) Entity(st *state.State) (AgentState, error) {140func (a *MachineAgent) Entity(st *state.State) (AgentState, error) {
136141
=== modified file 'cmd/jujud/machine_test.go'
--- cmd/jujud/machine_test.go 2013-06-21 13:33:32 +0000
+++ cmd/jujud/machine_test.go 2013-06-21 20:42:27 +0000
@@ -5,23 +5,21 @@
55
6import (6import (
7 "fmt"7 "fmt"
8 "path/filepath"
9 "reflect"
10 "sort"
11 "sync"
12 "time"
13
8 . "launchpad.net/gocheck"14 . "launchpad.net/gocheck"
9 "launchpad.net/juju-core/charm"15 "launchpad.net/juju-core/charm"
10 "launchpad.net/juju-core/cmd"16 "launchpad.net/juju-core/cmd"
11 "launchpad.net/juju-core/constraints"
12 "launchpad.net/juju-core/environs/agent"17 "launchpad.net/juju-core/environs/agent"
13 "launchpad.net/juju-core/environs/dummy"18 "launchpad.net/juju-core/environs/dummy"
14 envtesting "launchpad.net/juju-core/environs/testing"
15 "launchpad.net/juju-core/errors"
16 "launchpad.net/juju-core/state"19 "launchpad.net/juju-core/state"
17 "launchpad.net/juju-core/state/api"20 "launchpad.net/juju-core/state/api"
18 "launchpad.net/juju-core/state/api/params"21 "launchpad.net/juju-core/state/api/params"
19 "launchpad.net/juju-core/state/watcher"
20 "launchpad.net/juju-core/testing"22 "launchpad.net/juju-core/testing"
21 "launchpad.net/juju-core/version"
22 "path/filepath"
23 "reflect"
24 "time"
25)23)
2624
27type MachineSuite struct {25type MachineSuite struct {
@@ -41,23 +39,6 @@
41 s.agentSuite.TearDownSuite(c)39 s.agentSuite.TearDownSuite(c)
42}40}
4341
44// primeAgent adds a new Machine to run the given jobs, and sets up the
45// machine agent's directory. It returns the new machine, the
46// agent's configuration and the tools currently running.
47func (s *MachineSuite) primeAgent(c *C, jobs ...state.MachineJob) (*state.Machine, *agent.Conf, *state.Tools) {
48 m, err := s.State.InjectMachine("series", constraints.Value{}, "ardbeg-0", jobs...)
49 c.Assert(err, IsNil)
50 err = m.SetMongoPassword("machine-password")
51 c.Assert(err, IsNil)
52 err = m.SetPassword("machine-api-password")
53 c.Assert(err, IsNil)
54 conf, tools := s.agentSuite.primeAgent(c, state.MachineTag(m.Id()), "machine-password")
55 conf.MachineNonce = state.BootstrapNonce
56 conf.Write()
57 c.Assert(err, IsNil)
58 return m, conf, tools
59}
60
61// newAgent returns a new MachineAgent instance42// newAgent returns a new MachineAgent instance
62func (s *MachineSuite) newAgent(c *C, m *state.Machine) *MachineAgent {43func (s *MachineSuite) newAgent(c *C, m *state.Machine) *MachineAgent {
63 a := &MachineAgent{}44 a := &MachineAgent{}
@@ -92,13 +73,13 @@
9273
93func (s *MachineSuite) TestRunInvalidMachineId(c *C) {74func (s *MachineSuite) TestRunInvalidMachineId(c *C) {
94 c.Skip("agents don't yet distinguish between temporary and permanent errors")75 c.Skip("agents don't yet distinguish between temporary and permanent errors")
95 m, _, _ := s.primeAgent(c, state.JobHostUnits)76 m, _, _ := s.primeMachineAgent(c, state.JobHostUnits)
96 err := s.newAgent(c, m).Run(nil)77 err := s.newAgent(c, m).Run(nil)
97 c.Assert(err, ErrorMatches, "some error")78 c.Assert(err, ErrorMatches, "some error")
98}79}
9980
100func (s *MachineSuite) TestRunStop(c *C) {81func (s *MachineSuite) TestRunStop(c *C) {
101 m, ac, _ := s.primeAgent(c, state.JobHostUnits)82 m, ac, _ := s.primeMachineAgent(c, state.JobHostUnits)
102 a := s.newAgent(c, m)83 a := s.newAgent(c, m)
103 done := make(chan error)84 done := make(chan error)
104 go func() {85 go func() {
@@ -111,7 +92,7 @@
111}92}
11293
113func (s *MachineSuite) TestWithDeadMachine(c *C) {94func (s *MachineSuite) TestWithDeadMachine(c *C) {
114 m, _, _ := s.primeAgent(c, state.JobHostUnits, state.JobManageState)95 m, _, _ := s.primeMachineAgent(c, state.JobHostUnits, state.JobManageState)
115 err := m.EnsureDead()96 err := m.EnsureDead()
116 c.Assert(err, IsNil)97 c.Assert(err, IsNil)
117 a := s.newAgent(c, m)98 a := s.newAgent(c, m)
@@ -126,153 +107,6 @@
126 c.Assert(err, IsNil)107 c.Assert(err, IsNil)
127}108}
128109
129func (s *MachineSuite) TestDyingMachine(c *C) {
130 m, _, _ := s.primeAgent(c, state.JobHostUnits)
131 a := s.newAgent(c, m)
132 done := make(chan error)
133 go func() {
134 done <- a.Run(nil)
135 }()
136 defer func() {
137 c.Check(a.Stop(), IsNil)
138 }()
139 time.Sleep(1 * time.Second)
140 err := m.Destroy()
141 c.Assert(err, IsNil)
142 select {
143 case err := <-done:
144 c.Assert(err, IsNil)
145 case <-time.After(watcher.Period * 5 / 4):
146 // TODO(rog) Fix this so it doesn't wait for so long.
147 // https://bugs.launchpad.net/juju-core/+bug/1163983
148 c.Fatalf("timed out waiting for agent to terminate")
149 }
150 err = m.Refresh()
151 c.Assert(err, IsNil)
152 c.Assert(m.Life(), Equals, state.Dead)
153}
154
155func (s *MachineSuite) TestHostUnits(c *C) {
156 m, conf, _ := s.primeAgent(c, state.JobHostUnits)
157 a := s.newAgent(c, m)
158 ctx, reset := patchDeployContext(c, conf.StateInfo, conf.DataDir)
159 defer reset()
160 go func() { c.Check(a.Run(nil), IsNil) }()
161 defer func() { c.Check(a.Stop(), IsNil) }()
162
163 // check that unassigned units don't trigger any deployments.
164 svc, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
165 c.Assert(err, IsNil)
166 u0, err := svc.AddUnit()
167 c.Assert(err, IsNil)
168 u1, err := svc.AddUnit()
169 c.Assert(err, IsNil)
170 ctx.waitDeployed(c)
171
172 // assign u0, check it's deployed.
173 err = u0.AssignToMachine(m)
174 c.Assert(err, IsNil)
175 ctx.waitDeployed(c, u0.Name())
176
177 // "start the agent" for u0 to prevent short-circuited remove-on-destroy;
178 // check that it's kept deployed despite being Dying.
179 err = u0.SetStatus(params.StatusStarted, "")
180 c.Assert(err, IsNil)
181 err = u0.Destroy()
182 c.Assert(err, IsNil)
183 ctx.waitDeployed(c, u0.Name())
184
185 // add u1 to the machine, check it's deployed.
186 err = u1.AssignToMachine(m)
187 c.Assert(err, IsNil)
188 ctx.waitDeployed(c, u0.Name(), u1.Name())
189
190 // make u0 dead; check the deployer recalls the unit and removes it from
191 // state.
192 err = u0.EnsureDead()
193 c.Assert(err, IsNil)
194 ctx.waitDeployed(c, u1.Name())
195 err = u0.Refresh()
196 c.Assert(errors.IsNotFoundError(err), Equals, true)
197
198 // short-circuit-remove u1 after it's been deployed; check it's recalled
199 // and removed from state.
200 err = u1.Destroy()
201 c.Assert(err, IsNil)
202 err = u1.Refresh()
203 c.Assert(errors.IsNotFoundError(err), Equals, true)
204 ctx.waitDeployed(c)
205}
206
207func (s *MachineSuite) TestManageEnviron(c *C) {
208 usefulVersion := version.Current
209 usefulVersion.Series = "series" // to match the charm created below
210 envtesting.UploadFakeToolsVersion(c, s.Conn.Environ.Storage(), usefulVersion)
211 m, _, _ := s.primeAgent(c, state.JobManageEnviron)
212 op := make(chan dummy.Operation, 200)
213 dummy.Listen(op)
214
215 a := s.newAgent(c, m)
216 // Make sure the agent is stopped even if the test fails.
217 defer a.Stop()
218 done := make(chan error)
219 go func() {
220 done <- a.Run(nil)
221 }()
222
223 // Check that the provisioner and firewaller are alive by doing
224 // a rudimentary check that it responds to state changes.
225
226 // Add one unit to a service; it should get allocated a machine
227 // and then its ports should be opened.
228 charm := s.AddTestingCharm(c, "dummy")
229 svc, err := s.State.AddService("test-service", charm)
230 c.Assert(err, IsNil)
231 err = svc.SetExposed()
232 c.Assert(err, IsNil)
233 units, err := s.Conn.AddUnits(svc, 1, "")
234 c.Assert(err, IsNil)
235 c.Check(opRecvTimeout(c, s.State, op, dummy.OpStartInstance{}), NotNil)
236
237 // Wait for the instance id to show up in the state.
238 id1, err := units[0].AssignedMachineId()
239 c.Assert(err, IsNil)
240 m1, err := s.State.Machine(id1)
241 c.Assert(err, IsNil)
242 w := m1.Watch()
243 defer w.Stop()
244 for _ = range w.Changes() {
245 err = m1.Refresh()
246 c.Assert(err, IsNil)
247 if _, ok := m1.InstanceId(); ok {
248 break
249 }
250 }
251 err = units[0].OpenPort("tcp", 999)
252 c.Assert(err, IsNil)
253
254 c.Check(opRecvTimeout(c, s.State, op, dummy.OpOpenPorts{}), NotNil)
255
256 err = a.Stop()
257 c.Assert(err, IsNil)
258
259 select {
260 case err := <-done:
261 c.Assert(err, IsNil)
262 case <-time.After(5 * time.Second):
263 c.Fatalf("timed out waiting for agent to terminate")
264 }
265}
266
267func (s *MachineSuite) TestUpgrade(c *C) {
268 m, conf, currentTools := s.primeAgent(c, state.JobManageState, state.JobManageEnviron, state.JobHostUnits)
269 addAPIInfo(conf, m)
270 err := conf.Write()
271 c.Assert(err, IsNil)
272 a := s.newAgent(c, m)
273 s.testUpgrade(c, a, currentTools)
274}
275
276func addAPIInfo(conf *agent.Conf, m *state.Machine) {110func addAPIInfo(conf *agent.Conf, m *state.Machine) {
277 port := testing.FindTCPPort()111 port := testing.FindTCPPort()
278 conf.APIInfo = &api.Info{112 conf.APIInfo = &api.Info{
@@ -292,7 +126,7 @@
292}126}
293127
294func (s *MachineSuite) TestServeAPI(c *C) {128func (s *MachineSuite) TestServeAPI(c *C) {
295 stm, conf, _ := s.primeAgent(c, state.JobManageState)129 stm, conf, _ := s.primeMachineAgent(c, state.JobManageState)
296 addAPIInfo(conf, stm)130 addAPIInfo(conf, stm)
297 err := conf.Write()131 err := conf.Write()
298 c.Assert(err, IsNil)132 c.Assert(err, IsNil)
@@ -338,7 +172,7 @@
338}}172}}
339173
340func (s *MachineSuite) TestServeAPIWithBadConf(c *C) {174func (s *MachineSuite) TestServeAPIWithBadConf(c *C) {
341 m, conf, _ := s.primeAgent(c, state.JobManageState)175 m, conf, _ := s.primeMachineAgent(c, state.JobManageState)
342 addAPIInfo(conf, m)176 addAPIInfo(conf, m)
343 for i, t := range serveAPIWithBadConfTests {177 for i, t := range serveAPIWithBadConfTests {
344 c.Logf("test %d: %q", i, t.err)178 c.Logf("test %d: %q", i, t.err)
@@ -375,9 +209,64 @@
375}209}
376210
377func (s *MachineSuite) TestChangePasswordChanging(c *C) {211func (s *MachineSuite) TestChangePasswordChanging(c *C) {
378 m, _, _ := s.primeAgent(c, state.JobHostUnits)212 m, _, _ := s.primeMachineAgent(c, state.JobHostUnits)
379 newAgent := func() runner {213 newAgent := func() runner {
380 return s.newAgent(c, m)214 return s.newAgent(c, m)
381 }215 }
382 s.testAgentPasswordChanging(c, m, newAgent)216 s.testAgentPasswordChanging(c, m, newAgent)
383}217}
218
219type jobs []state.MachineJob
220
221func (s *MachineSuite) TestStateTasks(c *C) {
222 for i, test := range []struct {
223 info string
224 jobs []state.MachineJob
225 expect []string
226 }{{
227 jobs: jobs{state.JobHostUnits},
228 expect: []string{"upgrader", "machiner", "deployer"},
229 }, {
230 jobs: jobs{state.JobManageEnviron},
231 expect: []string{"upgrader", "machiner", "provisioning worker", "firewaller"},
232 }, {
233 jobs: jobs{state.JobManageState},
234 expect: []string{"upgrader", "machiner", "cleaner", "resumer"},
235 }, {
236 jobs: jobs{state.JobManageState, state.JobHostUnits},
237 expect: []string{"upgrader", "machiner", "cleaner", "resumer", "deployer"},
238 }} {
239 c.Logf("test %d: %v", i, test.jobs)
240 machine, conf, _ := s.primeMachineAgent(c, test.jobs...)
241 expect := make([]string, len(test.expect))
242 for i, kind := range test.expect {
243 switch kind {
244 case "machiner":
245 kind = fmt.Sprintf("machiner %s", machine.Id())
246 case "deployer":
247 kind = fmt.Sprintf("deployer for machine-%s", machine.Id())
248 }
249 expect[i] = kind
250 }
251 sort.Strings(expect)
252
253 // This is horrible. But it makes this functionality somewhat testable...
254 agent := s.newAgent(c, machine)
255 agent.Conf.Conf = conf
256
257 var wg = sync.WaitGroup{}
258 var actual []string
259 for _, t := range agent.StartTasks(s.State, machine) {
260 actual = append(actual, t.String())
261 wg.Add(1)
262 go func(t task) {
263 c.Check(t.Stop(), IsNil)
264 wg.Done()
265 }(t)
266 }
267 wg.Wait()
268 sort.Strings(actual)
269 c.Logf("%#v", actual)
270 c.Check(actual, DeepEquals, expect)
271 }
272}
384273
=== modified file 'cmd/jujud/unit.go'
--- cmd/jujud/unit.go 2013-05-28 14:22:24 +0000
+++ cmd/jujud/unit.go 2013-06-21 20:42:27 +0000
@@ -66,8 +66,8 @@
66 return err66 return err
67}67}
6868
69// RunOnce runs a unit agent once.69// StartTasks returns all tasks the unit agent is expected to perform.
70func (a *UnitAgent) RunOnce(st *state.State, e AgentState) error {70func (a *UnitAgent) StartTasks(st *state.State, e AgentState) []task {
71 unit := e.(*state.Unit)71 unit := e.(*state.Unit)
72 dataDir := a.Conf.DataDir72 dataDir := a.Conf.DataDir
73 tasks := []task{73 tasks := []task{
@@ -78,7 +78,7 @@
78 tasks = append(tasks,78 tasks = append(tasks,
79 newDeployer(st, unit.WatchSubordinateUnits(), dataDir))79 newDeployer(st, unit.WatchSubordinateUnits(), dataDir))
80 }80 }
81 return runTasks(a.tomb.Dying(), tasks...)81 return tasks
82}82}
8383
84func (a *UnitAgent) Entity(st *state.State) (AgentState, error) {84func (a *UnitAgent) Entity(st *state.State) (AgentState, error) {
8585
=== modified file 'environs/testing/storage.go'
--- environs/testing/storage.go 2013-06-20 13:27:50 +0000
+++ environs/testing/storage.go 2013-06-21 20:42:27 +0000
@@ -46,7 +46,7 @@
46 listener net.Listener46 listener net.Listener
47}47}
4848
49// NewEC2HTTPTestStorage creates a storage server for tests 49// NewEC2HTTPTestStorage creates a storage server for tests
50// with the HTTPStorageReader.50// with the HTTPStorageReader.
51func NewEC2HTTPTestStorage(ip string) (*EC2HTTPTestStorage, error) {51func NewEC2HTTPTestStorage(ip string) (*EC2HTTPTestStorage, error) {
52 var err error52 var err error

Subscribers

People subscribed via source and target branches

to status/vote changes: