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

Proposed by Trey Morris
Status: Merged
Approved by: Matt Dietz
Approved revision: 612
Merged at revision: 690
Proposed branch: lp:~tr3buchet/nova/xs_network_inject
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 480 lines (+223/-13)
15 files modified
bin/nova-manage (+5/-4)
nova/api/openstack/__init__.py (+1/-0)
nova/api/openstack/servers.py (+14/-0)
nova/compute/api.py (+7/-0)
nova/compute/manager.py (+12/-0)
nova/db/api.py (+10/-0)
nova/db/sqlalchemy/api.py (+24/-0)
nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py (+51/-0)
nova/db/sqlalchemy/models.py (+1/-0)
nova/network/manager.py (+7/-1)
nova/tests/api/openstack/test_servers.py (+12/-0)
nova/tests/test_xenapi.py (+6/-0)
nova/virt/xenapi/vmops.py (+55/-7)
nova/virt/xenapi_conn.py (+4/-0)
plugins/xenserver/xenapi/etc/xapi.d/plugins/agent (+14/-1)
To merge this branch: bzr merge lp:~tr3buchet/nova/xs_network_inject
Reviewer Review Type Date Requested Status
Ed Leafe (community) Approve
Sandy Walsh (community) Approve
Jay Pipes (community) Approve
Matt Dietz (community) Approve
Review via email: mp+49906@code.launchpad.net

Description of the change

added labels to networks for use in multi-nic
added writing network data to xenstore param-list
added call to agent to reset network
added reset_network call to openstack api

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

Don't think I've ever seen a 5-space tabstop. Copy/paste error? :)

58 -# vim: tabstop=4 shiftwidth=4 softtabstop=4
59 +# vim: tabstop=5 shiftwidth=4 softtabstop=4

Please s/003_cactus/003_add_network_label_column/ for the migrate:

183 === added file 'nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py'

We need >1 migration file for a release, and naming the migration file with something that describes the schema changes in it is a common practice.

258 def create_networks(self, context, cidr, num_networks, network_size,
259 - cidr_v6, *args, **kwargs):
260 + cidr_v6, label, *args, **kwargs):

Above, you have added label as a required argument to the network manager's create_networks() method, however:

1) I don't see where calls to create_networks() have been changed to pass in this newly-required label parameter
2) The label field of the Network model was added as an optional field, so it seems that perhaps the label param to create_networks() should have a default?

Other than those little things, the code looks good. Was there a plan for test code for this new functionality?

Cheers!
jay

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

wow, for the life of me i can't figure out how that tabstop got set to 5.. must have been some rogue vim keystrokes or something

Revision history for this message
Jay Pipes (jaypipes) wrote :

Per our conversation on IRC, I now see you supplying the label='public' to the create_networks() function in nova-manage. You can disregard that comment from my review. Thanks!

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

updates in place.

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

lgtm.

review: Approve
Revision history for this message
Jay Pipes (jaypipes) wrote :

nice work.

review: Approve
Revision history for this message
Ed Leafe (ed-leafe) wrote :

The new file "nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py" needs a correct copyright; NASA didn't create it in 2010. Change it to:
# Copyright 2011 OpenStack LLC

Other than that, lgtm.

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Good job Trey!

Why does network_get_all_by_instance only check FixedIPs?

Not your fault, but too bad we can't get any unit tests at the Xen level ... where it's needed.

Trivial comment: doc strings should be proper sentences (caps and punctuation) and remove unnecessary whitespace. (compute.api)

I'll approve after I hear back from you.

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

