Merge lp:~citrix-openstack/nova/xenapi-vlan-network-manager into lp:~hudson-openstack/nova/trunk

Proposed by Salvatore Orlando
Status: Merged
Approved by: Matt Dietz
Approved revision: 705
Merged at revision: 906
Proposed branch: lp:~citrix-openstack/nova/xenapi-vlan-network-manager
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 1378 lines (+429/-226)
9 files modified
nova/network/linux_net.py (+0/-3)
nova/network/manager.py (+2/-0)
nova/network/xenapi_net.py (+85/-0)
nova/tests/db/fakes.py (+44/-21)
nova/tests/fake_utils.py (+10/-7)
nova/tests/test_xenapi.py (+54/-45)
nova/virt/xenapi/fake.py (+107/-65)
nova/virt/xenapi/network_utils.py (+17/-2)
nova/virt/xenapi/vmops.py (+110/-83)
To merge this branch: bzr merge lp:~citrix-openstack/nova/xenapi-vlan-network-manager
Reviewer Review Type Date Requested Status
termie (community) Needs Fixing
Trey Morris (community) Approve
Thierry Carrez (community) ffe Approve
Matt Dietz (community) Approve
Review via email: mp+53660@code.launchpad.net

Commit message

Added VLAN networking support for XenAPI

Description of the change

The branch proposed for merged implements the xenapi-vlan-network-manager blueprint (https://blueprints.launchpad.net/nova/+spec/xenapi-vlan-network-manager).

It extends VLAN network manager supports to compute nodes running xenapi.
To do so, we create a xenapi specific network driver, which implements only ensure_vlan_bridge.
This way, when the compute node invokes NetworkManager.setup_compute_network, the appropriate VLAN configuration is performed using XenAPI.

Note: the network node is not affected at all by this change, and it should therefore be executed using the linux_net driver as usual. The compute node should be executed with the xenapi_net driver instead.

To post a comment you must log in.
Revision history for this message
Matt Dietz (cerberus) wrote :

I merged with trunk and got a conflict in network/manager.py

<<<<<<< TREE
flags.DEFINE_string('vlan_interface', 'eth0',
                    'network device for vlans')
flags.DEFINE_integer('num_networks', 1000, 'Number of networks to support')
=======
flags.DEFINE_integer('num_networks', 1, 'Number of networks to support')
>>>>>>> MERGE-SOURCE

Also had a unit-test error in one of the test files, so the rest of the XenAPI tests aren't currently running. Forget a bzr add?

======================================================================
ERROR: Failure: ImportError (cannot import name fake_utils)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.6/dist-packages/nose/loader.py", line 390, in loadTestsFromName
    addr.filename, addr.module)
  File "/usr/local/lib/python2.6/dist-packages/nose/importer.py", line 39, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/local/lib/python2.6/dist-packages/nose/importer.py", line 86, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/root/code/nova/xenapi-vlan-network-manager/nova/tests/test_xenapi.py", line 41, in <module>
    from nova.tests import fake_utils
ImportError: cannot import name fake_utils

----------------------------------------------------------------------

36) Shouldn't this be the citrix copyright?

623-632) this seems really odd to me. What's an example of how we would actually fail into the exception block?

Tiny nits:

116 + "Expected %(vlan_num)d") % locals())

You might want a space before Expected or it will run up against the period from the previous sentence

review: Needs Fixing
Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :
Download full text (3.4 KiB)

Hi Matt,
thanks for reviewing this branch

> I merged with trunk and got a conflict in network/manager.py
>
> <<<<<<< TREE
> flags.DEFINE_string('vlan_interface', 'eth0',
> 'network device for vlans')
> flags.DEFINE_integer('num_networks', 1000, 'Number of networks to support')
> =======
> flags.DEFINE_integer('num_networks', 1, 'Number of networks to support')
> >>>>>>> MERGE-SOURCE
>

I guess the conflict has arisen from a recent merge (the one for solving a bug in nova-manage). I will merge my branch with trunk, and push again...

> Also had a unit-test error in one of the test files, so the rest of the XenAPI
> tests aren't currently running. Forget a bzr add?

...and when pushing, this time I will not forget to add fake_utils.py!

>
> ======================================================================
> ERROR: Failure: ImportError (cannot import name fake_utils)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
> File "/usr/local/lib/python2.6/dist-packages/nose/loader.py", line 390, in
> loadTestsFromName
> addr.filename, addr.module)
> File "/usr/local/lib/python2.6/dist-packages/nose/importer.py", line 39, in
> importFromPath
> return self.importFromDir(dir_path, fqname)
> File "/usr/local/lib/python2.6/dist-packages/nose/importer.py", line 86, in
> importFromDir
> mod = load_module(part_fqname, fh, filename, desc)
> File "/root/code/nova/xenapi-vlan-network-
> manager/nova/tests/test_xenapi.py", line 41, in <module>
> from nova.tests import fake_utils
> ImportError: cannot import name fake_utils
>
> ----------------------------------------------------------------------
>
> 36) Shouldn't this be the citrix copyright?
>

Yes, it should. Shame on me...

> 623-632) this seems really odd to me. What's an example of how we would
> actually fail into the exception block?
>

I agree this try/except block is quite weird.
The main reason is that find_network_with_bridge expects to find a network on the Xen backend whose attribute 'bridge' matches the 'bridge' column in the nova db or the FLAGS.flat_network_bridge flag.
However, while this is true for flat networking (in which we tipically set the flag to xenbr0 or something like that), this does not work anymore with VLAN networks. In XenAPI names for bridges associated with VLAN networks are auto-generated. For instance, for VLAN 111 it will not be possible to call the bridge br111, but a name like xapiXX (where XX is an integer) will be generated.
To solve this problem, when we create a VLAN network in XenAPI, we set its 'name' attribute to the value of the 'bridge' column in the name db. We then lookup for the appropriate network by looking at the 'name' attribute instead of the 'bridge' one.

The try/except block first tries to identify the network by bridge, and then, if it fails, tries with the 'name' attribute. I agree however that this does not look good at all. I will therefore shift the burden of the bridge/name lookup into NetworkHelper, implementing a method that looks for both properties and then returns the first network for which either the 'bridge' or the 'nam...

Read more...

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

The branch has been updated and a new round of review is kindly asked.
Matt's comments have been addressed as follows:
- resolved conflicts with trunk (this does not exclude new conflicts in the future, though!)
- added missing fake_utils.py file
- changed copyright header in xenapi_net.py
- removed exception block in create_vif, changed find_network_with_bridge to deal with both bridge and name_label attributes
- Fixed missing whitespace problem with debug line

Also, the unit test for spawning VM on VLAN network has been slightly changed to reflect changes in the code

689. By Salvatore Orlando

removed excess debug line

690. By Salvatore Orlando

merge trunk

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

lgtm.

review: Approve
691. By Salvatore Orlando

merge trunk

692. By Salvatore Orlando

merge trunk

693. By Salvatore Orlando

merge trunk

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

Requesting FF exception for this branch, as it has already 1 approve vote and there's no major dispute on the way it is implemented.

The branch touches two areas:
- VM spawn procedure for xenapi backend (thoroughly tested)
- Network driver for xenapi backend (new area - a test has been added in test_xenapi which offers good code coverage)

Benefit:
- this branch will enable support the Vlan Network manager on XenAPI backends. This support has also been implemented for VSphere backends in a branch that was merged yesterday.

Risk assessment:
- No risk for network manager, as its code has not been touched at all. The xenapi network driver will be used only by the compute node for setting up VLANs on xen hosts. This also means that there cannot be any impact on other capabilities of the Network Manager, such as VPN access or Floating IPs.
- Since this is a new feature, and the associated code is executed only when a compute node uses XenAPI and the VLan manager, there is no risk of breaking libvirt, hyperv, or mware installations, or XenAPI installations which use the flat manager

694. By Salvatore Orlando

merge trunk

Revision history for this message
Thierry Carrez (ttx) wrote :

This is also a bit large, but relatively self-contained. It needs to get merged soon, FFe granted for merging by Tuesday evening.

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

Looks good. Unless I'm missing something multi-nic and this should get along just fine unless you want to have vlans on multiple interfaces, in which case setting the vlan_interface with a flag isnt'a good idea. The flat_network_bridge flag is going away with multi-nic, perhaps vlan_interface will need to go away too?

approve for now but we should probably discuss.

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

214 + def fake_network_get_all_by_instance(context, instance_id):
215 + l = []
216 + #even instance numbers are on vlan networks
217 + if instance_id % 2 == 0:
218 + l.append(FakeModel(vlan_network_fields))
219 + else:
220 + l.append(FakeModel(flat_network_fields))
221 + return l

missed this... please don't use 'l' as a variable name (lower case L).

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

as this is down to the wire...
if I'm asleep or not around when 'l' is fixed, as that's all my approval is hinged upon, feel free to approve out from under me or whatever to get it in

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

> as this is down to the wire...
> if I'm asleep or not around when 'l' is fixed, as that's all my approval is
> hinged upon, feel free to approve out from under me or whatever to get it in

Hi Trey, I'm going to update the 'l' thing right now.

As regards vlan_interface, its semantics are probably different from flat_network_bridge.
flat_network_bridge tells (or least it used to tell!) nova-compute which bridge should be used for VIFs. Of course, with multiple VIFs per VM, this does not make sense anymore.
vlan_interface instead represents the physical interface on the hypervisor we will use for creating the bridge for the vlan network.
In libvirt this means using vconfig to create a vlan on vlan_interface, create a new bridge, and then attach the vlan interface to this bridge; the virtual interface is then attached to newly created bridge.
In xenapi instead this means using xapi to create a network, retrieve the PIF associated with vlan_interface and then create a VLAN object using the network and the PIF. The VIF is then attached to the newly created network.

Summarizing I think that after multi-nic we will still need vlan_interface. What we will need to discuss is probably the way in which we create these bridges. Currently the nova-compute node does that, probably we want to let the network service do all networking-related arrangements.

695. By Salvatore Orlando

merge trunk
addressing Trey's comments

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

Branch updated.
Lowercase 'L' not used anymore.
Merge with trunk (rev 892)

Revision history for this message
termie (termie) wrote :

- please put a space between the # and the comment in all cases, preferably with Sentence case
- please end your docstrings with periods in all cases
- line 87: please put the beginning of the dictionary as the same line as the opening curly brace and indent the next lines to match
- line 93: please us parens to wrap the multiline expression rather than escaping the newline, as in:

expr = (foo == BLAH
        and bar < 3)

- line 104: please place one argument per line, not two, or place them all on the same line on the next line and double indent
- line 114: please use != instead of <>
- please adjust all docstrings to match the formatting described in HACKING
- line 362: you have a commented out line that should probably be removed
- in xenapi tests you want 'from nova import log as logging' rather than as LOG, and you want it in the correct place in the import list, as described in HACKING
- line 454: please indent this line to match the other arguments
- line 619: the newline does not need to be escaped
- line 656: please put the beginning of the dict contents on teh first line and indent the rest of the contents to match it
- same for 676 and 684
- line 699: the correct format is # TODO(salvatore-orlando): the contents
- line 740: parens instead of escaped newlines, please
- same for line 777

review: Needs Fixing
696. By Armando Migliaccio

addressed termies review (first round)

697. By Armando Migliaccio

addressed termie's review (second round)

698. By Armando Migliaccio

addressed termies review (third round)

Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

Folks,

thanks very much for the review. This last round was regarding coding style. I have addressed issues raised by Termie, whereas Salvatore addressed Trey's.

I guess we are ready to roll now.

Cheers,
Armando

699. By Armando Migliaccio

merge trunk

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

looks good to me

review: Approve
700. By Armando Migliaccio

merge trunk

Revision history for this message
termie (termie) wrote :

thanks for the style cleanups :) there were still a few that didn't quite make it:

- single-line docstrings should be on a single line:

"""Single line docstring."""

not

"""
Single line docstring.
"""

- Multiline docstrings consist of a one-line summary on the first line, followed by a newline, followed by a more detailed description, such as:

"""Single line summary.

More detailed description.

"""

(We have the extra newline at the end to be nice to emacs users).

- you need some extra newlines at the top of nova/network/xenapi_network between the imports and LOG and between LOG and FLAGS
- when importing import only modules, not objects directly, for example instead of

+from nova.virt.xenapi.network_utils import NetworkHelper

you would want

from nova.virt.xenapi import network_utils

and then later on in your code you use network_utils.NetworkHelper

I agree this gets a little verbose do to the current code style in the xenapi tools, that code currently has a bit of a java fragrance, but this is the python way (we don't use static methods or factories nearly as much as java).

- when you define _CLASSES in fake.py you don't need to escape the newline (remove the backslash)

- in _create_local_sr a common way of dealing with long lines in that situation is to move everything to the next line and double indent, rather than escaping a new line, for example:

sr_ref = _create_object(
        'SR',
        {'name_label': 'Local storage',
         'type': 'lvm',
         'content_type': 'user',
         'shared': False,
         'physical_size': str(1 << 30),
         'physical_utilisation': str(0),
         'virtual_allocation': str(0),
         'other_config': {
                 'i18n-original-value-name_label': 'Local storage',
                 'i18n-key': 'local-storage'},
         'VDIs': []})

review: Needs Fixing
Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

thanks for your thorough review. Please see my comments inline

> thanks for the style cleanups :) there were still a few that didn't quite make
> it:
>
> - single-line docstrings should be on a single line:
>
> """Single line docstring."""
>
> not
>
> """
> Single line docstring.
> """
>
> - Multiline docstrings consist of a one-line summary on the first line,
> followed by a newline, followed by a more detailed description, such as:
>
> """Single line summary.
>
> More detailed description.
>
> """
>
> (We have the extra newline at the end to be nice to emacs users).
>
>

I wish I didn't edit those in the first place as they weren't part of the initial diff...I was just acting politely ;)

> - you need some extra newlines at the top of nova/network/xenapi_network
> between the imports and LOG and between LOG and FLAGS

What do you mean? linux_net.py in the same folder has got the same number of lines. Can you clarify this please?

> - when importing import only modules, not objects directly, for example
> instead of
>
> +from nova.virt.xenapi.network_utils import NetworkHelper
>
> you would want
>
> from nova.virt.xenapi import network_utils
>
> and then later on in your code you use network_utils.NetworkHelper

I am not sure this rule has been applied consistently throughout the code, but I'll oblige. On a more general note, it'd be nice to have a summarized set of Coding Style Guidelines on the Wiki (some sort of checklist other that http://www.python.org/dev/peps/pep-0008/, which is way too long). This may help and reduce the number of review rounds

>
> I agree this gets a little verbose do to the current code style in the xenapi
> tools, that code currently has a bit of a java fragrance, but this is the
> python way (we don't use static methods or factories nearly as much as java).
>
> - when you define _CLASSES in fake.py you don't need to escape the newline
> (remove the backslash)
>
> - in _create_local_sr a common way of dealing with long lines in that
> situation is to move everything to the next line and double indent, rather
> than escaping a new line, for example:
>
> sr_ref = _create_object(
> 'SR',
> {'name_label': 'Local storage',
> 'type': 'lvm',
> 'content_type': 'user',
> 'shared': False,
> 'physical_size': str(1 << 30),
> 'physical_utilisation': str(0),
> 'virtual_allocation': str(0),
> 'other_config': {
> 'i18n-original-value-name_label': 'Local storage',
> 'i18n-key': 'local-storage'},
> 'VDIs': []})

but then the newline for the inner dictionary is okay?

701. By Armando Migliaccio

style fixes

702. By Armando Migliaccio

merge trunk

703. By Armando Migliaccio

fix docstrings

Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

okay, I hope I caught them all!

Cheers,
Armando

704. By Armando Migliaccio

merge trunk

705. By Armando Migliaccio

sorted pep8 errors that were introduced during previous fixes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/network/linux_net.py'
2--- nova/network/linux_net.py 2011-03-24 18:51:38 +0000
3+++ nova/network/linux_net.py 2011-03-29 10:28:19 +0000
4@@ -44,13 +44,10 @@
5 flags.DEFINE_string('dhcp_domain',
6 'novalocal',
7 'domain to use for building the hostnames')
8-
9 flags.DEFINE_string('networks_path', '$state_path/networks',
10 'Location to keep network config files')
11 flags.DEFINE_string('public_interface', 'eth0',
12 'Interface for public IP addresses')
13-flags.DEFINE_string('vlan_interface', 'eth0',
14- 'network device for vlans')
15 flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'),
16 'location of nova-dhcpbridge')
17 flags.DEFINE_string('routing_source_ip', '$my_ip',
18
19=== modified file 'nova/network/manager.py'
20--- nova/network/manager.py 2011-03-24 19:04:24 +0000
21+++ nova/network/manager.py 2011-03-29 10:28:19 +0000
22@@ -73,6 +73,8 @@
23 flags.DEFINE_string('flat_network_dhcp_start', '10.0.0.2',
24 'Dhcp start for FlatDhcp')
25 flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks')
26+flags.DEFINE_string('vlan_interface', 'eth0',
27+ 'network device for vlans')
28 flags.DEFINE_integer('num_networks', 1, 'Number of networks to support')
29 flags.DEFINE_string('vpn_ip', '$my_ip',
30 'Public IP for the cloudpipe VPN servers')
31
32=== added file 'nova/network/xenapi_net.py'
33--- nova/network/xenapi_net.py 1970-01-01 00:00:00 +0000
34+++ nova/network/xenapi_net.py 2011-03-29 10:28:19 +0000
35@@ -0,0 +1,85 @@
36+# vim: tabstop=4 shiftwidth=4 softtabstop=4
37+
38+# Copyright (c) 2011 Citrix Systems, Inc.
39+# Copyright 2011 OpenStack LLC.
40+#
41+# Licensed under the Apache License, Version 2.0 (the "License"); you may
42+# not use this file except in compliance with the License. You may obtain
43+# a copy of the License at
44+#
45+# http://www.apache.org/licenses/LICENSE-2.0
46+#
47+# Unless required by applicable law or agreed to in writing, software
48+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
49+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
50+# License for the specific language governing permissions and limitations
51+# under the License.
52+
53+"""
54+Implements vlans, bridges, and iptables rules using linux utilities.
55+"""
56+
57+import os
58+
59+from nova import db
60+from nova import exception
61+from nova import flags
62+from nova import log as logging
63+from nova import utils
64+from nova.virt.xenapi_conn import XenAPISession
65+from nova.virt.xenapi import network_utils
66+
67+LOG = logging.getLogger("nova.xenapi_net")
68+
69+FLAGS = flags.FLAGS
70+
71+
72+def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
73+ """Create a vlan and bridge unless they already exist."""
74+ # Open xenapi session
75+ LOG.debug("ENTERING ensure_vlan_bridge in xenapi net")
76+ url = FLAGS.xenapi_connection_url
77+ username = FLAGS.xenapi_connection_username
78+ password = FLAGS.xenapi_connection_password
79+ session = XenAPISession(url, username, password)
80+ # Check whether bridge already exists
81+ # Retrieve network whose name_label is "bridge"
82+ network_ref = network_utils.NetworkHelper.find_network_with_name_label(
83+ session,
84+ bridge)
85+ if network_ref == None:
86+ # If bridge does not exists
87+ # 1 - create network
88+ description = "network for nova bridge %s" % bridge
89+ network_rec = {'name_label': bridge,
90+ 'name_description': description,
91+ 'other_config': {}}
92+ network_ref = session.call_xenapi('network.create', network_rec)
93+ # 2 - find PIF for VLAN
94+ expr = 'field "device" = "%s" and \
95+ field "VLAN" = "-1"' % FLAGS.vlan_interface
96+ pifs = session.call_xenapi('PIF.get_all_records_where', expr)
97+ pif_ref = None
98+ # Multiple PIF are ok: we are dealing with a pool
99+ if len(pifs) == 0:
100+ raise Exception(
101+ _('Found no PIF for device %s') % FLAGS.vlan_interface)
102+ # 3 - create vlan for network
103+ for pif_ref in pifs.keys():
104+ session.call_xenapi('VLAN.create',
105+ pif_ref,
106+ str(vlan_num),
107+ network_ref)
108+ else:
109+ # Check VLAN tag is appropriate
110+ network_rec = session.call_xenapi('network.get_record', network_ref)
111+ # Retrieve PIFs from network
112+ for pif_ref in network_rec['PIFs']:
113+ # Retrieve VLAN from PIF
114+ pif_rec = session.call_xenapi('PIF.get_record', pif_ref)
115+ pif_vlan = int(pif_rec['VLAN'])
116+ # Raise an exception if VLAN != vlan_num
117+ if pif_vlan != vlan_num:
118+ raise Exception(_("PIF %(pif_rec['uuid'])s for network "
119+ "%(bridge)s has VLAN id %(pif_vlan)d. "
120+ "Expected %(vlan_num)d") % locals())
121
122=== modified file 'nova/tests/db/fakes.py'
123--- nova/tests/db/fakes.py 2011-03-25 01:23:33 +0000
124+++ nova/tests/db/fakes.py 2011-03-29 10:28:19 +0000
125@@ -25,7 +25,7 @@
126
127
128 def stub_out_db_instance_api(stubs, injected=True):
129- """ Stubs out the db API for creating Instances """
130+ """Stubs out the db API for creating Instances."""
131
132 INSTANCE_TYPES = {
133 'm1.tiny': dict(memory_mb=512,
134@@ -56,27 +56,39 @@
135 flavorid=5,
136 rxtx_cap=5)}
137
138- network_fields = {
139- 'id': 'test',
140- 'bridge': 'xenbr0',
141- 'label': 'test_network',
142- 'netmask': '255.255.255.0',
143- 'cidr_v6': 'fe80::a00:0/120',
144- 'netmask_v6': '120',
145- 'gateway': '10.0.0.1',
146- 'gateway_v6': 'fe80::a00:1',
147- 'broadcast': '10.0.0.255',
148- 'dns': '10.0.0.2',
149- 'ra_server': None,
150- 'injected': injected}
151-
152- fixed_ip_fields = {
153- 'address': '10.0.0.3',
154- 'address_v6': 'fe80::a00:3',
155- 'network_id': 'test'}
156+ flat_network_fields = {'id': 'fake_flat',
157+ 'bridge': 'xenbr0',
158+ 'label': 'fake_flat_network',
159+ 'netmask': '255.255.255.0',
160+ 'cidr_v6': 'fe80::a00:0/120',
161+ 'netmask_v6': '120',
162+ 'gateway': '10.0.0.1',
163+ 'gateway_v6': 'fe80::a00:1',
164+ 'broadcast': '10.0.0.255',
165+ 'dns': '10.0.0.2',
166+ 'ra_server': None,
167+ 'injected': injected}
168+
169+ vlan_network_fields = {'id': 'fake_vlan',
170+ 'bridge': 'br111',
171+ 'label': 'fake_vlan_network',
172+ 'netmask': '255.255.255.0',
173+ 'cidr_v6': 'fe80::a00:0/120',
174+ 'netmask_v6': '120',
175+ 'gateway': '10.0.0.1',
176+ 'gateway_v6': 'fe80::a00:1',
177+ 'broadcast': '10.0.0.255',
178+ 'dns': '10.0.0.2',
179+ 'ra_server': None,
180+ 'vlan': 111,
181+ 'injected': False}
182+
183+ fixed_ip_fields = {'address': '10.0.0.3',
184+ 'address_v6': 'fe80::a00:3',
185+ 'network_id': 'fake_flat'}
186
187 class FakeModel(object):
188- """ Stubs out for model """
189+ """Stubs out for model."""
190 def __init__(self, values):
191 self.values = values
192
193@@ -96,10 +108,19 @@
194 return INSTANCE_TYPES[name]
195
196 def fake_network_get_by_instance(context, instance_id):
197+ # Even instance numbers are on vlan networks
198+ if instance_id % 2 == 0:
199+ return FakeModel(vlan_network_fields)
200+ else:
201+ return FakeModel(flat_network_fields)
202 return FakeModel(network_fields)
203
204 def fake_network_get_all_by_instance(context, instance_id):
205- return [FakeModel(network_fields)]
206+ # Even instance numbers are on vlan networks
207+ if instance_id % 2 == 0:
208+ return [FakeModel(vlan_network_fields)]
209+ else:
210+ return [FakeModel(flat_network_fields)]
211
212 def fake_instance_get_fixed_address(context, instance_id):
213 return FakeModel(fixed_ip_fields).address
214@@ -111,6 +132,8 @@
215 return [FakeModel(fixed_ip_fields)]
216
217 stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance)
218+ stubs.Set(db, 'network_get_all_by_instance',
219+ fake_network_get_all_by_instance)
220 stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all)
221 stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name)
222 stubs.Set(db, 'instance_get_fixed_address',
223
224=== modified file 'nova/tests/fake_utils.py'
225--- nova/tests/fake_utils.py 2011-03-24 02:01:46 +0000
226+++ nova/tests/fake_utils.py 2011-03-29 10:28:19 +0000
227@@ -14,8 +14,7 @@
228 # License for the specific language governing permissions and limitations
229 # under the License.
230
231-"""This modules stubs out functions in nova.utils
232-"""
233+"""This modules stubs out functions in nova.utils."""
234
235 import re
236 import types
237@@ -42,21 +41,25 @@
238
239
240 def fake_execute_set_repliers(repliers):
241- """Allows the client to configure replies to commands"""
242+ """Allows the client to configure replies to commands."""
243 global _fake_execute_repliers
244 _fake_execute_repliers = repliers
245
246
247 def fake_execute_default_reply_handler(*ignore_args, **ignore_kwargs):
248- """A reply handler for commands that haven't been added to the reply
249- list. Returns empty strings for stdout and stderr
250+ """A reply handler for commands that haven't been added to the reply list.
251+
252+ Returns empty strings for stdout and stderr.
253+
254 """
255 return '', ''
256
257
258 def fake_execute(*cmd_parts, **kwargs):
259- """This function stubs out execute, optionally executing
260- a preconfigued function to return expected data
261+ """This function stubs out execute.
262+
263+ It optionally executes a preconfigued function to return expected data.
264+
265 """
266 global _fake_execute_repliers
267
268
269=== modified file 'nova/tests/test_xenapi.py'
270--- nova/tests/test_xenapi.py 2011-03-25 00:16:53 +0000
271+++ nova/tests/test_xenapi.py 2011-03-29 10:28:19 +0000
272@@ -14,9 +14,7 @@
273 # License for the specific language governing permissions and limitations
274 # under the License.
275
276-"""
277-Test suite for XenAPI
278-"""
279+"""Test suite for XenAPI."""
280
281 import functools
282 import os
283@@ -65,9 +63,7 @@
284
285
286 class XenAPIVolumeTestCase(test.TestCase):
287- """
288- Unit tests for Volume operations
289- """
290+ """Unit tests for Volume operations."""
291 def setUp(self):
292 super(XenAPIVolumeTestCase, self).setUp()
293 self.stubs = stubout.StubOutForTesting()
294@@ -101,7 +97,7 @@
295 return db.volume_create(self.context, vol)
296
297 def test_create_iscsi_storage(self):
298- """ This shows how to test helper classes' methods """
299+ """This shows how to test helper classes' methods."""
300 stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests)
301 session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass')
302 helper = volume_utils.VolumeHelper
303@@ -116,7 +112,7 @@
304 db.volume_destroy(context.get_admin_context(), vol['id'])
305
306 def test_parse_volume_info_raise_exception(self):
307- """ This shows how to test helper classes' methods """
308+ """This shows how to test helper classes' methods."""
309 stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests)
310 session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass')
311 helper = volume_utils.VolumeHelper
312@@ -130,7 +126,7 @@
313 db.volume_destroy(context.get_admin_context(), vol['id'])
314
315 def test_attach_volume(self):
316- """ This shows how to test Ops classes' methods """
317+ """This shows how to test Ops classes' methods."""
318 stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests)
319 conn = xenapi_conn.get_connection(False)
320 volume = self._create_volume()
321@@ -149,7 +145,7 @@
322 check()
323
324 def test_attach_volume_raise_exception(self):
325- """ This shows how to test when exceptions are raised """
326+ """This shows how to test when exceptions are raised."""
327 stubs.stubout_session(self.stubs,
328 stubs.FakeSessionForVolumeFailedTests)
329 conn = xenapi_conn.get_connection(False)
330@@ -172,9 +168,7 @@
331
332
333 class XenAPIVMTestCase(test.TestCase):
334- """
335- Unit tests for VM operations
336- """
337+ """Unit tests for VM operations."""
338 def setUp(self):
339 super(XenAPIVMTestCase, self).setUp()
340 self.manager = manager.AuthManager()
341@@ -188,6 +182,7 @@
342 instance_name_template='%d')
343 xenapi_fake.reset()
344 xenapi_fake.create_local_srs()
345+ xenapi_fake.create_local_pifs()
346 db_fakes.stub_out_db_instance_api(self.stubs)
347 xenapi_fake.create_network('fake', FLAGS.flat_network_bridge)
348 stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
349@@ -247,12 +242,12 @@
350
351 check()
352
353- def create_vm_record(self, conn, os_type):
354+ def create_vm_record(self, conn, os_type, instance_id=1):
355 instances = conn.list_instances()
356- self.assertEquals(instances, ['1'])
357+ self.assertEquals(instances, [str(instance_id)])
358
359 # Get Nova record for VM
360- vm_info = conn.get_info(1)
361+ vm_info = conn.get_info(instance_id)
362 # Get XenAPI record for VM
363 vms = [rec for ref, rec
364 in xenapi_fake.get_all_records('VM').iteritems()
365@@ -286,19 +281,19 @@
366 key = 'vm-data/networking/aabbccddeeff'
367 xenstore_value = xenstore_data[key]
368 tcpip_data = ast.literal_eval(xenstore_value)
369- self.assertEquals(tcpip_data, {
370- 'label': 'test_network',
371- 'broadcast': '10.0.0.255',
372- 'ips': [{'ip': '10.0.0.3',
373- 'netmask':'255.255.255.0',
374- 'enabled':'1'}],
375- 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff',
376- 'netmask': '120',
377- 'enabled': '1',
378- 'gateway': 'fe80::a00:1'}],
379- 'mac': 'aa:bb:cc:dd:ee:ff',
380- 'dns': ['10.0.0.2'],
381- 'gateway': '10.0.0.1'})
382+ self.assertEquals(tcpip_data,
383+ {'label': 'fake_flat_network',
384+ 'broadcast': '10.0.0.255',
385+ 'ips': [{'ip': '10.0.0.3',
386+ 'netmask':'255.255.255.0',
387+ 'enabled':'1'}],
388+ 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff',
389+ 'netmask': '120',
390+ 'enabled': '1',
391+ 'gateway': 'fe80::a00:1'}],
392+ 'mac': 'aa:bb:cc:dd:ee:ff',
393+ 'dns': ['10.0.0.2'],
394+ 'gateway': '10.0.0.1'})
395
396 def check_vm_params_for_windows(self):
397 self.assertEquals(self.vm['platform']['nx'], 'true')
398@@ -334,9 +329,9 @@
399
400 def _test_spawn(self, image_id, kernel_id, ramdisk_id,
401 instance_type="m1.large", os_type="linux",
402- check_injection=False):
403+ instance_id=1, check_injection=False):
404 stubs.stubout_loopingcall_start(self.stubs)
405- values = {'id': 1,
406+ values = {'id': instance_id,
407 'project_id': self.project.id,
408 'user_id': self.user.id,
409 'image_id': image_id,
410@@ -347,7 +342,7 @@
411 'os_type': os_type}
412 instance = db.instance_create(self.context, values)
413 self.conn.spawn(instance)
414- self.create_vm_record(self.conn, os_type)
415+ self.create_vm_record(self.conn, os_type, instance_id)
416 self.check_vm_record(self.conn, check_injection)
417
418 def test_spawn_not_enough_memory(self):
419@@ -468,6 +463,28 @@
420 # guest agent is detected
421 self.assertFalse(self._tee_executed)
422
423+ def test_spawn_vlanmanager(self):
424+ self.flags(xenapi_image_service='glance',
425+ network_manager='nova.network.manager.VlanManager',
426+ network_driver='nova.network.xenapi_net',
427+ vlan_interface='fake0')
428+ # Reset network table
429+ xenapi_fake.reset_table('network')
430+ # Instance id = 2 will use vlan network (see db/fakes.py)
431+ fake_instance_id = 2
432+ network_bk = self.network
433+ # Ensure we use xenapi_net driver
434+ self.network = utils.import_object(FLAGS.network_manager)
435+ self.network.setup_compute_network(None, fake_instance_id)
436+ self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE,
437+ glance_stubs.FakeGlance.IMAGE_KERNEL,
438+ glance_stubs.FakeGlance.IMAGE_RAMDISK,
439+ instance_id=fake_instance_id)
440+ # TODO(salvatore-orlando): a complete test here would require
441+ # a check for making sure the bridge for the VM's VIF is
442+ # consistent with bridge specified in nova db
443+ self.network = network_bk
444+
445 def test_spawn_with_network_qos(self):
446 self._create_instance()
447 for vif_ref in xenapi_fake.get_all('VIF'):
448@@ -497,7 +514,7 @@
449 self.stubs.UnsetAll()
450
451 def _create_instance(self):
452- """Creates and spawns a test instance"""
453+ """Creates and spawns a test instance."""
454 stubs.stubout_loopingcall_start(self.stubs)
455 values = {
456 'id': 1,
457@@ -515,9 +532,7 @@
458
459
460 class XenAPIDiffieHellmanTestCase(test.TestCase):
461- """
462- Unit tests for Diffie-Hellman code
463- """
464+ """Unit tests for Diffie-Hellman code."""
465 def setUp(self):
466 super(XenAPIDiffieHellmanTestCase, self).setUp()
467 self.alice = SimpleDH()
468@@ -541,9 +556,7 @@
469
470
471 class XenAPIMigrateInstance(test.TestCase):
472- """
473- Unit test for verifying migration-related actions
474- """
475+ """Unit test for verifying migration-related actions."""
476
477 def setUp(self):
478 super(XenAPIMigrateInstance, self).setUp()
479@@ -598,9 +611,7 @@
480
481
482 class XenAPIDetermineDiskImageTestCase(test.TestCase):
483- """
484- Unit tests for code that detects the ImageType
485- """
486+ """Unit tests for code that detects the ImageType."""
487 def setUp(self):
488 super(XenAPIDetermineDiskImageTestCase, self).setUp()
489 glance_stubs.stubout_glance_client(self.stubs,
490@@ -619,9 +630,7 @@
491 self.assertEqual(disk_type, dt)
492
493 def test_instance_disk(self):
494- """
495- If a kernel is specified then the image type is DISK (aka machine)
496- """
497+ """If a kernel is specified, the image type is DISK (aka machine)."""
498 FLAGS.xenapi_image_service = 'objectstore'
499 self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_MACHINE
500 self.fake_instance.kernel_id = glance_stubs.FakeGlance.IMAGE_KERNEL
501
502=== modified file 'nova/virt/xenapi/fake.py'
503--- nova/virt/xenapi/fake.py 2011-03-24 02:01:46 +0000
504+++ nova/virt/xenapi/fake.py 2011-03-29 10:28:19 +0000
505@@ -60,8 +60,8 @@
506 from nova import log as logging
507
508
509-_CLASSES = ['host', 'network', 'session', 'SR', 'VBD',\
510- 'PBD', 'VDI', 'VIF', 'VM', 'task']
511+_CLASSES = ['host', 'network', 'session', 'SR', 'VBD',
512+ 'PBD', 'VDI', 'VIF', 'PIF', 'VM', 'VLAN', 'task']
513
514 _db_content = {}
515
516@@ -78,30 +78,36 @@
517 for c in _CLASSES:
518 _db_content[c] = {}
519 create_host('fake')
520- create_vm('fake', 'Running', is_a_template=False, is_control_domain=True)
521+ create_vm('fake',
522+ 'Running',
523+ is_a_template=False,
524+ is_control_domain=True)
525+
526+
527+def reset_table(table):
528+ if not table in _CLASSES:
529+ return
530+ _db_content[table] = {}
531
532
533 def create_host(name_label):
534- return _create_object('host', {
535- 'name_label': name_label,
536- })
537+ return _create_object('host',
538+ {'name_label': name_label})
539
540
541 def create_network(name_label, bridge):
542- return _create_object('network', {
543- 'name_label': name_label,
544- 'bridge': bridge,
545- })
546+ return _create_object('network',
547+ {'name_label': name_label,
548+ 'bridge': bridge})
549
550
551 def create_vm(name_label, status,
552 is_a_template=False, is_control_domain=False):
553- return _create_object('VM', {
554- 'name_label': name_label,
555- 'power-state': status,
556- 'is_a_template': is_a_template,
557- 'is_control_domain': is_control_domain,
558- })
559+ return _create_object('VM',
560+ {'name_label': name_label,
561+ 'power-state': status,
562+ 'is_a_template': is_a_template,
563+ 'is_control_domain': is_control_domain})
564
565
566 def destroy_vm(vm_ref):
567@@ -123,27 +129,24 @@
568
569
570 def create_vdi(name_label, read_only, sr_ref, sharable):
571- return _create_object('VDI', {
572- 'name_label': name_label,
573- 'read_only': read_only,
574- 'SR': sr_ref,
575- 'type': '',
576- 'name_description': '',
577- 'sharable': sharable,
578- 'other_config': {},
579- 'location': '',
580- 'xenstore_data': '',
581- 'sm_config': {},
582- 'VBDs': {},
583- })
584+ return _create_object('VDI',
585+ {'name_label': name_label,
586+ 'read_only': read_only,
587+ 'SR': sr_ref,
588+ 'type': '',
589+ 'name_description': '',
590+ 'sharable': sharable,
591+ 'other_config': {},
592+ 'location': '',
593+ 'xenstore_data': '',
594+ 'sm_config': {},
595+ 'VBDs': {}})
596
597
598 def create_vbd(vm_ref, vdi_ref):
599- vbd_rec = {
600- 'VM': vm_ref,
601- 'VDI': vdi_ref,
602- 'currently_attached': False,
603- }
604+ vbd_rec = {'VM': vm_ref,
605+ 'VDI': vdi_ref,
606+ 'currently_attached': False}
607 vbd_ref = _create_object('VBD', vbd_rec)
608 after_VBD_create(vbd_ref, vbd_rec)
609 return vbd_ref
610@@ -169,19 +172,24 @@
611
612
613 def create_pbd(config, host_ref, sr_ref, attached):
614- return _create_object('PBD', {
615- 'device-config': config,
616- 'host': host_ref,
617- 'SR': sr_ref,
618- 'currently-attached': attached,
619- })
620+ return _create_object('PBD',
621+ {'device-config': config,
622+ 'host': host_ref,
623+ 'SR': sr_ref,
624+ 'currently-attached': attached})
625
626
627 def create_task(name_label):
628- return _create_object('task', {
629- 'name_label': name_label,
630- 'status': 'pending',
631- })
632+ return _create_object('task',
633+ {'name_label': name_label,
634+ 'status': 'pending'})
635+
636+
637+def create_local_pifs():
638+ """Adds a PIF for each to the local database with VLAN=-1.
639+ Do this one per host."""
640+ for host_ref in _db_content['host'].keys():
641+ _create_local_pif(host_ref)
642
643
644 def create_local_srs():
645@@ -192,25 +200,34 @@
646
647
648 def _create_local_sr(host_ref):
649- sr_ref = _create_object('SR', {
650- 'name_label': 'Local storage',
651- 'type': 'lvm',
652- 'content_type': 'user',
653- 'shared': False,
654- 'physical_size': str(1 << 30),
655- 'physical_utilisation': str(0),
656- 'virtual_allocation': str(0),
657- 'other_config': {
658- 'i18n-original-value-name_label': 'Local storage',
659- 'i18n-key': 'local-storage',
660- },
661- 'VDIs': []
662- })
663+ sr_ref = _create_object(
664+ 'SR',
665+ {'name_label': 'Local storage',
666+ 'type': 'lvm',
667+ 'content_type': 'user',
668+ 'shared': False,
669+ 'physical_size': str(1 << 30),
670+ 'physical_utilisation': str(0),
671+ 'virtual_allocation': str(0),
672+ 'other_config': {
673+ 'i18n-original-value-name_label': 'Local storage',
674+ 'i18n-key': 'local-storage'},
675+ 'VDIs': []})
676 pbd_ref = create_pbd('', host_ref, sr_ref, True)
677 _db_content['SR'][sr_ref]['PBDs'] = [pbd_ref]
678 return sr_ref
679
680
681+def _create_local_pif(host_ref):
682+ pif_ref = _create_object('PIF',
683+ {'name-label': 'Fake PIF',
684+ 'MAC': '00:11:22:33:44:55',
685+ 'physical': True,
686+ 'VLAN': -1,
687+ 'device': 'fake0',
688+ 'host_uuid': host_ref})
689+
690+
691 def _create_object(table, obj):
692 ref = str(uuid.uuid4())
693 obj['uuid'] = str(uuid.uuid4())
694@@ -234,6 +251,21 @@
695 return sr_ref
696
697
698+def _create_vlan(pif_ref, vlan_num, network_ref):
699+ pif_rec = get_record('PIF', pif_ref)
700+ vlan_pif_ref = _create_object('PIF',
701+ {'name-label': 'Fake VLAN PIF',
702+ 'MAC': '00:11:22:33:44:55',
703+ 'physical': True,
704+ 'VLAN': vlan_num,
705+ 'device': pif_rec['device'],
706+ 'host_uuid': pif_rec['host_uuid']})
707+ return _create_object('VLAN',
708+ {'tagged-pif': pif_ref,
709+ 'untagged-pif': vlan_pif_ref,
710+ 'tag': vlan_num})
711+
712+
713 def get_all(table):
714 return _db_content[table].keys()
715
716@@ -292,6 +324,10 @@
717 rec['currently_attached'] = False
718 rec['device'] = ''
719
720+ def PIF_get_all_records_where(self, _1, _2):
721+ # TODO (salvatore-orlando): filter table on _2
722+ return _db_content['PIF']
723+
724 def VM_get_xenstore_data(self, _1, vm_ref):
725 return _db_content['VM'][vm_ref].get('xenstore_data', '')
726
727@@ -302,7 +338,7 @@
728 db_ref['xenstore_data'][key] = None
729
730 def network_get_all_records_where(self, _1, _2):
731- # TODO (salvatore-orlando):filter table on _2
732+ # TODO (salvatore-orlando): filter table on _2
733 return _db_content['network']
734
735 def VM_add_to_xenstore_data(self, _1, vm_ref, key, value):
736@@ -318,6 +354,9 @@
737 def host_call_plugin(*args):
738 return 'herp'
739
740+ def network_get_all_records_where(self, _1, filter):
741+ return self.xenapi.network.get_all_records()
742+
743 def xenapi_request(self, methodname, params):
744 if methodname.startswith('login'):
745 self._login(methodname, params)
746@@ -337,10 +376,9 @@
747
748 def _login(self, method, params):
749 self._session = str(uuid.uuid4())
750- _db_content['session'][self._session] = {
751- 'uuid': str(uuid.uuid4()),
752- 'this_host': _db_content['host'].keys()[0],
753- }
754+ _db_content['session'][self._session] = \
755+ {'uuid': str(uuid.uuid4()),
756+ 'this_host': _db_content['host'].keys()[0]}
757
758 def _logout(self):
759 s = self._session
760@@ -456,12 +494,16 @@
761 def _create(self, name, params):
762 self._check_session(params)
763 is_sr_create = name == 'SR.create'
764+ is_vlan_create = name == 'VLAN.create'
765 # Storage Repositories have a different API
766- expected = is_sr_create and 10 or 2
767+ expected = is_sr_create and 10 or is_vlan_create and 4 or 2
768 self._check_arg_count(params, expected)
769 (cls, _) = name.split('.')
770 ref = is_sr_create and \
771- _create_sr(cls, params) or _create_object(cls, params[1])
772+ _create_sr(cls, params) or \
773+ is_vlan_create and \
774+ _create_vlan(params[1], params[2], params[3]) or \
775+ _create_object(cls, params[1])
776
777 # Call hook to provide any fixups needed (ex. creating backrefs)
778 after_hook = 'after_%s_create' % cls
779
780=== modified file 'nova/virt/xenapi/network_utils.py'
781--- nova/virt/xenapi/network_utils.py 2010-12-23 03:35:41 +0000
782+++ nova/virt/xenapi/network_utils.py 2011-03-29 10:28:19 +0000
783@@ -28,11 +28,26 @@
784 """
785 The class that wraps the helper methods together.
786 """
787+ @classmethod
788+ def find_network_with_name_label(cls, session, name_label):
789+ networks = session.call_xenapi('network.get_by_name_label', name_label)
790+ if len(networks) == 1:
791+ return networks[0]
792+ elif len(networks) > 1:
793+ raise Exception(_('Found non-unique network'
794+ ' for name_label %s') % name_label)
795+ else:
796+ return None
797
798 @classmethod
799 def find_network_with_bridge(cls, session, bridge):
800- """Return the network on which the bridge is attached, if found."""
801- expr = 'field "bridge" = "%s"' % bridge
802+ """
803+ Return the network on which the bridge is attached, if found.
804+ The bridge is defined in the nova db and can be found either in the
805+ 'bridge' or 'name_label' fields of the XenAPI network record.
806+ """
807+ expr = 'field "name__label" = "%s" or ' \
808+ 'field "bridge" = "%s"' % (bridge, bridge)
809 networks = session.call_xenapi('network.get_all_records_where', expr)
810 if len(networks) == 1:
811 return networks.keys()[0]
812
813=== modified file 'nova/virt/xenapi/vmops.py'
814--- nova/virt/xenapi/vmops.py 2011-03-25 00:16:53 +0000
815+++ nova/virt/xenapi/vmops.py 2011-03-29 10:28:19 +0000
816@@ -58,7 +58,7 @@
817 VMHelper.XenAPI = self.XenAPI
818
819 def list_instances(self):
820- """List VM instances"""
821+ """List VM instances."""
822 # TODO(justinsb): Should we just always use the details method?
823 # Seems to be the same number of API calls..
824 vm_refs = []
825@@ -69,7 +69,7 @@
826 return vm_refs
827
828 def list_instances_detail(self):
829- """List VM instances, returning InstanceInfo objects"""
830+ """List VM instances, returning InstanceInfo objects."""
831 instance_infos = []
832 for vm_ref in self._session.get_xenapi().VM.get_all():
833 vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)
834@@ -119,11 +119,11 @@
835 self._spawn(instance, vm_ref)
836
837 def spawn_rescue(self, instance):
838- """Spawn a rescue instance"""
839+ """Spawn a rescue instance."""
840 self.spawn(instance)
841
842 def _create_vm(self, instance, vdi_uuid, network_info=None):
843- """Create VM instance"""
844+ """Create VM instance."""
845 instance_name = instance.name
846 vm_ref = VMHelper.lookup(self._session, instance_name)
847 if vm_ref is not None:
848@@ -180,7 +180,7 @@
849 return vm_ref
850
851 def _spawn(self, instance, vm_ref):
852- """Spawn a new instance"""
853+ """Spawn a new instance."""
854 LOG.debug(_('Starting VM %s...'), vm_ref)
855 self._start(instance, vm_ref)
856 instance_name = instance.name
857@@ -236,7 +236,8 @@
858 return timer.start(interval=0.5, now=True)
859
860 def _get_vm_opaque_ref(self, instance_or_vm):
861- """Refactored out the common code of many methods that receive either
862+ """
863+ Refactored out the common code of many methods that receive either
864 a vm name or a vm instance, and want a vm instance in return.
865 """
866 # if instance_or_vm is a string it must be opaque ref or instance name
867@@ -264,21 +265,21 @@
868 return vm_ref
869
870 def _acquire_bootlock(self, vm):
871- """Prevent an instance from booting"""
872+ """Prevent an instance from booting."""
873 self._session.call_xenapi(
874 "VM.set_blocked_operations",
875 vm,
876 {"start": ""})
877
878 def _release_bootlock(self, vm):
879- """Allow an instance to boot"""
880+ """Allow an instance to boot."""
881 self._session.call_xenapi(
882 "VM.remove_from_blocked_operations",
883 vm,
884 "start")
885
886 def snapshot(self, instance, image_id):
887- """Create snapshot from a running VM instance
888+ """Create snapshot from a running VM instance.
889
890 :param instance: instance to be snapshotted
891 :param image_id: id of image to upload to
892@@ -298,6 +299,7 @@
893 3. Push-to-glance: Once coalesced, we call a plugin on the XenServer
894 that will bundle the VHDs together and then push the bundle into
895 Glance.
896+
897 """
898 template_vm_ref = None
899 try:
900@@ -330,11 +332,12 @@
901 return
902
903 def migrate_disk_and_power_off(self, instance, dest):
904- """Copies a VHD from one host machine to another
905-
906- :param instance: the instance that owns the VHD in question
907- :param dest: the destination host machine
908- :param disk_type: values are 'primary' or 'cow'
909+ """Copies a VHD from one host machine to another.
910+
911+ :param instance: the instance that owns the VHD in question.
912+ :param dest: the destination host machine.
913+ :param disk_type: values are 'primary' or 'cow'.
914+
915 """
916 vm_ref = VMHelper.lookup(self._session, instance.name)
917
918@@ -383,7 +386,7 @@
919 return {'base_copy': base_copy_uuid, 'cow': cow_uuid}
920
921 def link_disks(self, instance, base_copy_uuid, cow_uuid):
922- """Links the base copy VHD to the COW via the XAPI plugin"""
923+ """Links the base copy VHD to the COW via the XAPI plugin."""
924 vm_ref = VMHelper.lookup(self._session, instance.name)
925 new_base_copy_uuid = str(uuid.uuid4())
926 new_cow_uuid = str(uuid.uuid4())
927@@ -404,7 +407,7 @@
928 return new_cow_uuid
929
930 def resize_instance(self, instance, vdi_uuid):
931- """Resize a running instance by changing it's RAM and disk size """
932+ """Resize a running instance by changing it's RAM and disk size."""
933 #TODO(mdietz): this will need to be adjusted for swap later
934 #The new disk size must be in bytes
935
936@@ -418,18 +421,20 @@
937 LOG.debug(_("Resize instance %s complete") % (instance.name))
938
939 def reboot(self, instance):
940- """Reboot VM instance"""
941+ """Reboot VM instance."""
942 vm_ref = self._get_vm_opaque_ref(instance)
943 task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref)
944 self._session.wait_for_task(task, instance.id)
945
946 def set_admin_password(self, instance, new_pass):
947- """Set the root/admin password on the VM instance. This is done via
948- an agent running on the VM. Communication between nova and the agent
949- is done via writing xenstore records. Since communication is done over
950- the XenAPI RPC calls, we need to encrypt the password. We're using a
951- simple Diffie-Hellman class instead of the more advanced one in
952- M2Crypto for compatibility with the agent code.
953+ """Set the root/admin password on the VM instance.
954+
955+ This is done via an agent running on the VM. Communication between nova
956+ and the agent is done via writing xenstore records. Since communication
957+ is done over the XenAPI RPC calls, we need to encrypt the password.
958+ We're using a simple Diffie-Hellman class instead of the more advanced
959+ one in M2Crypto for compatibility with the agent code.
960+
961 """
962 # Need to uniquely identify this request.
963 transaction_id = str(uuid.uuid4())
964@@ -462,11 +467,14 @@
965 return resp_dict['message']
966
967 def inject_file(self, instance, path, contents):
968- """Write a file to the VM instance. The path to which it is to be
969- written and the contents of the file need to be supplied; both will
970- be base64-encoded to prevent errors with non-ASCII characters being
971- transmitted. If the agent does not support file injection, or the user
972- has disabled it, a NotImplementedError will be raised.
973+ """Write a file to the VM instance.
974+
975+ The path to which it is to be written and the contents of the file
976+ need to be supplied; both will be base64-encoded to prevent errors
977+ with non-ASCII characters being transmitted. If the agent does not
978+ support file injection, or the user has disabled it, a
979+ NotImplementedError will be raised.
980+
981 """
982 # Files/paths must be base64-encoded for transmission to agent
983 b64_path = base64.b64encode(path)
984@@ -487,7 +495,7 @@
985 return resp_dict['message']
986
987 def _shutdown(self, instance, vm_ref, hard=True):
988- """Shutdown an instance"""
989+ """Shutdown an instance."""
990 state = self.get_info(instance['name'])['state']
991 if state == power_state.SHUTDOWN:
992 instance_name = instance.name
993@@ -511,11 +519,11 @@
994 LOG.exception(exc)
995
996 def _shutdown_rescue(self, rescue_vm_ref):
997- """Shutdown a rescue instance"""
998+ """Shutdown a rescue instance."""
999 self._session.call_xenapi("Async.VM.hard_shutdown", rescue_vm_ref)
1000
1001 def _destroy_vdis(self, instance, vm_ref):
1002- """Destroys all VDIs associated with a VM"""
1003+ """Destroys all VDIs associated with a VM."""
1004 instance_id = instance.id
1005 LOG.debug(_("Destroying VDIs for Instance %(instance_id)s")
1006 % locals())
1007@@ -532,7 +540,7 @@
1008 LOG.exception(exc)
1009
1010 def _destroy_rescue_vdis(self, rescue_vm_ref):
1011- """Destroys all VDIs associated with a rescued VM"""
1012+ """Destroys all VDIs associated with a rescued VM."""
1013 vdi_refs = VMHelper.lookup_vm_vdis(self._session, rescue_vm_ref)
1014 for vdi_ref in vdi_refs:
1015 try:
1016@@ -541,7 +549,7 @@
1017 continue
1018
1019 def _destroy_rescue_vbds(self, rescue_vm_ref):
1020- """Destroys all VBDs tied to a rescue VM"""
1021+ """Destroys all VBDs tied to a rescue VM."""
1022 vbd_refs = self._session.get_xenapi().VM.get_VBDs(rescue_vm_ref)
1023 for vbd_ref in vbd_refs:
1024 vbd_rec = self._session.get_xenapi().VBD.get_record(vbd_ref)
1025@@ -550,8 +558,7 @@
1026 VMHelper.destroy_vbd(self._session, vbd_ref)
1027
1028 def _destroy_kernel_ramdisk(self, instance, vm_ref):
1029- """
1030- Three situations can occur:
1031+ """Three situations can occur:
1032
1033 1. We have neither a ramdisk nor a kernel, in which case we are a
1034 RAW image and can omit this step
1035@@ -561,6 +568,7 @@
1036
1037 3. We have both, in which case we safely remove both the kernel
1038 and the ramdisk.
1039+
1040 """
1041 instance_id = instance.id
1042 if not instance.kernel_id and not instance.ramdisk_id:
1043@@ -589,7 +597,7 @@
1044 LOG.debug(_("kernel/ramdisk files removed"))
1045
1046 def _destroy_vm(self, instance, vm_ref):
1047- """Destroys a VM record"""
1048+ """Destroys a VM record."""
1049 instance_id = instance.id
1050 try:
1051 task = self._session.call_xenapi('Async.VM.destroy', vm_ref)
1052@@ -600,7 +608,7 @@
1053 LOG.debug(_("Instance %(instance_id)s VM destroyed") % locals())
1054
1055 def _destroy_rescue_instance(self, rescue_vm_ref):
1056- """Destroy a rescue instance"""
1057+ """Destroy a rescue instance."""
1058 self._destroy_rescue_vbds(rescue_vm_ref)
1059 self._shutdown_rescue(rescue_vm_ref)
1060 self._destroy_rescue_vdis(rescue_vm_ref)
1061@@ -608,11 +616,11 @@
1062 self._session.call_xenapi("Async.VM.destroy", rescue_vm_ref)
1063
1064 def destroy(self, instance):
1065- """
1066- Destroy VM instance
1067+ """Destroy VM instance.
1068
1069 This is the method exposed by xenapi_conn.destroy(). The rest of the
1070 destroy_* methods are internal.
1071+
1072 """
1073 instance_id = instance.id
1074 LOG.info(_("Destroying VM for Instance %(instance_id)s") % locals())
1075@@ -621,13 +629,13 @@
1076
1077 def _destroy(self, instance, vm_ref, shutdown=True,
1078 destroy_kernel_ramdisk=True):
1079- """
1080- Destroys VM instance by performing:
1081-
1082- 1. A shutdown if requested
1083- 2. Destroying associated VDIs
1084- 3. Destroying kernel and ramdisk files (if necessary)
1085- 4. Destroying that actual VM record
1086+ """Destroys VM instance by performing:
1087+
1088+ 1. A shutdown if requested.
1089+ 2. Destroying associated VDIs.
1090+ 3. Destroying kernel and ramdisk files (if necessary).
1091+ 4. Destroying that actual VM record.
1092+
1093 """
1094 if vm_ref is None:
1095 LOG.warning(_("VM is not present, skipping destroy..."))
1096@@ -650,35 +658,36 @@
1097 callback(ret)
1098
1099 def pause(self, instance, callback):
1100- """Pause VM instance"""
1101+ """Pause VM instance."""
1102 vm_ref = self._get_vm_opaque_ref(instance)
1103 task = self._session.call_xenapi('Async.VM.pause', vm_ref)
1104 self._wait_with_callback(instance.id, task, callback)
1105
1106 def unpause(self, instance, callback):
1107- """Unpause VM instance"""
1108+ """Unpause VM instance."""
1109 vm_ref = self._get_vm_opaque_ref(instance)
1110 task = self._session.call_xenapi('Async.VM.unpause', vm_ref)
1111 self._wait_with_callback(instance.id, task, callback)
1112
1113 def suspend(self, instance, callback):
1114- """suspend the specified instance"""
1115+ """Suspend the specified instance."""
1116 vm_ref = self._get_vm_opaque_ref(instance)
1117 task = self._session.call_xenapi('Async.VM.suspend', vm_ref)
1118 self._wait_with_callback(instance.id, task, callback)
1119
1120 def resume(self, instance, callback):
1121- """resume the specified instance"""
1122+ """Resume the specified instance."""
1123 vm_ref = self._get_vm_opaque_ref(instance)
1124 task = self._session.call_xenapi('Async.VM.resume', vm_ref, False,
1125 True)
1126 self._wait_with_callback(instance.id, task, callback)
1127
1128 def rescue(self, instance, callback):
1129- """Rescue the specified instance
1130- - shutdown the instance VM
1131- - set 'bootlock' to prevent the instance from starting in rescue
1132- - spawn a rescue VM (the vm name-label will be instance-N-rescue)
1133+ """Rescue the specified instance.
1134+
1135+ - shutdown the instance VM.
1136+ - set 'bootlock' to prevent the instance from starting in rescue.
1137+ - spawn a rescue VM (the vm name-label will be instance-N-rescue).
1138
1139 """
1140 rescue_vm_ref = VMHelper.lookup(self._session,
1141@@ -702,10 +711,11 @@
1142 self._session.call_xenapi("Async.VBD.plug", rescue_vbd_ref)
1143
1144 def unrescue(self, instance, callback):
1145- """Unrescue the specified instance
1146- - unplug the instance VM's disk from the rescue VM
1147- - teardown the rescue VM
1148- - release the bootlock to allow the instance VM to start
1149+ """Unrescue the specified instance.
1150+
1151+ - unplug the instance VM's disk from the rescue VM.
1152+ - teardown the rescue VM.
1153+ - release the bootlock to allow the instance VM to start.
1154
1155 """
1156 rescue_vm_ref = VMHelper.lookup(self._session,
1157@@ -723,9 +733,11 @@
1158 self._start(instance, original_vm_ref)
1159
1160 def poll_rescued_instances(self, timeout):
1161- """Look for expirable rescued instances
1162+ """Look for expirable rescued instances.
1163+
1164 - forcibly exit rescue mode for any instances that have been
1165 in rescue mode for >= the provided timeout
1166+
1167 """
1168 last_ran = self.poll_rescue_last_ran
1169 if not last_ran:
1170@@ -761,30 +773,30 @@
1171 False)
1172
1173 def get_info(self, instance):
1174- """Return data about VM instance"""
1175+ """Return data about VM instance."""
1176 vm_ref = self._get_vm_opaque_ref(instance)
1177 vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)
1178 return VMHelper.compile_info(vm_rec)
1179
1180 def get_diagnostics(self, instance):
1181- """Return data about VM diagnostics"""
1182+ """Return data about VM diagnostics."""
1183 vm_ref = self._get_vm_opaque_ref(instance)
1184 vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)
1185 return VMHelper.compile_diagnostics(self._session, vm_rec)
1186
1187 def get_console_output(self, instance):
1188- """Return snapshot of console"""
1189+ """Return snapshot of console."""
1190 # TODO: implement this to fix pylint!
1191 return 'FAKE CONSOLE OUTPUT of instance'
1192
1193 def get_ajax_console(self, instance):
1194- """Return link to instance's ajax console"""
1195+ """Return link to instance's ajax console."""
1196 # TODO: implement this!
1197 return 'http://fakeajaxconsole/fake_url'
1198
1199 # TODO(tr3buchet) - remove this function after nova multi-nic
1200 def _get_network_info(self, instance):
1201- """creates network info list for instance"""
1202+ """Creates network info list for instance."""
1203 admin_context = context.get_admin_context()
1204 IPs = db.fixed_ip_get_all_by_instance(admin_context,
1205 instance['id'])
1206@@ -826,7 +838,7 @@
1207 def inject_network_info(self, instance, vm_ref, network_info):
1208 """
1209 Generate the network info and make calls to place it into the
1210- xenstore and the xenstore param list
1211+ xenstore and the xenstore param list.
1212 """
1213 logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref)
1214
1215@@ -847,7 +859,7 @@
1216 pass
1217
1218 def create_vifs(self, vm_ref, network_info):
1219- """Creates vifs for an instance"""
1220+ """Creates vifs for an instance."""
1221 logging.debug(_("creating vif(s) for vm: |%s|"), vm_ref)
1222
1223 # this function raises if vm_ref is not a vm_opaque_ref
1224@@ -858,8 +870,8 @@
1225 bridge = network['bridge']
1226 rxtx_cap = info.pop('rxtx_cap')
1227 network_ref = \
1228- NetworkHelper.find_network_with_bridge(self._session, bridge)
1229-
1230+ NetworkHelper.find_network_with_bridge(self._session,
1231+ bridge)
1232 VMHelper.create_vif(self._session, vm_ref, network_ref,
1233 mac_address, device, rxtx_cap)
1234
1235@@ -872,7 +884,8 @@
1236 args, vm_ref)
1237
1238 def list_from_xenstore(self, vm, path):
1239- """Runs the xenstore-ls command to get a listing of all records
1240+ """
1241+ Runs the xenstore-ls command to get a listing of all records
1242 from 'path' downward. Returns a dict with the sub-paths as keys,
1243 and the value stored in those paths as values. If nothing is
1244 found at that path, returns None.
1245@@ -881,7 +894,8 @@
1246 return json.loads(ret)
1247
1248 def read_from_xenstore(self, vm, path):
1249- """Returns the value stored in the xenstore record for the given VM
1250+ """
1251+ Returns the value stored in the xenstore record for the given VM
1252 at the specified location. A XenAPIPlugin.PluginError will be raised
1253 if any error is encountered in the read process.
1254 """
1255@@ -897,7 +911,8 @@
1256 return ret
1257
1258 def write_to_xenstore(self, vm, path, value):
1259- """Writes the passed value to the xenstore record for the given VM
1260+ """
1261+ Writes the passed value to the xenstore record for the given VM
1262 at the specified location. A XenAPIPlugin.PluginError will be raised
1263 if any error is encountered in the write process.
1264 """
1265@@ -905,7 +920,8 @@
1266 {'value': json.dumps(value)})
1267
1268 def clear_xenstore(self, vm, path):
1269- """Deletes the VM's xenstore record for the specified path.
1270+ """
1271+ Deletes the VM's xenstore record for the specified path.
1272 If there is no such record, the request is ignored.
1273 """
1274 self._make_xenstore_call('delete_record', vm, path)
1275@@ -922,7 +938,8 @@
1276
1277 def _make_plugin_call(self, plugin, method, vm, path, addl_args=None,
1278 vm_ref=None):
1279- """Abstracts out the process of calling a method of a xenapi plugin.
1280+ """
1281+ Abstracts out the process of calling a method of a xenapi plugin.
1282 Any errors raised by the plugin will in turn raise a RuntimeError here.
1283 """
1284 instance_id = vm.id
1285@@ -952,7 +969,8 @@
1286 return ret
1287
1288 def add_to_xenstore(self, vm, path, key, value):
1289- """Adds the passed key/value pair to the xenstore record for
1290+ """
1291+ Adds the passed key/value pair to the xenstore record for
1292 the given VM at the specified location. A XenAPIPlugin.PluginError
1293 will be raised if any error is encountered in the write process.
1294 """
1295@@ -965,7 +983,8 @@
1296 self.write_to_xenstore(vm, path, current)
1297
1298 def remove_from_xenstore(self, vm, path, key_or_keys):
1299- """Takes either a single key or a list of keys and removes
1300+ """
1301+ Takes either a single key or a list of keys and removes
1302 them from the xenstoreirecord data for the given VM.
1303 If the key doesn't exist, the request is ignored.
1304 """
1305@@ -992,7 +1011,8 @@
1306 ###### names to distinguish them. (dabo)
1307 ########################################################################
1308 def read_partial_from_param_xenstore(self, instance_or_vm, key_prefix):
1309- """Returns a dict of all the keys in the xenstore parameter record
1310+ """
1311+ Returns a dict of all the keys in the xenstore parameter record
1312 for the given instance that begin with the key_prefix.
1313 """
1314 data = self.read_from_param_xenstore(instance_or_vm)
1315@@ -1003,7 +1023,8 @@
1316 return data
1317
1318 def read_from_param_xenstore(self, instance_or_vm, keys=None):
1319- """Returns the xenstore parameter record data for the specified VM
1320+ """
1321+ Returns the xenstore parameter record data for the specified VM
1322 instance as a dict. Accepts an optional key or list of keys; if a
1323 value for 'keys' is passed, the returned dict is filtered to only
1324 return the values for those keys.
1325@@ -1025,9 +1046,11 @@
1326 return ret
1327
1328 def add_to_param_xenstore(self, instance_or_vm, key, val):
1329- """Takes a key/value pair and adds it to the xenstore parameter
1330+ """
1331+ Takes a key/value pair and adds it to the xenstore parameter
1332 record for the given vm instance. If the key exists in xenstore,
1333- it is overwritten"""
1334+ it is overwritten
1335+ """
1336 vm_ref = self._get_vm_opaque_ref(instance_or_vm)
1337 self.remove_from_param_xenstore(instance_or_vm, key)
1338 jsonval = json.dumps(val)
1339@@ -1035,7 +1058,8 @@
1340 (vm_ref, key, jsonval))
1341
1342 def write_to_param_xenstore(self, instance_or_vm, mapping):
1343- """Takes a dict and writes each key/value pair to the xenstore
1344+ """
1345+ Takes a dict and writes each key/value pair to the xenstore
1346 parameter record for the given vm instance. Any existing data for
1347 those keys is overwritten.
1348 """
1349@@ -1043,7 +1067,8 @@
1350 self.add_to_param_xenstore(instance_or_vm, k, v)
1351
1352 def remove_from_param_xenstore(self, instance_or_vm, key_or_keys):
1353- """Takes either a single key or a list of keys and removes
1354+ """
1355+ Takes either a single key or a list of keys and removes
1356 them from the xenstore parameter record data for the given VM.
1357 If the key doesn't exist, the request is ignored.
1358 """
1359@@ -1069,7 +1094,8 @@
1360
1361
1362 class SimpleDH(object):
1363- """This class wraps all the functionality needed to implement
1364+ """
1365+ This class wraps all the functionality needed to implement
1366 basic Diffie-Hellman-Merkle key exchange in Python. It features
1367 intelligent defaults for the prime and base numbers needed for the
1368 calculation, while allowing you to supply your own. It requires that
1369@@ -1078,7 +1104,8 @@
1370 is not available, a RuntimeError will be raised.
1371 """
1372 def __init__(self, prime=None, base=None, secret=None):
1373- """You can specify the values for prime and base if you wish;
1374+ """
1375+ You can specify the values for prime and base if you wish;
1376 otherwise, reasonable default values will be used.
1377 """
1378 if prime is None: