Merge lp:~hazmat/pyjuju/local-network into lp:pyjuju
- local-network
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Kapil Thangavelu | ||||
Approved revision: | 354 | ||||
Merged at revision: | 354 | ||||
Proposed branch: | lp:~hazmat/pyjuju/local-network | ||||
Merge into: | lp:pyjuju | ||||
Prerequisite: | lp:~hazmat/pyjuju/local-machine | ||||
Diff against target: |
371 lines (+362/-0) 2 files modified
juju/providers/lxc/network.py (+151/-0) juju/providers/lxc/tests/test_network.py (+211/-0) |
||||
To merge this branch: | bzr merge lp:~hazmat/pyjuju/local-network | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benjamin Saller (community) | Approve | ||
Gustavo Niemeyer | Approve | ||
William Reade (community) | Needs Fixing | ||
Review via email: mp+73616@code.launchpad.net |
Commit message
Description of the change
Gustavo Niemeyer (niemeyer) wrote : | # |
Very nice, +1 with some simple bits only.
[1]
+ subprocess.
It occurred to me that an error on these calls will pretty
often crash the whole application, because we won't catch
the exception coming out of it. Should we be trying to be
more careful about handling error situations and reporting
them instead of letting them go through, or are such errors
indeed supposed to terminate the app?
[2]
+def list_networks():
Having some sample output of the program in a comment would
make the logic much easier to follow.
[3]
+ networks_raw = filter(
+ None, [n.strip() for n in output.
+ for name, state, auto in [n.split() for n in networks_raw]:
+ networks[name] = (state == "active") and True or False
I agree regarding clarity. E.g.:
# The first two lines contain headers
output_lines = output.
for line in output_lines:
if not line.strip():
continue
name, state, auto = line.split()
networks[name] = (state == "active")
[4]
+def get_network_
Sample output would be useful as well. Would give a better idea
of what the code is looking after (e.g. why e.g. the ip is
processed the way it is).
Kapil Thangavelu (hazmat) wrote : | # |
Excerpts from Gustavo Niemeyer's message of Mon Sep 05 19:09:24 UTC 2011:
> Review: Approve
> Very nice, +1 with some simple bits only.
>
> [1]
>
> + subprocess.
>
> It occurred to me that an error on these calls will pretty
> often crash the whole application, because we won't catch
> the exception coming out of it. Should we be trying to be
> more careful about handling error situations and reporting
> them instead of letting them go through, or are such errors
> indeed supposed to terminate the app?
Indeed its terminal error. The network is done as part of the initial bootstrap
command. A failure here is terminal to any further usage of the environment, and
it seems appropriate that this bubbles up directly to the user on the cli.
i could go ahead and reuse the _cmd from the juju.lib.lxc work to give some
additional contextual information.
[2-4] Addressed, sample output included in docstrings, and additional clarity to
the parsing.
Benjamin Saller (bcsaller) wrote : | # |
The code in this branch looks fine. As we've talked about my issue is that we don't need to do all the heavy lifting that this branch currently does. Currently libvirt-bin does much/all of the networking setup and doing this by hand isn't really needed.
That said the code here may enable easier changes and better control in the future. So +1 with that reservations.
Kapil Thangavelu (hazmat) wrote : | # |
Excerpts from Benjamin Saller's message of Fri Sep 16 21:11:25 UTC 2011:
> Review: Approve
>
> The code in this branch looks fine. As we've talked about my issue is that we don't need to do all the heavy lifting that this branch currently does. Currently libvirt-bin does much/all of the networking setup and doing this by hand isn't really needed.
>
> That said the code here may enable easier changes and better control in the future. So +1 with that reservations.
Thanks. just to be clear this code defers all the heavy lifting to libvirt, its
just provides an abstraction over managing/inspecting libvirt networking
features from ensemble.. err juju ;-) I agree this is currently doing more than we
need as we're relying on the libvirt default network for the lxc provider,
instead of creating our own.
Preview Diff
1 | === added file 'juju/providers/lxc/network.py' |
2 | --- juju/providers/lxc/network.py 1970-01-01 00:00:00 +0000 |
3 | +++ juju/providers/lxc/network.py 2011-09-16 21:06:26 +0000 |
4 | @@ -0,0 +1,151 @@ |
5 | + |
6 | +import subprocess |
7 | +import tempfile |
8 | +from xml.etree import ElementTree |
9 | + |
10 | + |
11 | +from twisted.internet.defer import inlineCallbacks, returnValue |
12 | +from twisted.internet.threads import deferToThread |
13 | + |
14 | + |
15 | +libvirt_network_template = """\ |
16 | +<network> |
17 | + <name>%(name)s</name> |
18 | + <bridge name="vbr-%(name)s-%%d" /> |
19 | + <forward/> |
20 | + <ip address="192.168.%(subnet)d.1" netmask="255.255.255.0"> |
21 | + <dhcp> |
22 | + <range start="192.168.%(subnet)d.2" end="192.168.%(subnet)d.254" /> |
23 | + </dhcp> |
24 | + </ip> |
25 | +</network> |
26 | +""" |
27 | + |
28 | + |
29 | +class Network(object): |
30 | + """ Setup a bridge network with forwarding and dnsmasq for the environment. |
31 | + |
32 | + Utilizes libvirt's networking subsystem to actualize configuration. |
33 | + """ |
34 | + |
35 | + def __init__(self, name, subnet=131): |
36 | + """ |
37 | + :param name: Name of the libvirt network |
38 | + :param subnet: 192.168.x subnet to utilize |
39 | + """ |
40 | + self._name = name |
41 | + self._subnet = subnet |
42 | + |
43 | + def start(self): |
44 | + """Start the network. |
45 | + """ |
46 | + return deferToThread(start_network, self._name, self._subnet) |
47 | + |
48 | + def stop(self): |
49 | + """Stop the network. |
50 | + """ |
51 | + return deferToThread(stop_network, self._name) |
52 | + |
53 | + @inlineCallbacks |
54 | + def is_running(self): |
55 | + """Returns True if the network is currently active, False otherwise. |
56 | + """ |
57 | + networks = yield deferToThread(list_networks) |
58 | + returnValue(bool(networks.get(self._name, False))) |
59 | + |
60 | + def get_attributes(self): |
61 | + """Return attributes of the network as a dictionary. |
62 | + |
63 | + The network name, starting ip address, and bridge name are returned |
64 | + """ |
65 | + return deferToThread(get_network_attributes, self._name) |
66 | + |
67 | + |
68 | +def start_network(name, subnet): |
69 | + """Install a libvirt network for the environment. |
70 | + |
71 | + Alternatively do the nat and bridge by hand ala |
72 | + http://blog.mudy.info/2010/07/linux-container-on-amazon-ec2-server/ |
73 | + """ |
74 | + networks = list_networks() |
75 | + if name in networks: |
76 | + if networks[name]: |
77 | + return |
78 | + else: |
79 | + subprocess.check_call(["virsh", "net-start", name]) |
80 | + return |
81 | + |
82 | + libvirt_network = libvirt_network_template % ( |
83 | + dict(name=name, subnet=subnet)) |
84 | + network_file = tempfile.NamedTemporaryFile() |
85 | + network_file.write(libvirt_network) |
86 | + network_file.flush() |
87 | + |
88 | + subprocess.check_call(["virsh", "net-define", network_file.name]) |
89 | + network_file.close() |
90 | + subprocess.check_call(["virsh", "net-start", name]) |
91 | + |
92 | + |
93 | +def list_networks(): |
94 | + """Return a dictionary of network name to active status bools. |
95 | + |
96 | + Sample virsh net-list output:: |
97 | + |
98 | +Name State Autostart |
99 | +----------------------------------------- |
100 | +default active yes |
101 | +juju-test inactive no |
102 | +foobar inactive no |
103 | + |
104 | + Parsing the above would return:: |
105 | + {"default": True, "juju-test": False, "foobar": False} |
106 | + """ |
107 | + output = subprocess.check_output(["virsh", "net-list", "--all"]) |
108 | + networks = {} |
109 | + # Take the header off and normalize whitespace. |
110 | + net_lines = [n.strip() for n in output.splitlines()[2:]] |
111 | + for line in net_lines: |
112 | + if not line: |
113 | + continue |
114 | + name, state, auto = line.split() |
115 | + networks[name] = state == "active" |
116 | + return networks |
117 | + |
118 | + |
119 | +def get_network_attributes(name): |
120 | + """Return core attributes of a libvirt network. |
121 | + |
122 | + :param name: Name of the libvirt network. |
123 | + |
124 | + returns a dictionary containing the name, ip,, and bridge name. |
125 | + |
126 | + Sample virsh net-dumpxml output:: |
127 | +<network> |
128 | + <name>default</name> |
129 | + <uuid>56fb682c-26a8-3645-5e47-b77ce33eefc6</uuid> |
130 | + <forward mode='nat'/> |
131 | + <bridge name='virbr0' stp='on' delay='0' /> |
132 | + <ip address='192.168.122.1' netmask='255.255.255.0'> |
133 | + <dhcp> |
134 | + <range start='192.168.122.2' end='192.168.122.254' /> |
135 | + </dhcp> |
136 | + </ip> |
137 | +</network> |
138 | + """ |
139 | + output = subprocess.check_output(["virsh", "net-dumpxml", name]) |
140 | + attrs = {} |
141 | + etree = ElementTree.fromstring(output) |
142 | + attrs["name"] = etree.findtext("name") |
143 | + attrs["ip"] = etree.find("ip").attrib |
144 | + attrs["bridge"] = etree.find("bridge").attrib["name"] |
145 | + return attrs |
146 | + |
147 | + |
148 | +def stop_network(name): |
149 | + """Stop the named libvirt network. |
150 | + """ |
151 | + networks = list_networks() |
152 | + status = networks.get(name) |
153 | + if not status: |
154 | + return |
155 | + subprocess.check_call(["virsh", "net-stop", name]) |
156 | |
157 | === added file 'juju/providers/lxc/tests/test_network.py' |
158 | --- juju/providers/lxc/tests/test_network.py 1970-01-01 00:00:00 +0000 |
159 | +++ juju/providers/lxc/tests/test_network.py 2011-09-16 21:06:26 +0000 |
160 | @@ -0,0 +1,211 @@ |
161 | +import subprocess |
162 | + |
163 | +from twisted.internet.defer import inlineCallbacks |
164 | + |
165 | +from juju.providers.lxc.network import Network |
166 | +from juju.lib.testing import TestCase |
167 | +from juju.lib.mocker import MATCH |
168 | + |
169 | +# Captured raw output from virsh net-list |
170 | +network_list_default_output = """\ |
171 | +Name State Autostart |
172 | +----------------------------------------- |
173 | +default active yes |
174 | + |
175 | +""" |
176 | + |
177 | +# Parameterized output for test case |
178 | +network_list_output_template = """\ |
179 | +Name State Autostart |
180 | +----------------------------------------- |
181 | +default active yes |
182 | +%(name)s %(state)s %(autostart)s |
183 | + |
184 | +""" |
185 | + |
186 | +network_dump_xml_default_output = """\ |
187 | +<network> |
188 | + <name>default</name> |
189 | + <uuid>56fb682c-26a8-3645-5e47-b77ce33eefc6</uuid> |
190 | + <forward mode='nat'/> |
191 | + <bridge name='virbr0' stp='on' delay='0' /> |
192 | + <ip address='192.168.122.1' netmask='255.255.255.0'> |
193 | + <dhcp> |
194 | + <range start='192.168.122.2' end='192.168.122.254' /> |
195 | + </dhcp> |
196 | + </ip> |
197 | +</network> |
198 | +""" |
199 | + |
200 | + |
201 | +class NetworkTestCase(TestCase): |
202 | + |
203 | + # Mocked tests against the network class |
204 | + |
205 | + @inlineCallbacks |
206 | + def test_get_network_attributes(self): |
207 | + network_dumpxml_mock = self.mocker.mock() |
208 | + self.patch(subprocess, "check_output", network_dumpxml_mock) |
209 | + network_dumpxml_mock(["virsh", "net-dumpxml", "default"]) |
210 | + self.mocker.result(network_dump_xml_default_output) |
211 | + self.mocker.replay() |
212 | + |
213 | + network = Network("default") |
214 | + attributes = yield network.get_attributes() |
215 | + self.assertEqual( |
216 | + dict(name="default", |
217 | + ip=dict(address="192.168.122.1", netmask="255.255.255.0"), |
218 | + bridge="virbr0"), |
219 | + attributes) |
220 | + |
221 | + @inlineCallbacks |
222 | + def test_start(self): |
223 | + network_list_mock = self.mocker.mock() |
224 | + network_start_mock = self.mocker.mock() |
225 | + |
226 | + def check_definition(args): |
227 | + cmd, subcommand, template_path = args |
228 | + network_definition = open(template_path).read() |
229 | + self.assertIn( |
230 | + '<name>foobar</name>', network_definition) |
231 | + self.assertIn( |
232 | + '<bridge name="vbr-foobar-%d" />', network_definition) |
233 | + self.assertIn( |
234 | + '<range start="192.168.131.2" end="192.168.131.254" />', |
235 | + network_definition) |
236 | + self.assertIn( |
237 | + '<ip address="192.168.131.1" netmask="255.255.255.0">', |
238 | + network_definition) |
239 | + return True |
240 | + |
241 | + self.patch(subprocess, "check_output", network_list_mock) |
242 | + self.patch(subprocess, "check_call", network_start_mock) |
243 | + |
244 | + network_list_mock(["virsh", "net-list", "--all"]) |
245 | + self.mocker.result(network_list_default_output) |
246 | + |
247 | + network_start_mock(MATCH(check_definition)) |
248 | + network_start_mock(["virsh", "net-start", "foobar"]) |
249 | + self.mocker.replay() |
250 | + |
251 | + network = Network("foobar") |
252 | + yield network.start() |
253 | + |
254 | + @inlineCallbacks |
255 | + def test_start_stoppped_network(self): |
256 | + network_list_mock = self.mocker.mock() |
257 | + self.patch(subprocess, "check_output", network_list_mock) |
258 | + network_list_mock(["virsh", "net-list", "--all"]) |
259 | + |
260 | + self.mocker.result( |
261 | + network_list_output_template % dict( |
262 | + name="juju", state="inactive", autostart="no")) |
263 | + |
264 | + network_start_mock = self.mocker.mock() |
265 | + self.patch(subprocess, "check_call", network_start_mock) |
266 | + network_start_mock(["virsh", "net-start", "juju"]) |
267 | + self.mocker.replay() |
268 | + |
269 | + network = Network("juju") |
270 | + yield network.start() |
271 | + |
272 | + @inlineCallbacks |
273 | + def test_start_started_network(self): |
274 | + network_list_mock = self.mocker.mock() |
275 | + self.patch(subprocess, "check_output", network_list_mock) |
276 | + network_list_mock(["virsh", "net-list", "--all"]) |
277 | + |
278 | + self.mocker.result( |
279 | + network_list_output_template % dict( |
280 | + name="zoo", state="active", autostart="no")) |
281 | + |
282 | + # patch to catch any errant calls |
283 | + network_start_mock = self.mocker.mock() |
284 | + self.patch(subprocess, "check_call", network_start_mock) |
285 | + self.mocker.replay() |
286 | + network = Network("zoo") |
287 | + yield network.start() |
288 | + |
289 | + @inlineCallbacks |
290 | + def test_stop(self): |
291 | + network_list_mock = self.mocker.mock() |
292 | + network_stop_mock = self.mocker.mock() |
293 | + self.patch(subprocess, "check_output", network_list_mock) |
294 | + self.patch(subprocess, "check_call", network_stop_mock) |
295 | + |
296 | + network_list_mock(["virsh", "net-list", "--all"]) |
297 | + self.mocker.result( |
298 | + network_list_output_template % dict( |
299 | + name="zoo", state="active", autostart="no")) |
300 | + |
301 | + network_stop_mock(["virsh", "net-stop", "zoo"]) |
302 | + self.mocker.replay() |
303 | + |
304 | + network = Network("zoo") |
305 | + yield network.stop() |
306 | + |
307 | + @inlineCallbacks |
308 | + def test_stop_stopped_network(self): |
309 | + network_list_mock = self.mocker.mock() |
310 | + self.patch(subprocess, "check_output", network_list_mock) |
311 | + network_list_mock(["virsh", "net-list", "--all"]) |
312 | + |
313 | + self.mocker.result( |
314 | + network_list_output_template % dict( |
315 | + name="zoo", state="inactive", autostart="no")) |
316 | + |
317 | + # patch to catch any errant calls |
318 | + network_stop_mock = self.mocker.mock() |
319 | + self.patch(subprocess, "check_call", network_stop_mock) |
320 | + self.mocker.replay() |
321 | + |
322 | + network = Network("zoo") |
323 | + yield network.stop() |
324 | + |
325 | + @inlineCallbacks |
326 | + def test_stop_nonexistent_network(self): |
327 | + network_list_mock = self.mocker.mock() |
328 | + self.patch(subprocess, "check_output", network_list_mock) |
329 | + network_list_mock(["virsh", "net-list", "--all"]) |
330 | + |
331 | + self.mocker.result( |
332 | + network_list_output_template % dict( |
333 | + name="zoo", state="inactive", autostart="no")) |
334 | + |
335 | + # patch to catch any errant calls |
336 | + network_stop_mock = self.mocker.mock() |
337 | + self.patch(subprocess, "check_call", network_stop_mock) |
338 | + self.mocker.replay() |
339 | + |
340 | + network = Network("zebra") |
341 | + yield network.stop() |
342 | + |
343 | + @inlineCallbacks |
344 | + def test_is_running(self): |
345 | + network_list_mock = self.mocker.mock() |
346 | + self.patch(subprocess, "check_output", network_list_mock) |
347 | + |
348 | + # Network on |
349 | + network_list_mock(["virsh", "net-list", "--all"]) |
350 | + self.mocker.result( |
351 | + network_list_output_template % ( |
352 | + dict(name="foobar", state="active", autostart="off"))) |
353 | + |
354 | + # Network off |
355 | + network_list_mock(["virsh", "net-list", "--all"]) |
356 | + self.mocker.result( |
357 | + network_list_output_template % ( |
358 | + dict(name="foobar", state="inactive", autostart="off"))) |
359 | + |
360 | + # Non existent |
361 | + network_list_mock(["virsh", "net-list", "--all"]) |
362 | + self.mocker.result( |
363 | + network_list_output_template % ( |
364 | + dict(name="magic", state="inactive", autostart="off"))) |
365 | + |
366 | + self.mocker.replay() |
367 | + |
368 | + network = Network("foobar") |
369 | + self.assertTrue((yield network.is_running())) |
370 | + self.assertFalse((yield network.is_running())) |
371 | + self.assertFalse((yield network.is_running())) |
[0]
+ networks[name] = (state == "active") and True or False
Isn't that equivalent to:
+ networks[name] = (state == "active")
?
[1]
Nice tests; s/nonexistant/ nonexistent/ .
[2]
+def start_network(name, subnet):
I feel this function could be rearranged a little for clarity.
[3]
FAILED (skips=5, failures=3, errors=92, successes=1346)
Many due to "'FileStorage' object has no attribute 'get_url'", as noted in https:/ /code.launchpad .net/~hazmat/ ensemble/ lib-files/ +merge/ 73444, but it doesn't look like that's all of them.