Merge lp:~tr3buchet/nova/xs_multi_nic into lp:~hudson-openstack/nova/trunk

Proposed by Trey Morris
Status: Merged
Approved by: Matt Dietz
Approved revision: 808
Merged at revision: 843
Proposed branch: lp:~tr3buchet/nova/xs_multi_nic
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 234 lines (+67/-72)
2 files modified
nova/virt/xenapi/vm_utils.py (+2/-2)
nova/virt/xenapi/vmops.py (+65/-70)
To merge this branch: bzr merge lp:~tr3buchet/nova/xs_multi_nic
Reviewer Review Type Date Requested Status
Rick Harris (community) Approve
Matt Dietz (community) Approve
Review via email: mp+53458@code.launchpad.net

Commit message

xenapi support for multi_nic. This is a phase of multi_nic which allows xenapi to work as is and with multi_nic. The other virt driver(s) need to be updated with the same support.

Description of the change

xenapi now supports multi_nic. This has to be merged before or at the same time as multi_nic. Driver receive network information from the compute worker for spawn, inject networking info, and create vifs. Will continue to work if network information is not passed in (for example if multi_nic is not yet implemented). After multi_nic a few of these shims can be deleted.

network info format:
list of (network model object, {network info dict}) pairs. The network info dict is going to be created identically to how mappings are currently created in nova/virt/xenapi/vmops.py in def inject_network_info

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

Looks ok to me. I also understand your use case from offline discussions.

review: Approve
Revision history for this message
Rick Harris (rconradharris) wrote :

Nice work, Trey. Here are my thoughts so far (mostly style stuff):

> 9 + def spawn(self, instance, network_info=None):

Not exposed from xenapi_conn.py?

Are all virt-drivers going to accept a network_info=None kwarg?

> 25 + if network_info is not None:
> 26 + self.inject_network_info(instance, network_info)
> 27 + self.create_vifs(instance, [nw for (nw, mapping) in network_info])
> 28 + else:
> 29 + # TODO(tr3buchet) - goes away with multi-nic
> 30 + networks = self.inject_network_info(instance)
> 31 + self.create_vifs(instance, networks)

It seems like we should be able to simplify all of that to:

    networks = self.inject_network_info(instance, network_info)
    self.create_vifs(instance, networks)

Assuming `inject_network_info` returns `networks` correctly for both the
network_info=None and network_info!=None cases.

> def inject_network_info(self, instance, network_info=None):

This is method is handling two separate cases 1) multi_nic and 2) no
multi_nice. Since it's pretty long, I think it's a good canidate to be broken
up into two smaller methods, something like:

    def inject_network_info(self, instance, network_info=None):
        if network_info:
            networks = self.inject_network_info_multi_nic(self, instance, network_info)
        else:
            networks = self.inject_network_info_default(self, instance)

        return networks

    def inject_network_info_multi_nic(self, instance, network_info):
        # multi-nic stuff here
        return networks

    def inject_network_info_default(self, instance):
        # old code here
        return networks

If possible, it's a good idea to have the inject_network_info stuff continue
to return `networks`; this is more consistent and allows you to make the
simplification noted above.

> 59 + self.write_to_param_xenstore(vm_ref, {location: mapping})

Syntax error. {'location': mapping}

> 61 + self.write_to_xenstore(vm_ref, location,
> 62 + mapping['location'])

Appears misaligned. Seems like we have to 'accepted' conventions here:

1. Line-up with the first arg
    self.write_to_xenstore(vm_ref, location,
                           mapping['location'])

2. +1 level hanging indent
    self.write_to_xenstore(vm_ref, location,
        mapping['location'])
OR

    self.write_to_xenstore(
        vm_ref, location, mapping['location'])

Looks like there's an opportunity to DRY up the code a bit. Looks like both
multi-nic and default both do something like

    for network in networks:
        try:
            self.write_to_xenstore(vm_ref, location,
                                           mapping['location'])
        except KeyError:
            # catch KeyError for domid if instance isn't running
            pass

There should be a way of extracting that out.

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

refactoring!

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

fixed. also merged qos and tested.

Revision history for this message
Rick Harris (rconradharris) wrote :

Really nice work here, Trey. I think this is a much cleaner patch than last time.

A couple of small nits:

> 127 + self.write_to_param_xenstore(vm_ref, {location: info})

location: => 'location':

> 201 + args, vm_ref)

Should probably dedent to match opening ( of function

> except KeyError:
> # catch KeyError for domid if instance isn't running
> pass

I'm unclear why/how a KeyError is raised in that case. Any info on that? (The catching of KeyError seems a little wonky in this case, that's why I bring it up.)

> 167 + device = 0
> 168 + for (network, info) in network_info:

Really minor, but we could eliminate the manual handling of the device counting-var by using `enumerate`:

  for device, (network, info) in enumerate(network_info):

Also might be clearer if `device_id`. Thoughts?

I'm a little worried that the code here has zero-test coverage, we've already had syntax errors sneak into both versions of the patch.

Would it be possible to:

a) Add a unit-test around _get_network_info, something simple to flex the code that generates the network_info mappings from instances

and

b) Add a unit-test around inject_network_info. For this we'd need to stub out the _make_plugin_call method.

You have some refactor-in-the-future TODOs in there, so in addition to flexing the code, these tests, if added, would probably help with the future refactoring work. Just a thought :)

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

location is not supposed to be in quotes

This is just an opinion right?

KeyError is caught because when you attempt to write to the xenstore for an instance that has yet to be created there is no dom_id. This happens in the initial injection before boot and also in unittests.

I like the enumerate idea. playing with it now

in this refactor all code is covered by existing unittests

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

sorry for blunt, in a hurry.

I love the enumerate. Your reviews are brilliant!

Revision history for this message
Rick Harris (rconradharris) wrote :

> location is not supposed to be in quotes

You're right. Not used to seeing dict-literals use variable-names for keys.

> This is just an opinion right?

Yep, it's my opinion that it's 'unclear' to me :)

The problem, as I see it, is that a domain missing from an instance *is not* a KeyError. It happens to trip a KeyError by virtue of how it's implemented, but, conceptually, I wouldn't expect that to be what _make_plugin_call raised.

I think ideally we'd have something like NoDomainForInstance exception. However, since Nova isn't yet being that specific with its exceptions, perhaps RuntimeError with a helpful msg would be appropriate. So,

  args = {'dom_id': vm_rec['domid'], 'path': path}

might become:

  if 'dom_id' not in vm_rec:
        raise RuntimeError(_('Instance %s does not have a domain') % instance_id)
  args = {'dom_id': vm_rec['domid'], 'path': path}

This is really minor, I'm raising this more for discussion than anything else, but it did cause some confusion for me while reviewing, so I thought it worth mentioning.

> I like the enumerate idea. playing with it now

Cool, enumerate is damn useful.

> in this refactor all code is covered by existing unittests

I'm not so sure about that. http://paste.openstack.org/show/948/

I changed `location` to `location1` (which is non-existent) and all of the tests still passed.

> sorry for blunt, in a hurry.

No worries!

Revision history for this message
Rick Harris (rconradharris) wrote :

We can work on unit-tests and KeyError issue in follow-up patches. For now, probably better to get this in sooner so multi-nic work can progress.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/virt/xenapi/vm_utils.py'
2--- nova/virt/xenapi/vm_utils.py 2011-03-17 19:34:45 +0000
3+++ nova/virt/xenapi/vm_utils.py 2011-03-21 18:21:19 +0000
4@@ -234,11 +234,11 @@
5
6 @classmethod
7 def create_vif(cls, session, vm_ref, network_ref, mac_address,
8- dev="0", rxtx_cap=0):
9+ dev, rxtx_cap=0):
10 """Create a VIF record. Returns a Deferred that gives the new
11 VIF reference."""
12 vif_rec = {}
13- vif_rec['device'] = dev
14+ vif_rec['device'] = str(dev)
15 vif_rec['network'] = network_ref
16 vif_rec['VM'] = vm_ref
17 vif_rec['MAC'] = mac_address
18
19=== modified file 'nova/virt/xenapi/vmops.py'
20--- nova/virt/xenapi/vmops.py 2011-03-17 02:20:18 +0000
21+++ nova/virt/xenapi/vmops.py 2011-03-21 18:21:19 +0000
22@@ -81,11 +81,11 @@
23 instance.image_id, user, project, disk_image_type)
24 return vdi_uuid
25
26- def spawn(self, instance):
27+ def spawn(self, instance, network_info=None):
28 vdi_uuid = self.create_disk(instance)
29- self._spawn_with_disk(instance, vdi_uuid=vdi_uuid)
30+ self._spawn_with_disk(instance, vdi_uuid, network_info)
31
32- def _spawn_with_disk(self, instance, vdi_uuid):
33+ def _spawn_with_disk(self, instance, vdi_uuid, network_info=None):
34 """Create VM instance"""
35 instance_name = instance.name
36 vm_ref = VMHelper.lookup(self._session, instance_name)
37@@ -129,8 +129,12 @@
38 vdi_ref=vdi_ref, userdevice=0, bootable=True)
39
40 # inject_network_info and create vifs
41- networks = self.inject_network_info(instance)
42- self.create_vifs(instance, networks)
43+ # TODO(tr3buchet) - check to make sure we have network info, otherwise
44+ # create it now. This goes away once nova-multi-nic hits.
45+ if network_info is None:
46+ network_info = self._get_network_info(instance)
47+ self.create_vifs(vm_ref, network_info)
48+ self.inject_network_info(instance, vm_ref, network_info)
49
50 LOG.debug(_('Starting VM %s...'), vm_ref)
51 self._start(instance, vm_ref)
52@@ -181,7 +185,7 @@
53 timer.f = _wait_for_boot
54
55 # call to reset network to configure network from xenstore
56- self.reset_network(instance)
57+ self.reset_network(instance, vm_ref)
58
59 return timer.start(interval=0.5, now=True)
60
61@@ -685,24 +689,17 @@
62 # TODO: implement this!
63 return 'http://fakeajaxconsole/fake_url'
64
65- def inject_network_info(self, instance):
66- """
67- Generate the network info and make calls to place it into the
68- xenstore and the xenstore param list
69-
70- """
71- # TODO(tr3buchet) - remove comment in multi-nic
72- # I've decided to go ahead and consider multiple IPs and networks
73- # at this stage even though they aren't implemented because these will
74- # be needed for multi-nic and there was no sense writing it for single
75- # network/single IP and then having to turn around and re-write it
76- vm_ref = self._get_vm_opaque_ref(instance.id)
77- logging.debug(_("injecting network info to xenstore for vm: |%s|"),
78- vm_ref)
79+ # TODO(tr3buchet) - remove this function after nova multi-nic
80+ def _get_network_info(self, instance):
81+ """creates network info list for instance"""
82 admin_context = context.get_admin_context()
83- IPs = db.fixed_ip_get_all_by_instance(admin_context, instance['id'])
84+ IPs = db.fixed_ip_get_all_by_instance(admin_context,
85+ instance['id'])
86 networks = db.network_get_all_by_instance(admin_context,
87 instance['id'])
88+ flavor = db.instance_type_get_by_name(admin_context,
89+ instance['instance_type'])
90+ network_info = []
91 for network in networks:
92 network_IPs = [ip for ip in IPs if ip.network_id == network.id]
93
94@@ -719,67 +716,64 @@
95 "gateway": ip6.gatewayV6,
96 "enabled": "1"}
97
98- mac_id = instance.mac_address.replace(':', '')
99- location = 'vm-data/networking/%s' % mac_id
100- mapping = {
101+ info = {
102 'label': network['label'],
103 'gateway': network['gateway'],
104 'mac': instance.mac_address,
105+ 'rxtx_cap': flavor['rxtx_cap'],
106 'dns': [network['dns']],
107 'ips': [ip_dict(ip) for ip in network_IPs],
108 'ip6s': [ip6_dict(ip) for ip in network_IPs]}
109-
110- self.write_to_param_xenstore(vm_ref, {location: mapping})
111-
112+ network_info.append((network, info))
113+ return network_info
114+
115+ def inject_network_info(self, instance, vm_ref, network_info):
116+ """
117+ Generate the network info and make calls to place it into the
118+ xenstore and the xenstore param list
119+ """
120+ logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref)
121+
122+ # this function raises if vm_ref is not a vm_opaque_ref
123+ self._session.get_xenapi().VM.get_record(vm_ref)
124+
125+ for (network, info) in network_info:
126+ location = 'vm-data/networking/%s' % info['mac'].replace(':', '')
127+ self.write_to_param_xenstore(vm_ref, {location: info})
128 try:
129- self.write_to_xenstore(vm_ref, location, mapping['location'])
130+ # TODO(tr3buchet): fix function call after refactor
131+ #self.write_to_xenstore(vm_ref, location, info)
132+ self._make_plugin_call('xenstore.py', 'write_record', instance,
133+ location, {'value': json.dumps(info)},
134+ vm_ref)
135 except KeyError:
136 # catch KeyError for domid if instance isn't running
137 pass
138
139- return networks
140-
141- def create_vifs(self, instance, networks=None):
142- """
143- Creates vifs for an instance
144-
145- """
146- vm_ref = self._get_vm_opaque_ref(instance['id'])
147- admin_context = context.get_admin_context()
148- flavor = db.instance_type_get_by_name(admin_context,
149- instance.instance_type)
150+ def create_vifs(self, vm_ref, network_info):
151+ """Creates vifs for an instance"""
152 logging.debug(_("creating vif(s) for vm: |%s|"), vm_ref)
153- rxtx_cap = flavor['rxtx_cap']
154- if networks is None:
155- networks = db.network_get_all_by_instance(admin_context,
156- instance['id'])
157- # TODO(tr3buchet) - remove comment in multi-nic
158- # this bit here about creating the vifs will be updated
159- # in multi-nic to handle multiple IPs on the same network
160- # and multiple networks
161- # for now it works as there is only one of each
162- for network in networks:
163+
164+ # this function raises if vm_ref is not a vm_opaque_ref
165+ self._session.get_xenapi().VM.get_record(vm_ref)
166+
167+ for device, (network, info) in enumerate(network_info):
168+ mac_address = info['mac']
169 bridge = network['bridge']
170+ rxtx_cap = info.pop('rxtx_cap')
171 network_ref = \
172 NetworkHelper.find_network_with_bridge(self._session, bridge)
173
174- if network_ref:
175- try:
176- device = "1" if instance._rescue else "0"
177- except AttributeError:
178- device = "0"
179-
180- VMHelper.create_vif(self._session, vm_ref, network_ref,
181- instance.mac_address, device,
182- rxtx_cap=rxtx_cap)
183-
184- def reset_network(self, instance):
185- """
186- Creates uuid arg to pass to make_agent_call and calls it.
187-
188- """
189+ VMHelper.create_vif(self._session, vm_ref, network_ref,
190+ mac_address, device, rxtx_cap)
191+
192+ def reset_network(self, instance, vm_ref):
193+ """Creates uuid arg to pass to make_agent_call and calls it."""
194 args = {'id': str(uuid.uuid4())}
195- resp = self._make_agent_call('resetnetwork', instance, '', args)
196+ # TODO(tr3buchet): fix function call after refactor
197+ #resp = self._make_agent_call('resetnetwork', instance, '', args)
198+ resp = self._make_plugin_call('agent', 'resetnetwork', instance, '',
199+ args, vm_ref)
200
201 def list_from_xenstore(self, vm, path):
202 """Runs the xenstore-ls command to get a listing of all records
203@@ -820,25 +814,26 @@
204 """
205 self._make_xenstore_call('delete_record', vm, path)
206
207- def _make_xenstore_call(self, method, vm, path, addl_args={}):
208+ def _make_xenstore_call(self, method, vm, path, addl_args=None):
209 """Handles calls to the xenstore xenapi plugin."""
210 return self._make_plugin_call('xenstore.py', method=method, vm=vm,
211 path=path, addl_args=addl_args)
212
213- def _make_agent_call(self, method, vm, path, addl_args={}):
214+ def _make_agent_call(self, method, vm, path, addl_args=None):
215 """Abstracts out the interaction with the agent xenapi plugin."""
216 return self._make_plugin_call('agent', method=method, vm=vm,
217 path=path, addl_args=addl_args)
218
219- def _make_plugin_call(self, plugin, method, vm, path, addl_args={}):
220+ def _make_plugin_call(self, plugin, method, vm, path, addl_args=None,
221+ vm_ref=None):
222 """Abstracts out the process of calling a method of a xenapi plugin.
223 Any errors raised by the plugin will in turn raise a RuntimeError here.
224 """
225 instance_id = vm.id
226- vm_ref = self._get_vm_opaque_ref(vm)
227+ vm_ref = vm_ref or self._get_vm_opaque_ref(vm)
228 vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)
229 args = {'dom_id': vm_rec['domid'], 'path': path}
230- args.update(addl_args)
231+ args.update(addl_args or {})
232 try:
233 task = self._session.async_call_plugin(plugin, method, args)
234 ret = self._session.wait_for_task(task, instance_id)