Merge lp:~hazmat/pyjuju/local-network into lp:pyjuju

Proposed by Kapil Thangavelu
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
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
To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

[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.

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

Very nice, +1 with some simple bits only.

[1]

+ subprocess.check_call(["virsh", "net-start", name])

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.split("\n")][2:])
+ 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.splitlines()[2:]
for line in output_lines:
    if not line.strip():
        continue
    name, state, auto = line.split()
    networks[name] = (state == "active")

[4]

+def get_network_attributes(name):

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).

review: Approve
Revision history for this message
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.check_call(["virsh", "net-start", name])
>
> 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.

Revision history for this message
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.

review: Approve
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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()))

Subscribers

People subscribed via source and target branches

to status/vote changes: