Merge lp:~cbehrens/nova/passwd-with-spawn into lp:~hudson-openstack/nova/trunk

Proposed by Chris Behrens
Status: Merged
Approved by: Josh Kearney
Approved revision: 1115
Merged at revision: 1111
Proposed branch: lp:~cbehrens/nova/passwd-with-spawn
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 193 lines (+41/-23)
5 files modified
nova/api/openstack/servers.py (+2/-4)
nova/compute/api.py (+10/-13)
nova/compute/manager.py (+17/-4)
nova/tests/api/openstack/test_servers.py (+1/-2)
nova/virt/xenapi/vmops.py (+11/-0)
To merge this branch: bzr merge lp:~cbehrens/nova/passwd-with-spawn
Reviewer Review Type Date Requested Status
Josh Kearney (community) Approve
Ed Leafe (community) Approve
Johannes Erdfelt (community) Approve
Review via email: mp+62236@code.launchpad.net

Description of the change

During the API create call, the API would kick off a build and then loop in a greenthread waiting for the scheduler to pick a host for the instance. After API would see a host was picked, it would cast to the compute node's set_admin_password method.

The API server really should not have to do this. The password to set should be pushed along with the build request, instead. The compute node can then set the password after it detects the instance has booted. This removes a greenthread from the API server, a loop that constantly checks the DB for the host, and finally a cast to the compute node.

To post a comment you must log in.
Revision history for this message
Ed Leafe (ed-leafe) wrote :

Line 86 of the diff:
   instance_ref.admin_password = ...
should be:
   instance_ref.admin_pass = ...

review: Needs Fixing
Revision history for this message
Josh Kearney (jk0) wrote :

This works great, but for some reason the password is not getting set in instances admin_pass column. I don't think this was working before your fix, but it might be something to look into.

Revision history for this message
Chris Behrens (cbehrens) wrote :

Ok. The DB is now updated with the admin_pass on create. Apparently this was never implemented before. I pushed the DB update down into where the password is actually changed, and the DB is only updated on success.

Also fixed a problem where password reset API call could cause compute to loop forever trying to reset a password is the password reset returned an error.

BTW: These changes speed up the API return to 'nova boot' a lot!

Revision history for this message
Johannes Erdfelt (johannes.erdfelt) wrote :

lgtm

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

The changes look good.

review: Approve
Revision history for this message
Josh Kearney (jk0) wrote :

Tested new changes. Works great.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (56.9 KiB)

The attempt to merge lp:~cbehrens/nova/passwd-with-spawn into lp:nova failed. Below is the output from the failed tests.

AccountsTest
    test_account_create OK
    test_account_delete OK
    test_account_update OK
    test_get_account OK
AdminAPITest
    test_admin_disabled OK
    test_admin_enabled OK
APITest
    test_exceptions_are_converted_to_faults OK
Test
    test_authorize_token OK
    test_authorize_user OK
    test_bad_token OK
    test_bad_user_bad_key OK
    test_bad_user_good_key OK
    test_no_user OK
    test_token_expiry OK
TestFunctional
    test_token_doesnotexist OK
    test_token_expiry OK
TestLimiter
    test_authorize_token OK
LimiterTest
    test_limiter_custom_max_limit OK
    test_limiter_limit_and_offset OK
    test_limiter_limit_medium OK
    test_limiter_limit_over_max OK
    test_limiter_limit_zero OK
    test_limiter_negative_limit OK
    test_limiter_negative_offset OK
    test_limiter_nothing OK
    test_limiter_offset_bad OK
    test_limiter_offset_blank OK
    test_limiter_offset_medium OK
    test_limiter_offset_over_max OK
    test_limiter_offset_zero OK
ActionExtensionTest
    test_extended_action OK
    test_invalid_action OK
    test_invalid_action_body OK
ExtensionControllerTest
    test_get_by_alias OK
    test_index OK
ExtensionManagerTest
    test_get_resources OK
