Merge lp:~openstack-gd/nova/libvirt-multinic-nova into lp:~hudson-openstack/nova/trunk

Proposed by Ilya Alekseyev
Status: Merged
Approved by: Paul Voccio
Approved revision: 826
Merged at revision: 876
Proposed branch: lp:~openstack-gd/nova/libvirt-multinic-nova
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 692 lines (+260/-164)
6 files modified
nova/tests/test_virt.py (+4/-2)
nova/utils.py (+9/-5)
nova/virt/driver.py (+1/-1)
nova/virt/interfaces.template (+13/-12)
nova/virt/libvirt.xml.template (+12/-9)
nova/virt/libvirt_conn.py (+221/-135)
To merge this branch: bzr merge lp:~openstack-gd/nova/libvirt-multinic-nova
Reviewer Review Type Date Requested Status
Trey Morris (community) Needs Fixing
Paul Voccio (community) Approve
Matt Dietz (community) Approve
Nachi Ueno (community) Needs Fixing
Vish Ishaya Pending
Joshua McKenty Pending
Sandy Walsh Pending
Review via email: mp+54589@code.launchpad.net

This proposal supersedes a proposal from 2011-03-17.

Description of the change

libvirt driver multi_nic support. In this phase libvirt can work with and without multi_nic support, as in multi_nic support for xenapi: https://code.launchpad.net/~tr3buchet/nova/xs_multi_nic/+merge/53458

To post a comment you must log in.
Revision history for this message
Sandy Walsh (sandy-walsh) wrote : Posted in a previous version of this proposal

149 if not network_info:

180 if _m is unused, you can just use _

182 if network_ref['injected']:
if 'injected' in network_ref: might be safer (no KeyError if missing)

or better
if not 'injected' in network_ref:
    continue

and pull the indent in the for rest

183 do we need to make the context inside the loop? can this be done outside?

186-188 can be replaced with ra_server = network_ref.get('ra_server', "fd00::"

The 'eth##' will be incremented each time ... even if injected, is that what you wanted?

253 if not ...

263 commented out code?

264 seems to overwrite 262?

351 353 commented out code?

354 if ra_servers:

398 should be like 416

450, 459, 482 use append()?

review: Needs Fixing
Revision history for this message
Ilya Alekseyev (ilyaalekseyev) wrote : Posted in a previous version of this proposal

Sandy, thank you very much for good review. Please look at my only comment below.

> The 'eth##' will be incremented each time ... even if injected, is that what
> you wanted?
>
yes

821. By Eldar Nugaev

migration gateway_v6 to network_info

822. By Eldar Nugaev

small fix

Revision history for this message
Nachi Ueno (nati-ueno) wrote :
Download full text (136.0 KiB)

Unit test fails, would you please check?

Failure
    runTest ERROR

======================================================================
ERROR: test_403 (nova.tests.api.openstack.test_limits.WsgiLimiterProxyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/nova/libvirt-multinic-nova/nova/tests/api/openstack/test_limits.py", line 578, in test_403
    delay, error = self.proxy.check_for_delay("GET", "/delayed")
  File "/home/nova/libvirt-multinic-nova/nova/api/openstack/limits.py", line 351, in check_for_delay
    conn.request("POST", "/", body, headers)
  File "/home/nova/libvirt-multinic-nova/nova/tests/api/openstack/test_limits.py", line 510, in request
    resp = str(req.get_response(self.app))
  File "/usr/lib/pymodules/python2.6/webob/request.py", line 918, in get_response
    application, catch_exc_info=False)
  File "/usr/lib/pymodules/python2.6/webob/request.py", line 886, in call_application
    app_iter = application(self.environ, start_response)
  File "/usr/lib/pymodules/python2.6/webob/dec.py", line 159, in __call__
    return resp(environ, start_response)
  File "/usr/lib/pymodules/python2.6/webob/exc.py", line 248, in __call__
    return self.generate_response(environ, start_response)
  File "/usr/lib/pymodules/python2.6/webob/exc.py", line 239, in generate_response
    content_type=content_type
  File "/usr/lib/pymodules/python2.6/webob/response.py", line 94, in __init__
    "You cannot set the body to a unicode value without a charset")
TypeError: You cannot set the body to a unicode value without a charset

======================================================================
ERROR: test_response_to_delays (nova.tests.api.openstack.test_limits.WsgiLimiterTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/nova/libvirt-multinic-nova/nova/tests/api/openstack/test_limits.py", line 455, in test_response_to_delays
    delay = self._request("GET", "/delayed")
  File "/home/nova/libvirt-multinic-nova/nova/tests/api/openstack/test_limits.py", line 426, in _request
    response = request.get_response(self.app)
  File "/usr/lib/pymodules/python2.6/webob/request.py", line 918, in get_response
    application, catch_exc_info=False)
  File "/usr/lib/pymodules/python2.6/webob/request.py", line 886, in call_application
    app_iter = application(self.environ, start_response)
  File "/usr/lib/pymodules/python2.6/webob/dec.py", line 159, in __call__
    return resp(environ, start_response)
  File "/usr/lib/pymodules/python2.6/webob/exc.py", line 248, in __call__
    return self.generate_response(environ, start_response)
  File "/usr/lib/pymodules/python2.6/webob/exc.py", line 239, in generate_response
    content_type=content_type
  File "/usr/lib/pymodules/python2.6/webob/response.py", line 94, in __init__
    "You cannot set the body to a unicode value without a charset")
TypeError: You cannot set the body to a unicode value without a charset

======================================================================
ERROR: test_response_to_d...

Revision history for this message
Nachi Ueno (nati-ueno) wrote :

I found the versions of some libraries causes the unit test fails.
Please ignore above comment.

Revision history for this message
Andrey Brindeyev (abrindeyev) wrote :

> I found the versions of some libraries causes the unit test fails.

Nachi,

Thanks for letting us know!

Could you please document here which libraries (and their versions) caused problems on your side?

Thanks in advance, Andrey.

Revision history for this message
Nachi Ueno (nati-ueno) wrote :
Download full text (11.2 KiB)

Hi Andrey,

We need following package when we use nova.sh.
sudo apt-get install python-webob
sudo apt-get install python-novaclient
# Sorry again,this is not by your branch.

Unit test was OK.
But I can not run instances.

When I run instance, I got a error message.
2011-03-24 07:01:07,634 ERROR nova.exception [-] Uncaught exception
(nova.exception): TRACE: Traceback (most recent call last):
(nova.exception): TRACE: File "/home/nova/libvirt-multinic-nova/nova/exception.py", line 120, in _wrap
(nova.exception): TRACE: return f(*args, **kw)
(nova.exception): TRACE: File "/home/nova/libvirt-multinic-nova/nova/virt/libvirt_conn.py", line 466, in spawn
(nova.exception): TRACE: xml = self.to_xml(instance, network_info)
(nova.exception): TRACE: File "/home/nova/libvirt-multinic-nova/nova/virt/libvirt_conn.py", line 811, in to_xml
(nova.exception): TRACE: network_info = _get_network_info(instance)
(nova.exception): TRACE: File "/home/nova/libvirt-multinic-nova/nova/virt/libvirt_conn.py", line 193, in _get_network_info
(nova.exception): TRACE: 'ip6s': [ip6_dict(ip) for ip in network_ips]}
(nova.exception): TRACE: File "/home/nova/libvirt-multinic-nova/nova/virt/libvirt_conn.py", line 178, in ip6_dict
(nova.exception): TRACE: "ip": utils.to_global_ipv6(prefix, mac),
(nova.exception): TRACE: File "/home/nova/libvirt-multinic-nova/nova/utils.py", line 316, in to_global_ipv6
(nova.exception): TRACE: maskIP = netaddr.IPNetwork(prefix).ip
(nova.exception): TRACE: File "/usr/lib/pymodules/python2.6/netaddr/ip/__init__.py", line 688, in __init__
(nova.exception): TRACE: prefix, suffix = addr.split('/')
(nova.exception): TRACE: AttributeError: 'NoneType' object has no attribute 'split'
(nova.exception): TRACE:
2011-03-24 07:01:07,645 ERROR nova.compute.manager [MAD68M-0SV70DSREX1ZM admin admin] Instance '1' failed to spawn. Is virtualization enabled in the BIOS?
(nova.compute.manager): TRACE: Traceback (most recent call last):
(nova.compute.manager): TRACE: File "/home/nova/libvirt-multinic-nova/nova/compute/manager.py", line 225, in run_instance
(nova.compute.manager): TRACE: self.driver.spawn(instance_ref)
(nova.compute.manager): TRACE: File "/home/nova/libvirt-multinic-nova/nova/exception.py", line 126, in _wrap
(nova.compute.manager): TRACE: raise Error(str(e))
(nova.compute.manager): TRACE: Error: 'NoneType' object has no attribute 'split'
(nova.compute.manager): TRACE:
libvir: QEMU error : Domain not found: no domain with matching name 'instance-00000001'

I found that _get_network_info should check FLAGS.use_ipv6.

156 def _get_network_info(instance):
 157 #TODO(ilyaalekseyev) If we will keep this function
 158 # we should cache network_info
 159 admin_context = context.get_admin_context()
 160 ip_addresses = db.fixed_ip_get_all_by_instance(admin_context,
 161 instance['id'])
 162
 163 networks = db.network_get_all_by_instance(admin_context,
 164 instance['id'])
 165 network_info = []
 166
 167 def i...

review: Needs Fixing
Revision history for this message
Ilya Alekseyev (ilyaalekseyev) wrote :

Nachi, thank you much for review.
Fixing.

823. By Ilya Alekseyev

couple of bugs fixed

824. By Eldar Nugaev

pep8 clearing

825. By Ilya Alekseyev

trunk merged. conflicts resolved

Revision history for this message
Matt Dietz (cerberus) wrote :

Nachi: could you use http://paste.openstack.org/ in the future for large tracebacks or test failure lists? It would make things a lot easier to read overall

Revision history for this message
Ilya Alekseyev (ilyaalekseyev) wrote :

Hi Nachi!

We checked that VMs is starting and available via ipv4.
Thank you very much for you help.

Revision history for this message
Matt Dietz (cerberus) wrote :

I'd like to see a lot more unit tests covering this functionality. The only addition I see is you updated one test to check for a mac address. Considering the substantial change this represents, going forward untested is pretty scary. Do you think you could throw a few tests in there?

Nit-picks / Style junk:

42 + return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\
43 + format()

I'd like to see format() indented at least 8 spaces for readability's sake.

146 + #TODO(ilyaalekseyev) If we will keep this function
147 + # we should cache network_info

You should be consistent with your spacing. I'd move TODO over one space

review: Needs Fixing
826. By Ilya Alekseyev

style and spacing fixed

Revision history for this message
Eldar Nugaev (reldan) wrote :

Hi Matt.
Thank you too much for your review.

We're synchronizing our changes with Trey Morris, he's working on add multi-nic support for xen https://code.launchpad.net/~tr3buchet/nova/xs_multi_nic/+merge/53458. So we did our changes by the same way and at this moment it is not changing functionality. Existed unittest already covered our changes. But we agree with you. Let us merge our changes to the trunk before FF and create additional tests as a bug fix.

Next blueprint https://blueprints.launchpad.net/nova/+spec/nova-multi-nic contains changes in db model.

> I'd like to see a lot more unit tests covering this functionality. The only
> addition I see is you updated one test to check for a mac address. Considering
> the substantial change this represents, going forward untested is pretty
> scary. Do you think you could throw a few tests in there?
>
> Nit-picks / Style junk:
>
> 42 + return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') |
> maskIP).\
> 43 + format()
>
> I'd like to see format() indented at least 8 spaces for readability's sake.
>
> 146 + #TODO(ilyaalekseyev) If we will keep this function
> 147 + # we should cache network_info
>
> You should be consistent with your spacing. I'd move TODO over one space

Revision history for this message
Eldar Nugaev (reldan) wrote :
Revision history for this message
Matt Dietz (cerberus) wrote :

Alright Eldar, that works for me.

review: Approve
Revision history for this message
Paul Voccio (pvo) wrote :

ok

review: Approve
Revision history for this message
Trey Morris (tr3buchet) wrote :

looks good to me. You'll want to add 'rxtx_cap' and 'broadcast' to network info in _get_network_info() as in vmops.

review: Needs Fixing
Revision history for this message
Trey Morris (tr3buchet) wrote :

or it's already merged.. holy hell.

Revision history for this message
Ilya Alekseyev (ilyaalekseyev) wrote :

Thank you Trey.
I've created new bug, we will fix it.
https://bugs.launchpad.net/nova/+bug/742128

Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

Hi Ilya,

I have updated xenapi code for doing network injection, using interfaces.template.
I've seen that you populate the template in the following way:

net = str(Template(ifc_template, searchList=[{'interfaces': nets}]))

I tried to do the same thing, but Cheetah was complaining that it could not find 'use_ipv6'. I looked at the template and found out that use_ipv6 is a first-level variable:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

#for $ifc in $interfaces
auto ${ifc.name}
iface ${ifc.name} inet static
        address ${ifc.address}
        netmask ${ifc.netmask}
        broadcast ${ifc.broadcast}
        gateway ${ifc.gateway}
        dns-nameservers ${ifc.dns}

--> #if $use_ipv6 <--
iface ${ifc.name} inet6 static
    address ${ifc.address_v6}
    netmask ${ifc.netmask_v6}
    gateway ${ifc.gateway_v6}
#end if

#end for

I therefore updated my code in the following way (and it worked):

        net = str(template(template_data,
                        searchList=[{'interfaces': interfaces_info,
                                     'use_ipv6': FLAGS.use_ipv6}]))

What am I missing? Do you have any hint about why I neeeded to pass use_ipv6 in this way?

Thanks,
Salvatore

Revision history for this message
Ilya Alekseyev (ilyaalekseyev) wrote :

Hi Salvatore!

I think you did it in right way.
I kept use_ipv6 as first level variable bacause it depends on global flag.

