Merge lp:~themue/juju-core/go-firewaller-global-mode into lp:~juju/juju-core/trunk

Proposed by Frank Mueller
Status: Rejected
Rejected by: Gustavo Niemeyer
Proposed branch: lp:~themue/juju-core/go-firewaller-global-mode
Merge into: lp:~juju/juju-core/trunk
Diff against target: 269 lines (+148/-10)
3 files modified
environs/dummy/environs.go (+45/-10)
worker/firewaller/firewaller.go (+31/-0)
worker/firewaller/firewaller_test.go (+72/-0)
To merge this branch: bzr merge lp:~themue/juju-core/go-firewaller-global-mode
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+128529@code.launchpad.net

Description of the change

firewaller: added port counter for global mode

For the global mode the firewaller now counts the
number of port usages and only opens a port once and
closes it when its usage is closed the last time.
Also found that the dummy provider not yet handles
the global mode (one test failed) and fixed that.

https://codereview.appspot.com/6635043/

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

looks good. a few remarks below.

https://codereview.appspot.com/6635043/diff/1/environs/dummy/environs.go
File environs/dummy/environs.go (right):

https://codereview.appspot.com/6635043/diff/1/environs/dummy/environs.go#newcode590
environs/dummy/environs.go:590: if inst.defaultFirewall() {
i think this would be clearer (and easier to change if we ever add
modes) as:

switch inst.e.Config().FirewallMode() {
case config.FwDefault:
    ....
case config.FwGlobal:
    ....
default:
    panic("unknown mode")
}

FWIW, i think config.FwDefault is not a great name. FwPerInstance would
make it clearer what the default behaviour is intended to be.

https://codereview.appspot.com/6635043/diff/1/environs/dummy/environs.go#newcode618
environs/dummy/environs.go:618: if inst.defaultFirewall() {
ditto

https://codereview.appspot.com/6635043/diff/1/environs/dummy/environs.go#newcode640
environs/dummy/environs.go:640: if inst.defaultFirewall() {
ditto

https://codereview.appspot.com/6635043/diff/1/environs/dummy/environs.go#newcode656
environs/dummy/environs.go:656: func (inst *instance) defaultFirewall()
bool {
i'm not sure this method pulls its weight.

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

https://codereview.appspot.com/6635043/diff/1/worker/firewaller/firewaller.go#newcode214
worker/firewaller/firewaller.go:214: // already open (for opening) and
which are still needed (for closing).
// filterGlobalPorts returns the ports that actually need
// opening and closing, given the ports that have been opened
// and closed on a particular machine.

?

https://codereview.appspot.com/6635043/diff/1/worker/firewaller/firewaller.go#newcode220
worker/firewaller/firewaller.go:220: openOut = []state.Port{}
d

https://codereview.appspot.com/6635043/diff/1/worker/firewaller/firewaller.go#newcode221
worker/firewaller/firewaller.go:221: closeOut = []state.Port{}
d

https://codereview.appspot.com/6635043/diff/1/worker/firewaller/firewaller.go#newcode224
worker/firewaller/firewaller.go:224: // Open only the first one.
// The port is not already open.

?

https://codereview.appspot.com/6635043/diff/1/worker/firewaller/firewaller.go#newcode230
worker/firewaller/firewaller.go:230: if fw.globalPorts[port] == 1 {
i'd prefer to see this after the decrement, so it's obvious we're
looking for zero.

https://codereview.appspot.com/6635043/diff/1/worker/firewaller/firewaller.go#newcode231
worker/firewaller/firewaller.go:231: // Close only the last one.
// The last reference to the port is gone,
// so close the port globally.

https://codereview.appspot.com/6635043/

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

As we talked online, the fix in dummy should be split out from this CL.
Also, can we talk a bit about the firewaller change? I don't understand
how the proposed change will solve the described issues.

https://codereview.appspot.com/6635043/

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

https://codereview.appspot.com/6635043/diff/1/environs/dummy/environs.go
File environs/dummy/environs.go (right):

https://codereview.appspot.com/6635043/diff/1/environs/dummy/environs.go#newcode590
environs/dummy/environs.go:590: if inst.defaultFirewall() {
On 2012/10/08 16:10:36, rog wrote:
> i think this would be clearer (and easier to change if we ever add
modes) as:

> switch inst.e.Config().FirewallMode() {
> case config.FwDefault:
> ....
> case config.FwGlobal:
> ....
> default:
> panic("unknown mode")
> }

> FWIW, i think config.FwDefault is not a great name. FwPerInstance
would make it
> clearer what the default behaviour is intended to be.

The reason for FwDefault is that different environments may only be able
to support a given kind of firewall. PerInstance, for example, is not
entirely true. It's per security group, and that mode is unsupported by
MaaS.

https://codereview.appspot.com/6635043/

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

We can't check for "default" within the firewaller, since the default
for different environments may be different (which is the reason why we
name it "default" rather than the actual behavior). Also, we have to
consider how to handle firewall restarts. The way it currently works,
depending on a full-blown initialization of all the firewall ports,
isn't going to scale (100k open/close ports on every restart.. sounds
bad).

https://codereview.appspot.com/6635043/

Unmerged revisions

628. By Frank Mueller

firewaller: added port counter for global mode

627. By Roger Peppe

trivial: add PasswordHash and RandomBytes

R=niemeyer
CC=
https://codereview.appspot.com/6615047

626. By Roger Peppe

cmd/jujud: make bootstrap-state accept password

This makes bootstrap-state accept all the same flags
as the agents (including --data-dir which it doesn't use;
but it seems to me that it may well use it in the future).

R=fwereade, niemeyer
CC=
https://codereview.appspot.com/6623047

625. By Roger Peppe

state: make SetAdminPassword("") idempotent

R=TheMue, niemeyer
CC=
https://codereview.appspot.com/6622047

624. By Frank Mueller

ec2: integrated firewall mode configuration

After the adding of a configuration switch for default
or global firewall mode this behavior is now integrated
in EC2. If the mode is "global", then instead of a group
per machine one group "juju-<name>-global" is created
additionally to the juju group. All machines share this
global group then and opening and closing ports on it
so effect all machines.

R=niemeyer
CC=
https://codereview.appspot.com/6589073

623. By Dave Cheney

cmd: add tests for --format=nnn

Add checks for --format=nnn flag parsing.

This will break for people who have not updated gnuflag to the latest version, this is a good thing.

R=fwereade, niemeyer
CC=
https://codereview.appspot.com/6601056

622. By William Reade

uniter: integrate filter type

By tweaking the filter slightly, to provide more carefully-tailored events,
the uniter Modes have been radically simplified.

Note that state.Unit was previously mis-specified, and is now fixed: it's
fine (in fact, potentially necessary) to resolve errors when the unit is
Dying.

Also note that there are roughly 50% more full Uniter tests than the
previous branch, but they run in roughly the same amount of time. Win!
But! This is actually a significant slowdown, because I fixed a bunch of
happy-path 500ms waits in the uniter tests. It's true that this variant does
more unnecessary work in the service of simpler Mode code, and I haven't been
able to find any obvious hotspots (apart from the pre-existing one) so if
reviewers would bear subtle performance implications in mind I would be most
grateful :).

R=rog, niemeyer
CC=
https://codereview.appspot.com/6588053

621. By Dave Cheney

cmd/jujuc: config-get: return default values

As discussed on IRC, this proposal is not the solution to the complex issue of versioned configuration settings. It is not even a start. However with this patch in place I am able to get charms running on ec2 and fix integration issues.

R=niemeyer
CC=
https://codereview.appspot.com/6591080

620. By William Reade

jujuc: move from cmd/jujuc/server

as discussed on IRC

R=niemeyer
CC=
https://codereview.appspot.com/6607043

619. By Roger Peppe

environs/config: add admin-secret attribute

R=niemeyer
CC=
https://codereview.appspot.com/6587085

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'environs/dummy/environs.go'
2--- environs/dummy/environs.go 2012-09-21 11:45:08 +0000
3+++ environs/dummy/environs.go 2012-10-08 15:45:24 +0000
4@@ -107,6 +107,7 @@
5 maxId int // maximum instance id allocated so far.
6 insts map[string]*instance
7 ports map[int]map[state.Port]bool
8+ globalPorts map[state.Port]bool
9 bootstrapped bool
10 storageDelay time.Duration
11 storage *storage
12@@ -175,10 +176,11 @@
13 // storage requests.
14 func newState(name string, ops chan<- Operation) *environState {
15 s := &environState{
16- name: name,
17- ops: ops,
18- insts: make(map[string]*instance),
19- ports: make(map[int]map[state.Port]bool),
20+ name: name,
21+ ops: ops,
22+ insts: make(map[string]*instance),
23+ ports: make(map[int]map[state.Port]bool),
24+ globalPorts: make(map[state.Port]bool),
25 }
26 s.storage = newStorage(s, "/"+name+"/private")
27 s.publicStorage = newStorage(s, "/"+name+"/public")
28@@ -472,6 +474,7 @@
29 return nil, fmt.Errorf("cannot find image for %s-%s", tools.Series, tools.Arch)
30 }
31 i := &instance{
32+ e: e,
33 state: e.state,
34 id: fmt.Sprintf("%s-%d", e.state.name, e.state.maxId),
35 ports: make(map[state.Port]bool),
36@@ -550,6 +553,7 @@
37 }
38
39 type instance struct {
40+ e *environ
41 state *environState
42 ports map[state.Port]bool
43 id string
44@@ -583,8 +587,17 @@
45 InstanceId: inst.Id(),
46 Ports: ports,
47 }
48- for _, p := range ports {
49- inst.ports[p] = true
50+ if inst.defaultFirewall() {
51+ // Default: Open per machine.
52+ for _, p := range ports {
53+ inst.ports[p] = true
54+ }
55+
56+ } else {
57+ // Global: Open globally.
58+ for _, p := range ports {
59+ inst.state.globalPorts[p] = true
60+ }
61 }
62 return nil
63 }
64@@ -602,8 +615,17 @@
65 InstanceId: inst.Id(),
66 Ports: ports,
67 }
68- for _, p := range ports {
69- delete(inst.ports, p)
70+ if inst.defaultFirewall() {
71+ // Default: Close per machine.
72+ for _, p := range ports {
73+ delete(inst.ports, p)
74+ }
75+
76+ } else {
77+ // Global: Close globally.
78+ for _, p := range ports {
79+ delete(inst.state.globalPorts, p)
80+ }
81 }
82 return nil
83 }
84@@ -615,13 +637,26 @@
85 }
86 inst.state.mu.Lock()
87 defer inst.state.mu.Unlock()
88- for p := range inst.ports {
89- ports = append(ports, p)
90+ if inst.defaultFirewall() {
91+ // Default: Return local ports.
92+ for p := range inst.ports {
93+ ports = append(ports, p)
94+ }
95+ } else {
96+ // Global: Return global ports.
97+ for p := range inst.state.globalPorts {
98+ ports = append(ports, p)
99+ }
100 }
101 state.SortPorts(ports)
102 return
103 }
104
105+// defaultFirewall tests, if the firewall mode is default.
106+func (inst *instance) defaultFirewall() bool {
107+ return inst.e.Config().FirewallMode() == config.FwDefault
108+}
109+
110 // providerDelay controls the delay before dummy responds.
111 // non empty values in JUJU_DUMMY_DELAY will be parsed as
112 // time.Durations into this value.
113
114=== modified file 'worker/firewaller/firewaller.go'
115--- worker/firewaller/firewaller.go 2012-10-01 12:29:05 +0000
116+++ worker/firewaller/firewaller.go 2012-10-08 15:45:24 +0000
117@@ -3,6 +3,7 @@
118 import (
119 "fmt"
120 "launchpad.net/juju-core/environs"
121+ "launchpad.net/juju-core/environs/config"
122 "launchpad.net/juju-core/log"
123 "launchpad.net/juju-core/state"
124 "launchpad.net/juju-core/state/watcher"
125@@ -22,6 +23,7 @@
126 unitsChange chan *unitsChange
127 unitds map[string]*unitData
128 portsChange chan *portsChange
129+ globalPorts map[state.Port]int
130 serviceds map[string]*serviceData
131 exposedChange chan *exposedChange
132 }
133@@ -36,6 +38,7 @@
134 unitsChange: make(chan *unitsChange),
135 unitds: make(map[string]*unitData),
136 portsChange: make(chan *portsChange),
137+ globalPorts: make(map[state.Port]int),
138 serviceds: make(map[string]*serviceData),
139 exposedChange: make(chan *exposedChange),
140 }
141@@ -160,6 +163,7 @@
142 }
143 toOpen := diff(want, machined.ports)
144 toClose := diff(machined.ports, want)
145+ toOpen, toClose = fw.filterGlobalPorts(toOpen, toClose)
146 machined.ports = want
147
148 // If there's nothing to do, do nothing.
149@@ -206,6 +210,33 @@
150 return nil
151 }
152
153+// filterGlobalPorts checks in case of the global firewall mode, which ports are
154+// already open (for opening) and which are still needed (for closing).
155+func (fw *Firewaller) filterGlobalPorts(openIn, closeIn []state.Port) (openOut, closeOut []state.Port) {
156+ if fw.environ.Config().FirewallMode() == config.FwDefault {
157+ return openIn, closeIn
158+ }
159+ // Global mode, so filter and count.
160+ openOut = []state.Port{}
161+ closeOut = []state.Port{}
162+ for _, port := range openIn {
163+ if fw.globalPorts[port] == 0 {
164+ // Open only the first one.
165+ openOut = append(openOut, port)
166+ }
167+ fw.globalPorts[port]++
168+ }
169+ for _, port := range closeIn {
170+ if fw.globalPorts[port] == 1 {
171+ // Close only the last one.
172+ closeOut = append(closeOut, port)
173+ delete(fw.globalPorts, port)
174+ }
175+ fw.globalPorts[port]--
176+ }
177+ return
178+}
179+
180 // machineLifeChanged starts watching new machines when the firewaller
181 // is starting, or when new machines come to life, and stops watching
182 // machines that are dying.
183
184=== modified file 'worker/firewaller/firewaller_test.go'
185--- worker/firewaller/firewaller_test.go 2012-09-28 17:40:29 +0000
186+++ worker/firewaller/firewaller_test.go 2012-10-08 15:45:24 +0000
187@@ -3,6 +3,7 @@
188 import (
189 . "launchpad.net/gocheck"
190 "launchpad.net/juju-core/environs"
191+ "launchpad.net/juju-core/environs/config"
192 "launchpad.net/juju-core/environs/dummy"
193 "launchpad.net/juju-core/juju/testing"
194 "launchpad.net/juju-core/state"
195@@ -482,3 +483,74 @@
196 err = s.State.RemoveMachine(m.Id())
197 c.Assert(err, IsNil)
198 }
199+
200+func (s *FirewallerSuite) TestGlobalMode(c *C) {
201+ // Change configuration.
202+ oldConfig := s.Conn.Environ.Config()
203+ defer func() {
204+ err := s.Conn.Environ.SetConfig(oldConfig)
205+ c.Assert(err, IsNil)
206+ err = s.State.SetEnvironConfig(oldConfig)
207+ c.Assert(err, IsNil)
208+ }()
209+
210+ attrs := s.Conn.Environ.Config().AllAttrs()
211+ attrs["firewall-mode"] = config.FwGlobal
212+ newConfig, err := s.Conn.Environ.Config().Apply(attrs)
213+ c.Assert(err, IsNil)
214+ err = s.Conn.Environ.SetConfig(newConfig)
215+ c.Assert(err, IsNil)
216+ err = s.State.SetEnvironConfig(newConfig)
217+ c.Assert(err, IsNil)
218+
219+ // Start firewall and open ports.
220+ fw := firewaller.NewFirewaller(s.State)
221+ defer func() { c.Assert(fw.Stop(), IsNil) }()
222+
223+ svc1, err := s.State.AddService("wordpress", s.charm)
224+ c.Assert(err, IsNil)
225+ err = svc1.SetExposed()
226+ c.Assert(err, IsNil)
227+
228+ u1, m1 := s.addUnit(c, svc1)
229+ inst1 := s.startInstance(c, m1)
230+ err = u1.OpenPort("tcp", 80)
231+ c.Assert(err, IsNil)
232+ err = u1.OpenPort("tcp", 8080)
233+ c.Assert(err, IsNil)
234+
235+ svc2, err := s.State.AddService("moinmoin", s.charm)
236+ c.Assert(err, IsNil)
237+ err = svc2.SetExposed()
238+ c.Assert(err, IsNil)
239+
240+ u2, m2 := s.addUnit(c, svc2)
241+ inst2 := s.startInstance(c, m2)
242+ err = u2.OpenPort("tcp", 80)
243+ c.Assert(err, IsNil)
244+
245+ // Check that both machines have the same open ports.
246+ s.assertPorts(c, inst1, m1.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})
247+ s.assertPorts(c, inst2, m2.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})
248+
249+ // Check that closing a multiple used port on one machine lets it untouched.
250+ err = u1.ClosePort("tcp", 80)
251+ c.Assert(err, IsNil)
252+
253+ s.assertPorts(c, inst1, m1.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})
254+ s.assertPorts(c, inst2, m2.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})
255+
256+ // Check that closing a single port effects all machines.
257+ err = u1.ClosePort("tcp", 8080)
258+ c.Assert(err, IsNil)
259+
260+ s.assertPorts(c, inst1, m1.Id(), []state.Port{{"tcp", 80}})
261+ s.assertPorts(c, inst2, m2.Id(), []state.Port{{"tcp", 80}})
262+
263+ // Check that closing the last usage of a port closes it globally.
264+ err = u2.ClosePort("tcp", 80)
265+ c.Assert(err, IsNil)
266+
267+ s.assertPorts(c, inst1, m1.Id(), nil)
268+ s.assertPorts(c, inst2, m2.Id(), nil)
269+}

Subscribers

People subscribed via source and target branches