dabo: easy peasy
sandy: FixedIPs as opposed to both fixed and floating? caps and punctuation :(. englilish is getting into the code it seems

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

still checking fixed IPs only as no one knows what floating IPs are. Also haven't heard back from anyone who should.

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

lgtm

review: Approve
Revision history for this message
Ed Leafe (ed-leafe) wrote :

kewl

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/nova-manage'
2--- bin/nova-manage 2011-02-17 17:59:51 +0000
3+++ bin/nova-manage 2011-02-17 19:12:16 +0000
4@@ -472,8 +472,8 @@
5 """Class for managing networks."""
6
7 def create(self, fixed_range=None, num_networks=None,
8- network_size=None, vlan_start=None, vpn_start=None,
9- fixed_range_v6=None):
10+ network_size=None, vlan_start=None,
11+ vpn_start=None, fixed_range_v6=None, label='public'):
12 """Creates fixed ips for host by range
13 arguments: [fixed_range=FLAG], [num_networks=FLAG],
14 [network_size=FLAG], [vlan_start=FLAG],
15@@ -495,9 +495,10 @@
16 cidr=fixed_range,
17 num_networks=int(num_networks),
18 network_size=int(network_size),
19+ vlan_start=int(vlan_start),
20+ vpn_start=int(vpn_start),
21 cidr_v6=fixed_range_v6,
22- vlan_start=int(vlan_start),
23- vpn_start=int(vpn_start))
24+ label=label)
25
26 def list(self):
27 """List all created networks"""
28
29=== modified file 'nova/api/openstack/__init__.py'
30--- nova/api/openstack/__init__.py 2011-01-31 22:43:03 +0000
31+++ nova/api/openstack/__init__.py 2011-02-17 19:12:16 +0000
32@@ -79,6 +79,7 @@
33 server_members["actions"] = "GET"
34 server_members['suspend'] = 'POST'
35 server_members['resume'] = 'POST'
36+ server_members['reset_network'] = 'POST'
37
38 mapper.resource("server", "servers", controller=servers.Controller(),
39 collection={'detail': 'GET'},
40
41=== modified file 'nova/api/openstack/servers.py'
42--- nova/api/openstack/servers.py 2011-01-26 16:16:16 +0000
43+++ nova/api/openstack/servers.py 2011-02-17 19:12:16 +0000
44@@ -249,6 +249,20 @@
45 return faults.Fault(exc.HTTPUnprocessableEntity())
46 return exc.HTTPAccepted()
47
48+ def reset_network(self, req, id):
49+ """
50+ Reset networking on an instance (admin only).
51+
52+ """
53+ context = req.environ['nova.context']
54+ try:
55+ self.compute_api.reset_network(context, id)
56+ except:
57+ readable = traceback.format_exc()
58+ LOG.exception(_("Compute.api::reset_network %s"), readable)
59+ return faults.Fault(exc.HTTPUnprocessableEntity())
60+ return exc.HTTPAccepted()
61+
62 def pause(self, req, id):
63 """ Permit Admins to Pause the server. """
64 ctxt = req.environ['nova.context']
65
66=== modified file 'nova/compute/api.py'
67--- nova/compute/api.py 2011-02-15 18:19:52 +0000
68+++ nova/compute/api.py 2011-02-17 19:12:16 +0000
69@@ -466,6 +466,13 @@
70 instance = self.get(context, instance_id)
71 return instance['locked']
72
73+ def reset_network(self, context, instance_id):
74+ """
75+ Reset networking on the instance.
76+
77+ """
78+ self._cast_compute_message('reset_network', context, instance_id)
79+
80 def attach_volume(self, context, instance_id, volume_id, device):
81 if not re.match("^/dev/[a-z]d[a-z]+$", device):
82 raise exception.ApiError(_("Invalid device specified: %s. "
83
84=== modified file 'nova/compute/manager.py'
85--- nova/compute/manager.py 2011-02-09 10:08:15 +0000
86+++ nova/compute/manager.py 2011-02-17 19:12:16 +0000
87@@ -498,6 +498,18 @@
88 instance_ref = self.db.instance_get(context, instance_id)
89 return instance_ref['locked']
90
91+ @checks_instance_lock
92+ def reset_network(self, context, instance_id):
93+ """
94+ Reset networking on the instance.
95+
96+ """
97+ context = context.elevated()
98+ instance_ref = self.db.instance_get(context, instance_id)
99+ LOG.debug(_('instance %s: reset network'), instance_id,
100+ context=context)
101+ self.driver.reset_network(instance_ref)
102+
103 @exception.wrap_exception
104 def get_console_output(self, context, instance_id):
105 """Send the console output for an instance."""
106
107=== modified file 'nova/db/api.py'
108--- nova/db/api.py 2011-02-16 11:15:48 +0000
109+++ nova/db/api.py 2011-02-17 19:12:16 +0000
110@@ -293,6 +293,11 @@
111 return IMPL.fixed_ip_get_by_address(context, address)
112
113
114+def fixed_ip_get_all_by_instance(context, instance_id):
115+ """Get fixed ips by instance or raise if none exist."""
116+ return IMPL.fixed_ip_get_all_by_instance(context, instance_id)
117+
118+
119 def fixed_ip_get_instance(context, address):
120 """Get an instance for a fixed ip by address."""
121 return IMPL.fixed_ip_get_instance(context, address)
122@@ -521,6 +526,11 @@
123 return IMPL.network_get_by_instance(context, instance_id)
124
125
126+def network_get_all_by_instance(context, instance_id):
127+ """Get all networks by instance id or raise if none exist."""
128+ return IMPL.network_get_all_by_instance(context, instance_id)
129+
130+
131 def network_get_index(context, network_id):
132 """Get non-conflicting index for network."""
133 return IMPL.network_get_index(context, network_id)
134
135=== modified file 'nova/db/sqlalchemy/api.py'
136--- nova/db/sqlalchemy/api.py 2011-02-17 14:14:45 +0000
137+++ nova/db/sqlalchemy/api.py 2011-02-17 19:12:16 +0000
138@@ -609,6 +609,17 @@
139
140
141 @require_context
142+def fixed_ip_get_all_by_instance(context, instance_id):
143+ session = get_session()
144+ rv = session.query(models.FixedIp).\
145+ filter_by(instance_id=instance_id).\
146+ filter_by(deleted=False)
147+ if not rv:
148+ raise exception.NotFound(_('No address for instance %s') % instance_id)
149+ return rv
150+
151+
152+@require_context
153 def fixed_ip_get_instance_v6(context, address):
154 session = get_session()
155 mac = utils.to_mac(address)
156@@ -1109,6 +1120,19 @@
157
158
159 @require_admin_context
160+def network_get_all_by_instance(_context, instance_id):
161+ session = get_session()
162+ rv = session.query(models.Network).\
163+ filter_by(deleted=False).\
164+ join(models.Network.fixed_ips).\
165+ filter_by(instance_id=instance_id).\
166+ filter_by(deleted=False)
167+ if not rv:
168+ raise exception.NotFound(_('No network for instance %s') % instance_id)
169+ return rv
170+
171+
172+@require_admin_context
173 def network_set_host(context, network_id, host_id):
174 session = get_session()
175 with session.begin():
176
177=== added file 'nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py'
178--- nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py 1970-01-01 00:00:00 +0000
179+++ nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py 2011-02-17 19:12:16 +0000
180@@ -0,0 +1,51 @@
181+# vim: tabstop=4 shiftwidth=4 softtabstop=4
182+
183+# Copyright 2011 OpenStack LLC
184+# All Rights Reserved.
185+#
186+# Licensed under the Apache License, Version 2.0 (the "License"); you may
187+# not use this file except in compliance with the License. You may obtain
188+# a copy of the License at
189+#
190+# http://www.apache.org/licenses/LICENSE-2.0
191+#
192+# Unless required by applicable law or agreed to in writing, software
193+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
194+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
195+# License for the specific language governing permissions and limitations
196+# under the License.
197+
198+from sqlalchemy import *
199+from migrate import *
200+
201+from nova import log as logging
202+
203+
204+meta = MetaData()
205+
206+
207+networks = Table('networks', meta,
208+ Column('id', Integer(), primary_key=True, nullable=False),
209+ )
210+
211+
212+#
213+# New Tables
214+#
215+
216+
217+#
218+# Tables to alter
219+#
220+
221+networks_label = Column(
222+ 'label',
223+ String(length=255, convert_unicode=False, assert_unicode=None,
224+ unicode_error=None, _warn_on_bytestring=False))
225+
226+
227+def upgrade(migrate_engine):
228+ # Upgrade operations go here. Don't create your own engine;
229+ # bind migrate_engine to your metadata
230+ meta.bind = migrate_engine
231+ networks.create_column(networks_label)
232
233=== modified file 'nova/db/sqlalchemy/models.py'
234--- nova/db/sqlalchemy/models.py 2011-02-01 16:09:34 +0000
235+++ nova/db/sqlalchemy/models.py 2011-02-17 19:12:16 +0000
236@@ -373,6 +373,7 @@
237 "vpn_public_port"),
238 {'mysql_engine': 'InnoDB'})
239 id = Column(Integer, primary_key=True)
240+ label = Column(String(255))
241
242 injected = Column(Boolean, default=False)
243 cidr = Column(String(255), unique=True)
244
245=== modified file 'nova/network/manager.py'
246--- nova/network/manager.py 2011-02-09 20:49:25 +0000
247+++ nova/network/manager.py 2011-02-17 19:12:16 +0000
248@@ -331,11 +331,12 @@
249 pass
250
251 def create_networks(self, context, cidr, num_networks, network_size,
252- cidr_v6, *args, **kwargs):
253+ cidr_v6, label, *args, **kwargs):
254 """Create networks based on parameters."""
255 fixed_net = IPy.IP(cidr)
256 fixed_net_v6 = IPy.IP(cidr_v6)
257 significant_bits_v6 = 64
258+ count = 1
259 for index in range(num_networks):
260 start = index * network_size
261 significant_bits = 32 - int(math.log(network_size, 2))
262@@ -348,6 +349,11 @@
263 net['gateway'] = str(project_net[1])
264 net['broadcast'] = str(project_net.broadcast())
265 net['dhcp_start'] = str(project_net[2])
266+ if num_networks > 1:
267+ net['label'] = "%s_%d" % (label, count)
268+ else:
269+ net['label'] = label
270+ count += 1
271
272 if(FLAGS.use_ipv6):
273 cidr_v6 = "%s/%s" % (fixed_net_v6[0], significant_bits_v6)
274
275=== modified file 'nova/tests/api/openstack/test_servers.py'
276--- nova/tests/api/openstack/test_servers.py 2011-01-27 18:48:00 +0000
277+++ nova/tests/api/openstack/test_servers.py 2011-02-17 19:12:16 +0000
278@@ -281,6 +281,18 @@
279 res = req.get_response(fakes.wsgi_app())
280 self.assertEqual(res.status_int, 202)
281
282+ def test_server_reset_network(self):
283+ FLAGS.allow_admin_api = True
284+ body = dict(server=dict(
285+ name='server_test', imageId=2, flavorId=2, metadata={},
286+ personality={}))
287+ req = webob.Request.blank('/v1.0/servers/1/reset_network')
288+ req.method = 'POST'
289+ req.content_type = 'application/json'
290+ req.body = json.dumps(body)
291+ res = req.get_response(fakes.wsgi_app())
292+ self.assertEqual(res.status_int, 202)
293+
294 def test_server_diagnostics(self):
295 req = webob.Request.blank("/v1.0/servers/1/diagnostics")
296 req.method = "GET"
297
298=== modified file 'nova/tests/test_xenapi.py'
299--- nova/tests/test_xenapi.py 2011-02-09 10:08:15 +0000
300+++ nova/tests/test_xenapi.py 2011-02-17 19:12:16 +0000
301@@ -32,6 +32,7 @@
302 from nova.virt.xenapi import fake as xenapi_fake
303 from nova.virt.xenapi import volume_utils
304 from nova.virt.xenapi.vmops import SimpleDH
305+from nova.virt.xenapi.vmops import VMOps
306 from nova.tests.db import fakes as db_fakes
307 from nova.tests.xenapi import stubs
308 from nova.tests.glance import stubs as glance_stubs
309@@ -141,6 +142,10 @@
310 self.stubs.UnsetAll()
311
312
313+def reset_network(*args):
314+ pass
315+
316+
317 class XenAPIVMTestCase(test.TestCase):
318 """
319 Unit tests for VM operations
320@@ -162,6 +167,7 @@
321 stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
322 stubs.stubout_get_this_vm_uuid(self.stubs)
323 stubs.stubout_stream_disk(self.stubs)
324+ self.stubs.Set(VMOps, 'reset_network', reset_network)
325 glance_stubs.stubout_glance_client(self.stubs,
326 glance_stubs.FakeGlance)
327 self.conn = xenapi_conn.get_connection(False)
328
329=== modified file 'nova/virt/xenapi/vmops.py'
330--- nova/virt/xenapi/vmops.py 2011-02-15 17:30:19 +0000
331+++ nova/virt/xenapi/vmops.py 2011-02-17 19:12:16 +0000
332@@ -66,6 +66,7 @@
333 if vm is not None:
334 raise exception.Duplicate(_('Attempted to create'
335 ' non-unique name %s') % instance.name)
336+
337 #ensure enough free memory is available
338 if not VMHelper.ensure_free_mem(self._session, instance):
339 name = instance['name']
340@@ -75,10 +76,6 @@
341 instance['id'],
342 power_state.SHUTDOWN)
343 return
344- bridge = db.network_get_by_instance(context.get_admin_context(),
345- instance['id'])['bridge']
346- network_ref = \
347- NetworkHelper.find_network_with_bridge(self._session, bridge)
348
349 user = AuthManager().get_user(instance.user_id)
350 project = AuthManager().get_project(instance.project_id)
351@@ -107,9 +104,46 @@
352 instance, kernel, ramdisk, pv_kernel)
353 VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True)
354
355- if network_ref:
356- VMHelper.create_vif(self._session, vm_ref,
357- network_ref, instance.mac_address)
358+ # write network info
359+ admin_context = context.get_admin_context()
360+
361+ # TODO(tr3buchet) - remove comment in multi-nic
362+ # I've decided to go ahead and consider multiple IPs and networks
363+ # at this stage even though they aren't implemented because these will
364+ # be needed for multi-nic and there was no sense writing it for single
365+ # network/single IP and then having to turn around and re-write it
366+ IPs = db.fixed_ip_get_all_by_instance(admin_context, instance['id'])
367+ for network in db.network_get_all_by_instance(admin_context,
368+ instance['id']):
369+ network_IPs = [ip for ip in IPs if ip.network_id == network.id]
370+
371+ def ip_dict(ip):
372+ return {'netmask': network['netmask'],
373+ 'enabled': '1',
374+ 'ip': ip.address}
375+
376+ mac_id = instance.mac_address.replace(':', '')
377+ location = 'vm-data/networking/%s' % mac_id
378+ mapping = {'label': network['label'],
379+ 'gateway': network['gateway'],
380+ 'mac': instance.mac_address,
381+ 'dns': [network['dns']],
382+ 'ips': [ip_dict(ip) for ip in network_IPs]}
383+ self.write_to_param_xenstore(vm_ref, {location: mapping})
384+
385+ # TODO(tr3buchet) - remove comment in multi-nic
386+ # this bit here about creating the vifs will be updated
387+ # in multi-nic to handle multiple IPs on the same network
388+ # and multiple networks
389+ # for now it works as there is only one of each
390+ bridge = network['bridge']
391+ network_ref = \
392+ NetworkHelper.find_network_with_bridge(self._session, bridge)
393+
394+ if network_ref:
395+ VMHelper.create_vif(self._session, vm_ref,
396+ network_ref, instance.mac_address)
397+
398 LOG.debug(_('Starting VM %s...'), vm_ref)
399 self._session.call_xenapi('VM.start', vm_ref, False, False)
400 instance_name = instance.name
401@@ -117,6 +151,8 @@
402 % locals())
403
404 # NOTE(armando): Do we really need to do this in virt?
405+ # NOTE(tr3buchet): not sure but wherever we do it, we need to call
406+ # reset_network afterwards
407 timer = utils.LoopingCall(f=None)
408
409 def _wait_for_boot():
410@@ -137,6 +173,10 @@
411 timer.stop()
412
413 timer.f = _wait_for_boot
414+
415+ # call reset networking
416+ self.reset_network(instance)
417+
418 return timer.start(interval=0.5, now=True)
419
420 def _get_vm_opaque_ref(self, instance_or_vm):
421@@ -398,6 +438,14 @@
422 # TODO: implement this!
423 return 'http://fakeajaxconsole/fake_url'
424
425+ def reset_network(self, instance):
426+ """
427+ Creates uuid arg to pass to make_agent_call and calls it.
428+
429+ """
430+ args = {'id': str(uuid.uuid4())}
431+ resp = self._make_agent_call('resetnetwork', instance, '', args)
432+
433 def list_from_xenstore(self, vm, path):
434 """Runs the xenstore-ls command to get a listing of all records
435 from 'path' downward. Returns a dict with the sub-paths as keys,
436
437=== modified file 'nova/virt/xenapi_conn.py'
438--- nova/virt/xenapi_conn.py 2011-01-25 23:20:55 +0000
439+++ nova/virt/xenapi_conn.py 2011-02-17 19:12:16 +0000
440@@ -188,6 +188,10 @@
441 """resume the specified instance"""
442 self._vmops.resume(instance, callback)
443
444+ def reset_network(self, instance):
445+ """reset networking for specified instance"""
446+ self._vmops.reset_network(instance)
447+
448 def get_info(self, instance_id):
449 """Return data about VM instance"""
450 return self._vmops.get_info(instance_id)
451
452=== modified file 'plugins/xenserver/xenapi/etc/xapi.d/plugins/agent'
453--- plugins/xenserver/xenapi/etc/xapi.d/plugins/agent 2011-02-08 10:14:51 +0000
454+++ plugins/xenserver/xenapi/etc/xapi.d/plugins/agent 2011-02-17 19:12:16 +0000
455@@ -91,6 +91,18 @@
456 return resp
457
458
459+@jsonify
460+def resetnetwork(self, arg_dict):
461+ """
462+ Writes a resquest to xenstore that tells the agent to reset networking.
463+
464+ """
465+ arg_dict['value'] = json.dumps({'name': 'resetnetwork', 'value': ''})
466+ request_id = arg_dict['id']
467+ arg_dict['path'] = "data/host/%s" % request_id
468+ xenstore.write_record(self, arg_dict)
469+
470+
471 def _wait_for_agent(self, request_id, arg_dict):
472 """Periodically checks xenstore for a response from the agent.
473 The request is always written to 'data/host/{id}', and
474@@ -124,4 +136,5 @@
475 if __name__ == "__main__":
476 XenAPIPlugin.dispatch(
477 {"key_init": key_init,
478- "password": password})
479+ "password": password,
480+ "resetnetwork": resetnetwork})