I'll fix my code, asap. Thank you.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/tests/test_virt.py'
2--- nova/tests/test_virt.py 2011-03-24 09:52:41 +0000
3+++ nova/tests/test_virt.py 2011-03-24 20:02:31 +0000
4@@ -796,7 +796,8 @@
5
6 instance_ref = db.instance_create(self.context,
7 {'user_id': 'fake',
8- 'project_id': 'fake'})
9+ 'project_id': 'fake',
10+ 'mac_address': '00:A0:C9:14:C8:29'})
11 inst_id = instance_ref['id']
12
13 ip = '10.11.12.13'
14@@ -813,7 +814,8 @@
15 'instance_id': instance_ref['id']})
16
17 def _ensure_all_called():
18- instance_filter = 'nova-instance-%s' % instance_ref['name']
19+ instance_filter = 'nova-instance-%s-%s' % (instance_ref['name'],
20+ '00A0C914C829')
21 secgroup_filter = 'nova-secgroup-%s' % self.security_group['id']
22 for required in [secgroup_filter, 'allow-dhcp-server',
23 'no-arp-spoofing', 'no-ip-spoofing',
24
25=== modified file 'nova/utils.py'
26--- nova/utils.py 2011-03-24 18:51:38 +0000
27+++ nova/utils.py 2011-03-24 20:02:31 +0000
28@@ -311,11 +311,15 @@
29
30
31 def to_global_ipv6(prefix, mac):
32- mac64 = netaddr.EUI(mac).eui64().words
33- int_addr = int(''.join(['%02x' % i for i in mac64]), 16)
34- mac64_addr = netaddr.IPAddress(int_addr)
35- maskIP = netaddr.IPNetwork(prefix).ip
36- return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).format()
37+ try:
38+ mac64 = netaddr.EUI(mac).eui64().words
39+ int_addr = int(''.join(['%02x' % i for i in mac64]), 16)
40+ mac64_addr = netaddr.IPAddress(int_addr)
41+ maskIP = netaddr.IPNetwork(prefix).ip
42+ return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\
43+ format()
44+ except TypeError:
45+ raise TypeError(_("Bad mac for to_global_ipv6: %s") % mac)
46
47
48 def to_mac(ipv6_address):
49
50=== modified file 'nova/virt/driver.py'
51--- nova/virt/driver.py 2011-03-24 02:15:41 +0000
52+++ nova/virt/driver.py 2011-03-24 20:02:31 +0000
53@@ -61,7 +61,7 @@
54 """Return a list of InstanceInfo for all registered VMs"""
55 raise NotImplementedError()
56
57- def spawn(self, instance):
58+ def spawn(self, instance, network_info=None):
59 """Launch a VM for the specified instance"""
60 raise NotImplementedError()
61
62
63=== modified file 'nova/virt/interfaces.template'
64--- nova/virt/interfaces.template 2011-03-02 01:12:47 +0000
65+++ nova/virt/interfaces.template 2011-03-24 20:02:31 +0000
66@@ -5,19 +5,20 @@
67 auto lo
68 iface lo inet loopback
69
70-# The primary network interface
71-auto eth0
72-iface eth0 inet static
73- address ${address}
74- netmask ${netmask}
75- broadcast ${broadcast}
76- gateway ${gateway}
77- dns-nameservers ${dns}
78+#for $ifc in $interfaces
79+auto ${ifc.name}
80+iface ${ifc.name} inet static
81+ address ${ifc.address}
82+ netmask ${ifc.netmask}
83+ broadcast ${ifc.broadcast}
84+ gateway ${ifc.gateway}
85+ dns-nameservers ${ifc.dns}
86
87 #if $use_ipv6
88-iface eth0 inet6 static
89- address ${address_v6}
90- netmask ${netmask_v6}
91- gateway ${gateway_v6}
92+iface ${ifc.name} inet6 static
93+ address ${ifc.address_v6}
94+ netmask ${ifc.netmask_v6}
95+ gateway ${ifc.gateway_v6}
96 #end if
97
98+#end for
99
100=== modified file 'nova/virt/libvirt.xml.template'
101--- nova/virt/libvirt.xml.template 2011-03-02 01:12:47 +0000
102+++ nova/virt/libvirt.xml.template 2011-03-24 20:02:31 +0000
103@@ -69,21 +69,24 @@
104 </disk>
105 #end if
106 #end if
107+
108+#for $nic in $nics
109 <interface type='bridge'>
110- <source bridge='${bridge_name}'/>
111- <mac address='${mac_address}'/>
112+ <source bridge='${nic.bridge_name}'/>
113+ <mac address='${nic.mac_address}'/>
114 <!-- <model type='virtio'/> CANT RUN virtio network right now -->
115- <filterref filter="nova-instance-${name}">
116- <parameter name="IP" value="${ip_address}" />
117- <parameter name="DHCPSERVER" value="${dhcp_server}" />
118-#if $getVar('extra_params', False)
119- ${extra_params}
120+ <filterref filter="nova-instance-${name}-${nic.id}">
121+ <parameter name="IP" value="${nic.ip_address}" />
122+ <parameter name="DHCPSERVER" value="${nic.dhcp_server}" />
123+#if $getVar('nic.extra_params', False)
124+ ${nic.extra_params}
125 #end if
126-#if $getVar('gateway_v6', False)
127- <parameter name="RASERVER" value="${gateway_v6}" />
128+#if $getVar('nic.gateway_v6', False)
129+ <parameter name="RASERVER" value="${nic.gateway_v6}" />
130 #end if
131 </filterref>
132 </interface>
133+#end for
134
135 <!-- The order is significant here. File must be defined first -->
136 <serial type="file">
137
138=== modified file 'nova/virt/libvirt_conn.py'
139--- nova/virt/libvirt_conn.py 2011-03-24 17:53:54 +0000
140+++ nova/virt/libvirt_conn.py 2011-03-24 20:02:31 +0000
141@@ -153,6 +153,51 @@
142 return int(net.version())
143
144
145+def _get_network_info(instance):
146+ # TODO(adiantum) If we will keep this function
147+ # we should cache network_info
148+ admin_context = context.get_admin_context()
149+
150+ ip_addresses = db.fixed_ip_get_all_by_instance(admin_context,
151+ instance['id'])
152+
153+ networks = db.network_get_all_by_instance(admin_context,
154+ instance['id'])
155+ network_info = []
156+
157+ def ip_dict(ip):
158+ return {
159+ "ip": ip.address,
160+ "netmask": network["netmask"],
161+ "enabled": "1"}
162+
163+ def ip6_dict(ip6):
164+ prefix = ip6.network.cidr_v6
165+ mac = instance.mac_address
166+ return {
167+ "ip": utils.to_global_ipv6(prefix, mac),
168+ "netmask": ip6.network.netmask_v6,
169+ "gateway": ip6.network.gateway_v6,
170+ "enabled": "1"}
171+
172+ for network in networks:
173+ network_ips = [ip for ip in ip_addresses
174+ if ip.network_id == network.id]
175+
176+ mapping = {
177+ 'label': network['label'],
178+ 'gateway': network['gateway'],
179+ 'mac': instance.mac_address,
180+ 'dns': [network['dns']],
181+ 'ips': [ip_dict(ip) for ip in network_ips]}
182+
183+ if FLAGS.use_ipv6:
184+ mapping['ip6s'] = [ip6_dict(ip) for ip in network_ips]
185+
186+ network_info.append((network, mapping))
187+ return network_info
188+
189+
190 class LibvirtConnection(driver.ComputeDriver):
191
192 def __init__(self, read_only):
193@@ -444,16 +489,18 @@
194 def poll_rescued_instances(self, timeout):
195 pass
196
197+ # NOTE(ilyaalekseyev): Implementation like in multinics
198+ # for xenapi(tr3buchet)
199 @exception.wrap_exception
200- def spawn(self, instance):
201- xml = self.to_xml(instance)
202+ def spawn(self, instance, network_info=None):
203+ xml = self.to_xml(instance, network_info)
204 db.instance_set_state(context.get_admin_context(),
205 instance['id'],
206 power_state.NOSTATE,
207 'launching')
208- self.firewall_driver.setup_basic_filtering(instance)
209- self.firewall_driver.prepare_instance_filter(instance)
210- self._create_image(instance, xml)
211+ self.firewall_driver.setup_basic_filtering(instance, network_info)
212+ self.firewall_driver.prepare_instance_filter(instance, network_info)
213+ self._create_image(instance, xml, network_info)
214 self._conn.createXML(xml, 0)
215 LOG.debug(_("instance %s: is running"), instance['name'])
216 self.firewall_driver.apply_instance_filter(instance)
217@@ -609,7 +656,14 @@
218 utils.execute('truncate', target, '-s', "%dG" % local_gb)
219 # TODO(vish): should we format disk by default?
220
221- def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None):
222+ def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None,
223+ network_info=None):
224+ if not network_info:
225+ network_info = _get_network_info(inst)
226+
227+ if not suffix:
228+ suffix = ''
229+
230 # syntactic nicety
231 def basepath(fname='', suffix=suffix):
232 return os.path.join(FLAGS.instances_path,
233@@ -685,28 +739,35 @@
234
235 key = str(inst['key_data'])
236 net = None
237- network_ref = db.network_get_by_instance(context.get_admin_context(),
238- inst['id'])
239- if network_ref['injected']:
240- admin_context = context.get_admin_context()
241- address = db.instance_get_fixed_address(admin_context, inst['id'])
242+
243+ nets = []
244+ ifc_template = open(FLAGS.injected_network_template).read()
245+ ifc_num = -1
246+ admin_context = context.get_admin_context()
247+ for (network_ref, mapping) in network_info:
248+ ifc_num += 1
249+
250+ if not 'injected' in network_ref:
251+ continue
252+
253+ address = mapping['ips'][0]['ip']
254 address_v6 = None
255 if FLAGS.use_ipv6:
256- address_v6 = db.instance_get_fixed_address_v6(admin_context,
257- inst['id'])
258-
259- interfaces_info = {'address': address,
260- 'netmask': network_ref['netmask'],
261- 'gateway': network_ref['gateway'],
262- 'broadcast': network_ref['broadcast'],
263- 'dns': network_ref['dns'],
264- 'address_v6': address_v6,
265- 'gateway_v6': network_ref['gateway_v6'],
266- 'netmask_v6': network_ref['netmask_v6'],
267- 'use_ipv6': FLAGS.use_ipv6}
268-
269- net = str(Template(self.interfaces_xml,
270- searchList=[interfaces_info]))
271+ address_v6 = mapping['ip6s'][0]['ip']
272+ net_info = {'name': 'eth%d' % ifc_num,
273+ 'address': address,
274+ 'netmask': network_ref['netmask'],
275+ 'gateway': network_ref['gateway'],
276+ 'broadcast': network_ref['broadcast'],
277+ 'dns': network_ref['dns'],
278+ 'address_v6': address_v6,
279+ 'gateway_v6': network_ref['gateway_v6'],
280+ 'netmask_v6': network_ref['netmask_v6'],
281+ 'use_ipv6': FLAGS.use_ipv6}
282+ nets.append(net_info)
283+
284+ net = str(Template(ifc_template, searchList=[{'interfaces': nets}]))
285+
286 if key or net:
287 inst_name = inst['name']
288 img_id = inst.image_id
289@@ -728,20 +789,11 @@
290 if FLAGS.libvirt_type == 'uml':
291 utils.execute('sudo', 'chown', 'root', basepath('disk'))
292
293- def to_xml(self, instance, rescue=False):
294- # TODO(termie): cache?
295- LOG.debug(_('instance %s: starting toXML method'), instance['name'])
296- network = db.network_get_by_instance(context.get_admin_context(),
297- instance['id'])
298- # FIXME(vish): stick this in db
299- instance_type = instance['instance_type']
300- # instance_type = test.INSTANCE_TYPES[instance_type]
301- instance_type = instance_types.get_instance_type(instance_type)
302- ip_address = db.instance_get_fixed_address(context.get_admin_context(),
303- instance['id'])
304+ def _get_nic_for_xml(self, network, mapping):
305 # Assume that the gateway also acts as the dhcp server.
306 dhcp_server = network['gateway']
307 gateway_v6 = network['gateway_v6']
308+ mac_id = mapping['mac'].replace(':', '')
309
310 if FLAGS.allow_project_net_traffic:
311 if FLAGS.use_ipv6:
312@@ -766,6 +818,38 @@
313 (net, mask)
314 else:
315 extra_params = "\n"
316+
317+ result = {
318+ 'id': mac_id,
319+ 'bridge_name': network['bridge'],
320+ 'mac_address': mapping['mac'],
321+ 'ip_address': mapping['ips'][0]['ip'],
322+ 'dhcp_server': dhcp_server,
323+ 'extra_params': extra_params,
324+ }
325+
326+ if gateway_v6:
327+ result['gateway_v6'] = gateway_v6 + "/128"
328+
329+ return result
330+
331+ def to_xml(self, instance, rescue=False, network_info=None):
332+ # TODO(termie): cache?
333+ LOG.debug(_('instance %s: starting toXML method'), instance['name'])
334+
335+ # TODO(adiantum) remove network_info creation code
336+ # when multinics will be completed
337+ if not network_info:
338+ network_info = _get_network_info(instance)
339+
340+ nics = []
341+ for (network, mapping) in network_info:
342+ nics.append(self._get_nic_for_xml(network,
343+ mapping))
344+ # FIXME(vish): stick this in db
345+ instance_type_name = instance['instance_type']
346+ instance_type = instance_types.get_instance_type(instance_type_name)
347+
348 if FLAGS.use_cow_images:
349 driver_type = 'qcow2'
350 else:
351@@ -777,17 +861,11 @@
352 instance['name']),
353 'memory_kb': instance_type['memory_mb'] * 1024,
354 'vcpus': instance_type['vcpus'],
355- 'bridge_name': network['bridge'],
356- 'mac_address': instance['mac_address'],
357- 'ip_address': ip_address,
358- 'dhcp_server': dhcp_server,
359- 'extra_params': extra_params,
360 'rescue': rescue,
361 'local': instance_type['local_gb'],
362- 'driver_type': driver_type}
363+ 'driver_type': driver_type,
364+ 'nics': nics}
365
366- if gateway_v6:
367- xml_info['gateway_v6'] = gateway_v6 + "/128"
368 if not rescue:
369 if instance['kernel_id']:
370 xml_info['kernel'] = xml_info['basepath'] + "/kernel"
371@@ -800,7 +878,6 @@
372 xml = str(Template(self.libvirt_xml, searchList=[xml_info]))
373 LOG.debug(_('instance %s: finished toXML method'),
374 instance['name'])
375-
376 return xml
377
378 def get_info(self, instance_name):
379@@ -1316,7 +1393,7 @@
380
381
382 class FirewallDriver(object):
383- def prepare_instance_filter(self, instance):
384+ def prepare_instance_filter(self, instance, network_info=None):
385 """Prepare filters for the instance.
386
387 At this point, the instance isn't running yet."""
388@@ -1350,7 +1427,7 @@
389 the security group."""
390 raise NotImplementedError()
391
392- def setup_basic_filtering(self, instance):
393+ def setup_basic_filtering(self, instance, network_info=None):
394 """Create rules to block spoofing and allow dhcp.
395
396 This gets called when spawning an instance, before
397@@ -1359,11 +1436,6 @@
398 """
399 raise NotImplementedError()
400
401- def _gateway_v6_for_instance(self, instance):
402- network = db.network_get_by_instance(context.get_admin_context(),
403- instance['id'])
404- return network['gateway_v6']
405-
406
407 class NWFilterFirewall(FirewallDriver):
408 """
409@@ -1455,10 +1527,13 @@
410 </rule>
411 </filter>'''
412
413- def setup_basic_filtering(self, instance):
414+ def setup_basic_filtering(self, instance, network_info=None):
415 """Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
416 logging.info('called setup_basic_filtering in nwfilter')
417
418+ if not network_info:
419+ network_info = _get_network_info(instance)
420+
421 if self.handle_security_groups:
422 # No point in setting up a filter set that we'll be overriding
423 # anyway.
424@@ -1467,9 +1542,11 @@
425 logging.info('ensuring static filters')
426 self._ensure_static_filters()
427
428- instance_filter_name = self._instance_filter_name(instance)
429- self._define_filter(self._filter_container(instance_filter_name,
430- ['nova-base']))
431+ for (network, mapping) in network_info:
432+ nic_id = mapping['mac'].replace(':', '')
433+ instance_filter_name = self._instance_filter_name(instance, nic_id)
434+ self._define_filter(self._filter_container(instance_filter_name,
435+ ['nova-base']))
436
437 def _ensure_static_filters(self):
438 if self.static_filters_configured:
439@@ -1560,48 +1637,60 @@
440 # Nothing to do
441 pass
442
443- def prepare_instance_filter(self, instance):
444+ def prepare_instance_filter(self, instance, network_info=None):
445 """
446 Creates an NWFilter for the given instance. In the process,
447 it makes sure the filters for the security groups as well as
448 the base filter are all in place.
449 """
450+ if not network_info:
451+ network_info = _get_network_info(instance)
452 if instance['image_id'] == FLAGS.vpn_image_id:
453 base_filter = 'nova-vpn'
454 else:
455 base_filter = 'nova-base'
456
457- instance_filter_name = self._instance_filter_name(instance)
458- instance_secgroup_filter_name = '%s-secgroup' % (instance_filter_name,)
459- instance_filter_children = [base_filter, instance_secgroup_filter_name]
460+ ctxt = context.get_admin_context()
461+
462+ instance_secgroup_filter_name = \
463+ '%s-secgroup' % (self._instance_filter_name(instance))
464+ #% (instance_filter_name,)
465+
466 instance_secgroup_filter_children = ['nova-base-ipv4',
467 'nova-base-ipv6',
468 'nova-allow-dhcp-server']
469- if FLAGS.use_ipv6:
470- gateway_v6 = self._gateway_v6_for_instance(instance)
471- if gateway_v6:
472- instance_secgroup_filter_children += ['nova-allow-ra-server']
473-
474- ctxt = context.get_admin_context()
475-
476- if FLAGS.allow_project_net_traffic:
477- instance_filter_children += ['nova-project']
478- if FLAGS.use_ipv6:
479- instance_filter_children += ['nova-project-v6']
480-
481- for security_group in db.security_group_get_by_instance(ctxt,
482- instance['id']):
483+
484+ for security_group in \
485+ db.security_group_get_by_instance(ctxt, instance['id']):
486
487 self.refresh_security_group_rules(security_group['id'])
488
489 instance_secgroup_filter_children += [('nova-secgroup-%s' %
490- security_group['id'])]
491+ security_group['id'])]
492
493- self._define_filter(
494+ self._define_filter(
495 self._filter_container(instance_secgroup_filter_name,
496 instance_secgroup_filter_children))
497
498- self._define_filter(
499+ for (network, mapping) in network_info:
500+ nic_id = mapping['mac'].replace(':', '')
501+ instance_filter_name = self._instance_filter_name(instance, nic_id)
502+ instance_filter_children = \
503+ [base_filter, instance_secgroup_filter_name]
504+
505+ if FLAGS.use_ipv6:
506+ gateway_v6 = network['gateway_v6']
507+
508+ if gateway_v6:
509+ instance_secgroup_filter_children += \
510+ ['nova-allow-ra-server']
511+
512+ if FLAGS.allow_project_net_traffic:
513+ instance_filter_children += ['nova-project']
514+ if FLAGS.use_ipv6:
515+ instance_filter_children += ['nova-project-v6']
516+
517+ self._define_filter(
518 self._filter_container(instance_filter_name,
519 instance_filter_children))
520
521@@ -1649,8 +1738,10 @@
522 xml += "chain='ipv4'>%s</filter>" % rule_xml
523 return xml
524
525- def _instance_filter_name(self, instance):
526- return 'nova-instance-%s' % instance['name']
527+ def _instance_filter_name(self, instance, nic_id=None):
528+ if not nic_id:
529+ return 'nova-instance-%s' % (instance['name'])
530+ return 'nova-instance-%s-%s' % (instance['name'], nic_id)
531
532
533 class IptablesFirewallDriver(FirewallDriver):
534@@ -1665,9 +1756,11 @@
535 self.iptables.ipv6['filter'].add_chain('sg-fallback')
536 self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP')
537
538- def setup_basic_filtering(self, instance):
539+ def setup_basic_filtering(self, instance, network_info=None):
540 """Use NWFilter from libvirt for this."""
541- return self.nwfilter.setup_basic_filtering(instance)
542+ if not network_info:
543+ network_info = _get_network_info(instance)
544+ return self.nwfilter.setup_basic_filtering(instance, network_info)
545
546 def apply_instance_filter(self, instance):
547 """No-op. Everything is done in prepare_instance_filter"""
548@@ -1681,29 +1774,40 @@
549 LOG.info(_('Attempted to unfilter instance %s which is not '
550 'filtered'), instance['id'])
551
552- def prepare_instance_filter(self, instance):
553+ def prepare_instance_filter(self, instance, network_info=None):
554+ if not network_info:
555+ network_info = _get_network_info(instance)
556 self.instances[instance['id']] = instance
557- self.add_filters_for_instance(instance)
558+ self.add_filters_for_instance(instance, network_info)
559 self.iptables.apply()
560
561- def add_filters_for_instance(self, instance):
562+ def add_filters_for_instance(self, instance, network_info=None):
563+ if not network_info:
564+ network_info = _get_network_info(instance)
565 chain_name = self._instance_chain_name(instance)
566
567 self.iptables.ipv4['filter'].add_chain(chain_name)
568- ipv4_address = self._ip_for_instance(instance)
569- self.iptables.ipv4['filter'].add_rule('local',
570- '-d %s -j $%s' %
571- (ipv4_address, chain_name))
572+
573+ ips_v4 = [ip['ip'] for (_, mapping) in network_info
574+ for ip in mapping['ips']]
575+
576+ for ipv4_address in ips_v4:
577+ self.iptables.ipv4['filter'].add_rule('local',
578+ '-d %s -j $%s' %
579+ (ipv4_address, chain_name))
580
581 if FLAGS.use_ipv6:
582 self.iptables.ipv6['filter'].add_chain(chain_name)
583- ipv6_address = self._ip_for_instance_v6(instance)
584- self.iptables.ipv6['filter'].add_rule('local',
585- '-d %s -j $%s' %
586- (ipv6_address,
587- chain_name))
588-
589- ipv4_rules, ipv6_rules = self.instance_rules(instance)
590+ ips_v6 = [ip['ip'] for (_, mapping) in network_info
591+ for ip in mapping['ip6s']]
592+
593+ for ipv6_address in ips_v6:
594+ self.iptables.ipv6['filter'].add_rule('local',
595+ '-d %s -j $%s' %
596+ (ipv6_address,
597+ chain_name))
598+
599+ ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)
600
601 for rule in ipv4_rules:
602 self.iptables.ipv4['filter'].add_rule(chain_name, rule)
603@@ -1719,7 +1823,9 @@
604 if FLAGS.use_ipv6:
605 self.iptables.ipv6['filter'].remove_chain(chain_name)
606
607- def instance_rules(self, instance):
608+ def instance_rules(self, instance, network_info=None):
609+ if not network_info:
610+ network_info = _get_network_info(instance)
611 ctxt = context.get_admin_context()
612
613 ipv4_rules = []
614@@ -1733,28 +1839,36 @@
615 ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
616 ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
617
618- dhcp_server = self._dhcp_server_for_instance(instance)
619- ipv4_rules += ['-s %s -p udp --sport 67 --dport 68 '
620- '-j ACCEPT' % (dhcp_server,)]
621+ dhcp_servers = [network['gateway'] for (network, _m) in network_info]
622+
623+ for dhcp_server in dhcp_servers:
624+ ipv4_rules.append('-s %s -p udp --sport 67 --dport 68 '
625+ '-j ACCEPT' % (dhcp_server,))
626
627 #Allow project network traffic
628 if FLAGS.allow_project_net_traffic:
629- cidr = self._project_cidr_for_instance(instance)
630- ipv4_rules += ['-s %s -j ACCEPT' % (cidr,)]
631+ cidrs = [network['cidr'] for (network, _m) in network_info]
632+ for cidr in cidrs:
633+ ipv4_rules.append('-s %s -j ACCEPT' % (cidr,))
634
635 # We wrap these in FLAGS.use_ipv6 because they might cause
636 # a DB lookup. The other ones are just list operations, so
637 # they're not worth the clutter.
638 if FLAGS.use_ipv6:
639 # Allow RA responses
640- gateway_v6 = self._gateway_v6_for_instance(instance)
641- if gateway_v6:
642- ipv6_rules += ['-s %s/128 -p icmpv6 -j ACCEPT' % (gateway_v6,)]
643+ gateways_v6 = [network['gateway_v6'] for (network, _) in
644+ network_info]
645+ for gateway_v6 in gateways_v6:
646+ ipv6_rules.append(
647+ '-s %s/128 -p icmpv6 -j ACCEPT' % (gateway_v6,))
648
649 #Allow project network traffic
650 if FLAGS.allow_project_net_traffic:
651- cidrv6 = self._project_cidrv6_for_instance(instance)
652- ipv6_rules += ['-s %s -j ACCEPT' % (cidrv6,)]
653+ cidrv6s = [network['cidr_v6'] for (network, _m)
654+ in network_info]
655+
656+ for cidrv6 in cidrv6s:
657+ ipv6_rules.append('-s %s -j ACCEPT' % (cidrv6,))
658
659 security_groups = db.security_group_get_by_instance(ctxt,
660 instance['id'])
661@@ -1836,31 +1950,3 @@
662
663 def _instance_chain_name(self, instance):
664 return 'inst-%s' % (instance['id'],)
665-
666- def _ip_for_instance(self, instance):
667- return db.instance_get_fixed_address(context.get_admin_context(),
668- instance['id'])
669-
670- def _ip_for_instance_v6(self, instance):
671- return db.instance_get_fixed_address_v6(context.get_admin_context(),
672- instance['id'])
673-
674- def _dhcp_server_for_instance(self, instance):
675- network = db.network_get_by_instance(context.get_admin_context(),
676- instance['id'])
677- return network['gateway']
678-
679- def _gateway_v6_for_instance(self, instance):
680- network = db.network_get_by_instance(context.get_admin_context(),
681- instance['id'])
682- return network['gateway_v6']
683-
684- def _project_cidr_for_instance(self, instance):
685- network = db.network_get_by_instance(context.get_admin_context(),
686- instance['id'])
687- return network['cidr']
688-
689- def _project_cidrv6_for_instance(self, instance):
690- network = db.network_get_by_instance(context.get_admin_context(),
691- instance['id'])
692- return network['cidr_v6']