Merge lp:~themue/juju-core/go-worker-firewaller-machines into lp:~juju/juju-core/trunk

Proposed by Frank Mueller
Status: Merged
Approved by: Gustavo Niemeyer
Approved revision: 302
Merged at revision: 313
Proposed branch: lp:~themue/juju-core/go-worker-firewaller-machines
Merge into: lp:~juju/juju-core/trunk
Diff against target: 325 lines (+310/-0)
3 files modified
worker/firewaller/export_test.go (+16/-0)
worker/firewaller/firewaller.go (+154/-0)
worker/firewaller/firewaller_test.go (+140/-0)
To merge this branch: bzr merge lp:~themue/juju-core/go-worker-firewaller-machines
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+114938@code.launchpad.net

Description of the change

worker: started implementation of the firewaller

Based on the work of Dave and Roger this proposal implements
the first part of the firewaller worker. The major loop now
contains a machines watcher beside the environment watcher.
This machines watcher adds and removes the watchers for the
individual machine units watchers. The changes of those watchers
will be handled with the next iteration.

https://codereview.appspot.com/6374069/

To post a comment you must log in.
Revision history for this message
Roger Peppe (rogpeppe) wrote :

mostly LGTM apart from the watcher stopping below,
and the unsafe testing access.

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode42
worker/firewaller/firewaller.go:42: // Get environment first.
defer watcher.Stop(fw.machineUnitsChanges, &fw.tomb)

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode43
worker/firewaller/firewaller.go:43: fw.environWatcher =
fw.st.WatchEnvironConfig()
defer watcher.Stop(fw.environWatcher, &fw.tomb)

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode54
worker/firewaller/firewaller.go:54: return
this isn't killing the machines watcher

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode69
worker/firewaller/firewaller.go:69: return
this isn't killing the environs watcher

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode90
worker/firewaller/firewaller.go:90: // TODO(mue) fill with life.
i like the image :-)

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller_test.go
File worker/firewaller/firewaller_test.go (right):

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller_test.go#newcode100
worker/firewaller/firewaller_test.go:100: allMachines :=
fw.AllMachines()
i liked this initially, but after a moment's thought, i'm not sure about
it - it accesses fw.machines in a non-thread-safe way and it makes the
test slower than it should be.

i'll have a chat online about this - i have an idea :-)

https://codereview.appspot.com/6374069/

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

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode16
worker/firewaller/firewaller.go:16: environWatcher
*state.ConfigWatcher
Shouldn't be a field, only used inside loop().

Also, I think I mentioned this somewhere before... why do the firewaller
and the provisioner each hold independent instances of what should be
the same Environ? Given that we can (AIUI) change environ config, and
use the environ itself, safely, at any time in any goroutine, I don't
see the benefit of duplicating the
get-me-an-environ-and-keep-it-up-to-date logic.

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode17
worker/firewaller/firewaller.go:17: machinesWatcher
*state.MachinesWatcher
Shouldn't be a field, only used inside loop().

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode42
worker/firewaller/firewaller.go:42: // Get environment first.
On 2012/07/16 16:40:46, rog wrote:
> defer watcher.Stop(fw.machineUnitsChanges, &fw.tomb)

defer close(fw.machineUnitsChanges)
defer watcher.Stop(fw.machinesWatcher, &fw.tomb)

(although as stated above I don't think machinesWatcher needs to be a
field)

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode53
worker/firewaller/firewaller.go:53: }
fw.tomb.Kill(watcher.MustErr(fw.environWatcher))

(except it shouln't be a field, and IMO shouldn't even exist here)

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode68
worker/firewaller/firewaller.go:68: }
watcher.MustErr, as above.

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode75
worker/firewaller/firewaller.go:75: // case of not managed machine?
Feels like a panic situation to me.

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode86
worker/firewaller/firewaller.go:86: fw.machines[addedMachine.Id()] = m
go machine.loop()