RequestExtensionTest
    test_get_resources_with_mgr OK
    test_get_resources_with_stub_mgr OK
ResourceExtensionTest
    test_get_resources OK
    test_get_resources_with_controller OK
    test_no_extension_present OK
TestFaults
    test_400_fault_json OK
    test_400_fault_xml OK
...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/openstack/servers.py'
2--- nova/api/openstack/servers.py 2011-05-18 15:31:41 +0000
3+++ nova/api/openstack/servers.py 2011-05-25 20:09:36 +0000
4@@ -180,7 +180,8 @@
5 key_name=key_name,
6 key_data=key_data,
7 metadata=env['server'].get('metadata', {}),
8- injected_files=injected_files)
9+ injected_files=injected_files,
10+ admin_password=password)
11 except quota.QuotaError as error:
12 self._handle_quota_error(error)
13
14@@ -190,8 +191,6 @@
15 builder = self._get_view_builder(req)
16 server = builder.build(inst, is_detail=True)
17 server['server']['adminPass'] = password
18- self.compute_api.set_admin_password(context, server['server']['id'],
19- password)
20 return server
21
22 def _deserialize_create(self, request):
23@@ -608,7 +607,6 @@
24
25 def _parse_update(self, context, server_id, inst_dict, update_dict):
26 if 'adminPass' in inst_dict['server']:
27- update_dict['admin_pass'] = inst_dict['server']['adminPass']
28 self.compute_api.set_admin_password(context, server_id,
29 inst_dict['server']['adminPass'])
30
31
32=== modified file 'nova/compute/api.py'
33--- nova/compute/api.py 2011-05-24 22:51:29 +0000
34+++ nova/compute/api.py 2011-05-25 20:09:36 +0000
35@@ -134,7 +134,8 @@
36 display_name='', display_description='',
37 key_name=None, key_data=None, security_group='default',
38 availability_zone=None, user_data=None, metadata={},
39- injected_files=None):
40+ injected_files=None,
41+ admin_password=None):
42 """Create the number and type of instances requested.
43
44 Verifies that quota and other arguments are valid.
45@@ -264,7 +265,8 @@
46 "instance_id": instance_id,
47 "instance_type": instance_type,
48 "availability_zone": availability_zone,
49- "injected_files": injected_files}})
50+ "injected_files": injected_files,
51+ "admin_password": admin_password}})
52
53 for group_id in security_groups:
54 self.trigger_security_group_members_refresh(elevated, group_id)
55@@ -503,15 +505,6 @@
56 raise exception.Error(_("Unable to find host for Instance %s")
57 % instance_id)
58
59- def _set_admin_password(self, context, instance_id, password):
60- """Set the root/admin password for the given instance."""
61- host = self._find_host(context, instance_id)
62-
63- rpc.cast(context,
64- self.db.queue_get_for(context, FLAGS.compute_topic, host),
65- {"method": "set_admin_password",
66- "args": {"instance_id": instance_id, "new_pass": password}})
67-
68 def snapshot(self, context, instance_id, name):
69 """Snapshot the given instance.
70
71@@ -665,8 +658,12 @@
72
73 def set_admin_password(self, context, instance_id, password=None):
74 """Set the root/admin password for the given instance."""
75- eventlet.spawn_n(self._set_admin_password, context, instance_id,
76- password)
77+ host = self._find_host(context, instance_id)
78+
79+ rpc.cast(context,
80+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
81+ {"method": "set_admin_password",
82+ "args": {"instance_id": instance_id, "new_pass": password}})
83
84 def inject_file(self, context, instance_id):
85 """Write a file to the given instance."""
86
87=== modified file 'nova/compute/manager.py'
88--- nova/compute/manager.py 2011-05-17 19:50:43 +0000
89+++ nova/compute/manager.py 2011-05-25 20:09:36 +0000
90@@ -221,6 +221,7 @@
91 context = context.elevated()
92 instance_ref = self.db.instance_get(context, instance_id)
93 instance_ref.injected_files = kwargs.get('injected_files', [])
94+ instance_ref.admin_pass = kwargs.get('admin_password', None)
95 if instance_ref['name'] in self.driver.list_instances():
96 raise exception.Error(_("Instance has already been created"))
97 LOG.audit(_("instance %s: starting..."), instance_id,
98@@ -405,22 +406,28 @@
99 @exception.wrap_exception
100 @checks_instance_lock
101 def set_admin_password(self, context, instance_id, new_pass=None):
102- """Set the root/admin password for an instance on this host."""
103+ """Set the root/admin password for an instance on this host.
104+
105+ This is generally only called by API password resets after an
106+ image has been built.
107+ """
108+
109 context = context.elevated()
110
111 if new_pass is None:
112 # Generate a random password
113 new_pass = utils.generate_password(FLAGS.password_length)
114
115- while True:
116+ max_tries = 10
117+
118+ for i in xrange(max_tries):
119 instance_ref = self.db.instance_get(context, instance_id)
120 instance_id = instance_ref["id"]
121 instance_state = instance_ref["state"]
122 expected_state = power_state.RUNNING
123
124 if instance_state != expected_state:
125- time.sleep(5)
126- continue
127+ raise exception.Error(_('Instance is not running'))
128 else:
129 try:
130 self.driver.set_admin_password(instance_ref, new_pass)
131@@ -436,6 +443,12 @@
132 except Exception, e:
133 # Catch all here because this could be anything.
134 LOG.exception(e)
135+ if i == max_tries - 1:
136+ # At some point this exception may make it back
137+ # to the API caller, and we don't want to reveal
138+ # too much. The real exception is logged above
139+ raise exception.Error(_('Internal error'))
140+ time.sleep(1)
141 continue
142
143 @exception.wrap_exception
144
145=== modified file 'nova/tests/api/openstack/test_servers.py'
146--- nova/tests/api/openstack/test_servers.py 2011-05-18 15:31:41 +0000
147+++ nova/tests/api/openstack/test_servers.py 2011-05-25 20:09:36 +0000
148@@ -774,8 +774,7 @@
149
150 def server_update(context, id, params):
151 filtered_dict = dict(
152- display_name='server_test',
153- admin_pass='bacon',
154+ display_name='server_test'
155 )
156 self.assertEqual(params, filtered_dict)
157 return filtered_dict
158
159=== modified file 'nova/virt/xenapi/vmops.py'
160--- nova/virt/xenapi/vmops.py 2011-05-25 14:57:52 +0000
161+++ nova/virt/xenapi/vmops.py 2011-05-25 20:09:36 +0000
162@@ -202,6 +202,13 @@
163 for path, contents in instance.injected_files:
164 LOG.debug(_("Injecting file path: '%s'") % path)
165 self.inject_file(instance, path, contents)
166+
167+ def _set_admin_password():
168+ admin_password = instance.admin_pass
169+ if admin_password:
170+ LOG.debug(_("Setting admin password"))
171+ self.set_admin_password(instance, admin_password)
172+
173 # NOTE(armando): Do we really need to do this in virt?
174 # NOTE(tr3buchet): not sure but wherever we do it, we need to call
175 # reset_network afterwards
176@@ -214,6 +221,7 @@
177 LOG.debug(_('Instance %s: booted'), instance_name)
178 timer.stop()
179 _inject_files()
180+ _set_admin_password()
181 return True
182 except Exception, exc:
183 LOG.warn(exc)
184@@ -458,6 +466,9 @@
185 # Successful return code from password is '0'
186 if resp_dict['returncode'] != '0':
187 raise RuntimeError(resp_dict['message'])
188+ db.instance_update(context.get_admin_context(),
189+ instance['id'],
190+ dict(admin_pass=new_pass))
191 return resp_dict['message']
192
193 def inject_file(self, instance, path, contents):