In fact, plausibly, m := newMachine(addedMachine)

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode129
worker/firewaller/firewaller.go:129: if !ok {
fw.tomb.Kill(watcher.MustErr(m.watcher))?

https://codereview.appspot.com/6374069/

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

I think there are several lurking gotchas in the sub-goroutines. While
I'm not *certain* that my analogous implementations in
https://codereview.appspot.com/6405044/ and
https://codereview.appspot.com/6402048/ are *bulletproof*, I'm pretty
sure they ensure events happen in the right order (ie no changes after a
depart, etc), and that this code doesn't. Ping me tomorrow if you want
to talk about it, the comments are a little scattered.

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode77
worker/firewaller/firewaller.go:77: m.watcher.Stop()
Don't we need to wait until the *machine* has finished, not just the
watcher? ie calling m.watcher.Stop() here does *not* guarantee that we
won't see any more events from it on machineUnitsChanges: we actually
need to be sure that the machine.loop goroutine has exited.

I think we need tombs for the sub-goroutines, as I have in the
relation-units-watcher and relation-unit branches (although in his case
the tomb and Stop() should probably be on machine)

https://codereview.appspot.com/6374069/diff/1/worker/firewaller/firewaller.go#newcode129
worker/firewaller/firewaller.go:129: if !ok {
On 2012/07/16 23:23:59, fwereade wrote:
> fw.tomb.Kill(watcher.MustErr(m.watcher))?

Feels a bit over-familiar, but better than dropping errors on the floor.

https://codereview.appspot.com/6374069/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

This is going in a great direction. Some initial comments.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode11
worker/firewaller/firewaller.go:11: // Firewaller manages the opening
and closing of ports.
// Firewaller watches the state for ports open or closed
// and reflects those changes onto the backing environment.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode52
worker/firewaller/firewaller.go:52: if err != nil {
I believe we want to execute the line below no matter what. The err is
nil, the MustErr is supposed to explode pointing out the inconsistency.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode60
worker/firewaller/firewaller.go:60: panic("trying to remove unmanaged
machine")
"trying to remove machine that wasn't added"

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode63
worker/firewaller/firewaller.go:63: panic("can't stop machine tracker")
We can't panic here. stop can fail in pretty usual circumstances that
are outside of the application's control (e.g. network issues). This
kind of error condition must be properly managed as usual.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode105
worker/firewaller/firewaller.go:105: type machine struct {
s/machine/machineTracker/

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode115
worker/firewaller/firewaller.go:115: func newMachine(mst *state.Machine,
fw *Firewaller, changes chan *machineUnitsChange) *machine {
newMachineTracker

This is not creating a machine. Please fix docs accordingly.

Also, the channel should be qualified with <- to make it clear that this
is an output channel.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode141
worker/firewaller/firewaller.go:141: return
case <-m.tomb.Dying():

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode143
worker/firewaller/firewaller.go:143: if !ok {
Checking for ok after using the received change?

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode151
worker/firewaller/firewaller.go:151: // stop lets the machine tracker
stop working.
// stop stops the machine tracker.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode157
worker/firewaller/firewaller.go:157: type service struct {
A whole type just to enclose a bool feels like overkill.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode161
worker/firewaller/firewaller.go:161: type unit struct {
Is this a unit tracker too?

https://codereview.appspot.com/6374069/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode44
worker/firewaller/firewaller.go:44: defer close(machineUnitsChanges)
i think this line should go before the machinesWatcher stop, otherwise
we'll see eof on the channel before the firewaller has properly stopped.
maybe that doesn't matter; i'm not sure.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode52
worker/firewaller/firewaller.go:52: if err != nil {
On 2012/07/17 16:13:50, niemeyer wrote:
> I believe we want to execute the line below no matter what. The err is
nil, the
> MustErr is supposed to explode pointing out the inconsistency.

in fact, if there's EOF from the machinesWatcher, it implies that the
machines watcher has already stopped, so there's no need to call Stop.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode118
worker/firewaller/firewaller.go:118: changes: changes,
we could store this in firewaller to make it obvious that the changes
channel is shared between all machines.

https://codereview.appspot.com/6374069/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode52
worker/firewaller/firewaller.go:52: if err != nil {
On 2012/07/17 17:26:24, rog wrote:
> On 2012/07/17 16:13:50, niemeyer wrote:
> > I believe we want to execute the line below no matter what. The err
is nil,
> the
> > MustErr is supposed to explode pointing out the inconsistency.

> in fact, if there's EOF from the machinesWatcher, it implies that the
machines
> watcher has already stopped, so there's no need to call Stop.

Good point.

https://codereview.appspot.com/6374069/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

Very close to LGTM, but I'd like to see more testing of the error paths.
What happens when zookeeper goes down, for example?
And I'm still unhappy about the non-thread-safe access to firewaller
variables in the tests.

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go#newcode145
worker/firewaller/firewaller.go:145: // Had been an error, so end the
loop.
// The watcher terminated prematurely, so end the loop.

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller_test.go
File worker/firewaller/firewaller_test.go (right):

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller_test.go#newcode81
worker/firewaller/firewaller_test.go:81: c.Assert(addedMachines,
DeepEquals, allMachines)
c.Assert(allMachines, DeepEquals, addedMachines)

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller_test.go#newcode91
worker/firewaller/firewaller_test.go:91: c.Assert(addedMachines,
DeepEquals, allMachines)
c.Assert(allMachines, DeepEquals, addedMachines)

https://codereview.appspot.com/6374069/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

On 2012/07/18 12:26:39, rog wrote:
> Very close to LGTM, but I'd like to see more testing of the error
paths.
> What happens when zookeeper goes down, for example?

from IRC:

[13:32:33] <rog> TheMue: here's a thought: currently the firewaller
opens its own state. how about we change the signature to
NewFirewaller(*state.State)?
[13:33:01] <rog> TheMue: then we can pass in a state that we have a
handle to, and can close that and see what happens
[13:33:34] <rog> TheMue: in fact, that enables both the PA and the
firewaller to use the same state object, which is probably a good thing.

https://codereview.appspot.com/6374069/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

On Wed, Jul 18, 2012 at 9:35 AM, <email address hidden> wrote:
> from IRC:
>
> [13:32:33] <rog> TheMue: here's a thought: currently the firewaller
> opens its own state. how about we change the signature to
> NewFirewaller(*state.State)?
> [13:33:01] <rog> TheMue: then we can pass in a state that we have a
> handle to, and can close that and see what happens
> [13:33:34] <rog> TheMue: in fact, that enables both the PA and the
> firewaller to use the same state object, which is probably a good thing.

+1!

We should change all workers to behave like that (in their respective CLs).

--

gustavo @ http://niemeyer.net

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

One more little round.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode63
worker/firewaller/firewaller.go:63: panic("can't stop machine tracker")
On 2012/07/18 07:52:44, TheMue wrote:
> On 2012/07/17 16:13:50, niemeyer wrote:
> > We can't panic here. stop can fail in pretty usual circumstances
that are
> > outside of the application's control (e.g. network issues). This
kind of error
> > condition must be properly managed as usual.

> Changed into logging and keeping the machine tracker, so that a future
removing
> won't fail or at least the finish of the firewaller will again try to
stop it.

It will not try to stop it again, as far as I can see. You got a Removed
notice for the respective node, and it won't show up again in the
future. There's also no good reason for a machineTracker to fail to
stop, unless there's a real problem. Unless you have something else in
mind, I think this should be a real error rather than a logged message.

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode143
worker/firewaller/firewaller.go:143: if !ok {
On 2012/07/18 07:52:44, TheMue wrote:
> In case of ok == false I need to send &machineUnitsChange{m, nil}.
Doing that
> explicitely I would need the same select as regular above. This way
change is
> nil, it would be send and aferwards the loop is left.

Ok, a comment sounds fine.

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go#newcode11
worker/firewaller/firewaller.go:11: // Firewaller watches the state for
ports open or closed
Sorry, my typo: s/open/opened/

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go#newcode139
worker/firewaller/firewaller.go:139: case mt.changes <-
&machineUnitsChange{mt, change}:
What happens if mt.changes is closed?

We have to keep that kind of concern in mind when working with channels.

https://codereview.appspot.com/6374069/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go#newcode139
worker/firewaller/firewaller.go:139: case mt.changes <-
&machineUnitsChange{mt, change}:
On 2012/07/18 13:56:01, niemeyer wrote:
> What happens if mt.changes is closed?

> We have to keep that kind of concern in mind when working with
channels.

AFAICS mt.changes is the same channel as machineUnitsChanges, which
there's no reason to close ever IMO (although I see that it is closed in
fact - i'd remove that).
I suggest putting machineUnitsChanges inside the firewaller type, so
this line would be:
case mt.firewaller.machineUnitsChanges <- ...

which makes the mux logic more clear, i think.

https://codereview.appspot.com/6374069/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/8001/worker/firewaller/firewaller.go#newcode63
worker/firewaller/firewaller.go:63: panic("can't stop machine tracker")
As I mentioned on IRC, I think your approach of logging is actually
right. What's misguiding here is the comment: the machine tracker *is*
stopped if we got here. The message should look like:

"machine tracker returned error when stopping: %v"

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/12001/worker/firewaller/firewaller.go#newcode63
worker/firewaller/firewaller.go:63: delete(fw.machines,
removedMachine.Id())
This should also be moved above the if block.

https://codereview.appspot.com/6374069/

301. By Frank Mueller

firewaller: more simplification and better testing after review

302. By Frank Mueller

firewaller: small line swap

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

LGTM with the following handled:

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go#newcode21
worker/firewaller/firewaller.go:21: func NewFirewaller(st *state.State)
(*Firewaller, error) {
Nice to see how this was simplified by moving logic out.

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go#newcode33
worker/firewaller/firewaller.go:33: defer fw.finish()
Nice cleanup as well!

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go#newcode51
worker/firewaller/firewaller.go:51: log.Debugf("firewaller:
remove-machine %v", removedMachine.Id())
Either this should be dropped, or clarified to state what's being
reported. No machines are being removed here. I suggest just dropping.

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go#newcode56
worker/firewaller/firewaller.go:56: log.Debugf("firewaller: add-machine
%v", m.id)
Ditto.

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go#newcode118
worker/firewaller/firewaller.go:118: case <-mt.firewaller.tomb.Dying():
Why do we need this here? If the firewaller dies, it will stop the
tracker.

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go#newcode126
worker/firewaller/firewaller.go:126: case <-mt.firewaller.tomb.Dying():
Ditto.

https://codereview.appspot.com/6374069/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go
File worker/firewaller/firewaller.go (right):

https://codereview.appspot.com/6374069/diff/19001/worker/firewaller/firewaller.go#newcode51
worker/firewaller/firewaller.go:51: log.Debugf("firewaller:
remove-machine %v", removedMachine.Id())
On 2012/07/19 00:16:16, niemeyer wrote:
> Either this should be dropped, or clarified to state what's being
reported. No
> machines are being removed here. I suggest just dropping.

Oops. I now realize you're using this to test the logic, which sounds
fine.

Please reword it as:

"firewaller: started tracking machine %d"

and

"firewaller: stopped tracking machine %d"

https://codereview.appspot.com/6374069/

303. By Frank Mueller

firewaller: final changes and merge of trunk before submit

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'worker/firewaller'
=== added file 'worker/firewaller/export_test.go'
--- worker/firewaller/export_test.go 1970-01-01 00:00:00 +0000
+++ worker/firewaller/export_test.go 2012-07-18 16:28:20 +0000
@@ -0,0 +1,16 @@
1package firewaller
2
3// AllMachines returns the ids of all monitored machines.
4func (fw *Firewaller) AllMachines() []int {
5 all := []int{}
6 for _, machine := range fw.machines {
7 all = append(all, machine.id)
8 }
9 return all
10}
11
12// CloseState allows to close the state of the firewaller
13// externally.
14func (fw *Firewaller) CloseState() error {
15 return fw.st.Close()
16}
017
=== added file 'worker/firewaller/firewaller.go'
--- worker/firewaller/firewaller.go 1970-01-01 00:00:00 +0000
+++ worker/firewaller/firewaller.go 2012-07-18 16:28:20 +0000
@@ -0,0 +1,154 @@
1package firewaller
2
3import (
4 "launchpad.net/juju-core/log"
5 "launchpad.net/juju-core/state"
6 "launchpad.net/juju-core/state/watcher"
7 "launchpad.net/tomb"
8)
9
10// Firewaller watches the state for ports opened or closed
11// and reflects those changes onto the backing environment.
12type Firewaller struct {
13 st *state.State
14 tomb tomb.Tomb
15 machinesWatcher *state.MachinesWatcher
16 machines map[int]*machineTracker
17 machineUnitsChanges chan *machineUnitsChange
18}
19
20// NewFirewaller returns a new Firewaller.
21func NewFirewaller(st *state.State) (*Firewaller, error) {
22 fw := &Firewaller{
23 st: st,
24 machinesWatcher: st.WatchMachines(),
25 machines: make(map[int]*machineTracker),
26 machineUnitsChanges: make(chan *machineUnitsChange),
27 }
28 go fw.loop()
29 return fw, nil
30}
31
32func (fw *Firewaller) loop() {
33 defer fw.finish()
34 for {
35 select {
36 case <-fw.tomb.Dying():
37 return
38 case change, ok := <-fw.machinesWatcher.Changes():
39 if !ok {
40 return
41 }
42 for _, removedMachine := range change.Removed {
43 m, ok := fw.machines[removedMachine.Id()]
44 if !ok {
45 panic("trying to remove machine that wasn't added")
46 }
47 delete(fw.machines, removedMachine.Id())
48 if err := m.stop(); err != nil {
49 log.Printf("machine tracker %d returned error when stopping: %v", removedMachine.Id(), err)
50 }
51 log.Debugf("firewaller: remove-machine %v", removedMachine.Id())
52 }
53 for _, addedMachine := range change.Added {
54 m := newMachineTracker(addedMachine, fw)
55 fw.machines[addedMachine.Id()] = m
56 log.Debugf("firewaller: add-machine %v", m.id)
57 }
58 case <-fw.machineUnitsChanges:
59 // TODO(mue) fill with life.
60 }
61 }
62}
63
64// finishes cleans up when the firewaller is stopping.
65func (fw *Firewaller) finish() {
66 watcher.Stop(fw.machinesWatcher, &fw.tomb)
67 for _, m := range fw.machines {
68 fw.tomb.Kill(m.stop())
69 }
70 fw.tomb.Done()
71}
72
73// Wait waits for the Firewaller to exit.
74func (fw *Firewaller) Wait() error {
75 return fw.tomb.Wait()
76}
77
78// Stop stops the Firewaller and returns any error encountered while stopping.
79func (fw *Firewaller) Stop() error {
80 fw.tomb.Kill(nil)
81 return fw.tomb.Wait()
82}
83
84// machineUnitsChange contains the changed units for one specific machine.
85type machineUnitsChange struct {
86 machine *machineTracker
87 change *state.MachineUnitsChange
88}
89
90// machineTracker keeps track of the unit changes of a machine.
91type machineTracker struct {
92 tomb tomb.Tomb
93 firewaller *Firewaller
94 id int
95 watcher *state.MachineUnitsWatcher
96 ports map[state.Port]*unitTracker
97}
98
99// newMachineTracker creates a new machine tracker keeping track of
100// unit changes of the passed machine.
101func newMachineTracker(mst *state.Machine, fw *Firewaller) *machineTracker {
102 mt := &machineTracker{
103 firewaller: fw,
104 id: mst.Id(),
105 watcher: mst.WatchUnits(),
106 ports: make(map[state.Port]*unitTracker),
107 }
108 go mt.loop()
109 return mt
110}
111
112// loop is the backend watching for machine units changes.
113func (mt *machineTracker) loop() {
114 defer mt.tomb.Done()
115 defer mt.watcher.Stop()
116 for {
117 select {
118 case <-mt.firewaller.tomb.Dying():
119 return
120 case <-mt.tomb.Dying():
121 return
122 case change, ok := <-mt.watcher.Changes():
123 // Send change or nil.
124 select {
125 case mt.firewaller.machineUnitsChanges <- &machineUnitsChange{mt, change}:
126 case <-mt.firewaller.tomb.Dying():
127 return
128 case <-mt.tomb.Dying():
129 return
130 }
131 // The watcher terminated prematurely, so end the loop.
132 if !ok {
133 mt.firewaller.tomb.Kill(watcher.MustErr(mt.watcher))
134 return
135 }
136 }
137 }
138}
139
140// stop stops the machine tracker.
141func (mt *machineTracker) stop() error {
142 mt.tomb.Kill(nil)
143 return mt.tomb.Wait()
144}
145
146type unitTracker struct {
147 service *serviceTracker
148 id string
149 ports []state.Port
150}
151
152type serviceTracker struct {
153 exposed bool
154}
0155
=== added file 'worker/firewaller/firewaller_test.go'
--- worker/firewaller/firewaller_test.go 1970-01-01 00:00:00 +0000
+++ worker/firewaller/firewaller_test.go 2012-07-18 16:28:20 +0000
@@ -0,0 +1,140 @@
1package firewaller_test
2
3import (
4 "fmt"
5 . "launchpad.net/gocheck"
6 "launchpad.net/juju-core/environs/dummy"
7 "launchpad.net/juju-core/log"
8 "launchpad.net/juju-core/state/testing"
9 coretesting "launchpad.net/juju-core/testing"
10 "launchpad.net/juju-core/worker/firewaller"
11 "sort"
12 "strings"
13 stdtesting "testing"
14 "time"
15)
16
17func TestPackage(t *stdtesting.T) {
18 coretesting.ZkTestPackage(t)
19}
20
21// hooLogger allows the grabbing of debug log statements
22// to compare them inside the tests.
23type hookLogger struct {
24 event chan string
25 oldTarget log.Logger
26}
27
28var logHook *hookLogger
29
30const prefix = "JUJU:DEBUG firewaller: "
31
32func (h *hookLogger) Output(calldepth int, s string) error {
33 err := h.oldTarget.Output(calldepth, s)
34 if strings.HasPrefix(s, prefix) {
35 h.event <- s[len(prefix):]
36 }
37 return err
38}
39
40func setUpLogHook() {
41 logHook = &hookLogger{
42 event: make(chan string, 30),
43 oldTarget: log.Target,
44 }
45 log.Target = logHook
46}
47
48func tearDownLogHook() {
49 log.Target = logHook.oldTarget
50}
51
52// assertEvents asserts that the expected events are received from
53// the firewaller, in no particular order.
54func assertEvents(c *C, expect []string) {
55 var got []string
56 for _ = range expect {
57 select {
58 case e := <-logHook.event:
59 got = append(got, e)
60 case <-time.After(500 * time.Millisecond):
61 c.Fatalf("expected %q; timed out after %q", expect, got)
62 }
63 }
64 select {
65 case e := <-logHook.event:
66 got = append(got, e)
67 c.Fatalf("expected %q; too many events %q ", expect, got)
68 case <-time.After(100 * time.Millisecond):
69 }
70 sort.Strings(expect)
71 sort.Strings(got)
72 c.Assert(got, DeepEquals, expect)
73}
74
75type FirewallerSuite struct {
76 coretesting.LoggingSuite
77 testing.StateSuite
78 op <-chan dummy.Operation
79}
80
81var _ = Suite(&FirewallerSuite{})
82
83func (s *FirewallerSuite) SetUpTest(c *C) {
84 s.LoggingSuite.SetUpTest(c)
85
86 op := make(chan dummy.Operation, 500)
87 dummy.Listen(op)
88 s.op = op
89
90 s.StateSuite.SetUpTest(c)
91}
92
93func (s *FirewallerSuite) TearDownTest(c *C) {
94 dummy.Reset()
95 s.LoggingSuite.TearDownTest(c)
96}
97
98func (s *FirewallerSuite) TestStartStop(c *C) {
99 fw, err := firewaller.NewFirewaller(s.State)
100 c.Assert(err, IsNil)
101 c.Assert(fw.Stop(), IsNil)
102}
103
104func (s *FirewallerSuite) TestAddRemoveMachine(c *C) {
105 fw, err := firewaller.NewFirewaller(s.State)
106 c.Assert(err, IsNil)
107
108 setUpLogHook()
109 defer tearDownLogHook()
110
111 m1, err := s.State.AddMachine()
112 c.Assert(err, IsNil)
113 m2, err := s.State.AddMachine()
114 c.Assert(err, IsNil)
115 m3, err := s.State.AddMachine()
116 c.Assert(err, IsNil)
117
118 assertEvents(c, []string{
119 fmt.Sprint("add-machine ", m1.Id()),
120 fmt.Sprint("add-machine ", m2.Id()),
121 fmt.Sprint("add-machine ", m3.Id()),
122 })
123
124 err = s.State.RemoveMachine(m2.Id())
125 c.Assert(err, IsNil)
126
127 assertEvents(c, []string{
128 fmt.Sprint("remove-machine ", m2.Id()),
129 })
130
131 c.Assert(fw.Stop(), IsNil)
132}
133
134func (s *FirewallerSuite) TestFirewallerStopOnStateClose(c *C) {
135 fw, err := firewaller.NewFirewaller(s.State)
136 c.Assert(err, IsNil)
137 fw.CloseState()
138 c.Check(fw.Wait(), ErrorMatches, ".* zookeeper is closing")
139 c.Assert(fw.Stop(), ErrorMatches, ".* zookeeper is closing")
140}

Subscribers

People subscribed via source and target branches