summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Prince <dan.prince@rackspace.com>2011-09-16 19:02:08 +0000
committerTarmac <>2011-09-16 19:02:08 +0000
commit5cc402a8fedd5862ee82fdcac72d659039cdff97 (patch)
tree6e380dd1a74d22bc54bb53fce309038e6f501703
parent2e0e2def5cd4a3fe899573304beb2d4851cc37b8 (diff)
parent5d5a05bddb59cff6cf5ac1f7104da6030197a9c3 (diff)
Update the v1.0 rescue admin action and the v1.1 rescue extension to
generate 'adminPass'. Fixes an issue where rescue commands were broken on XenServer. lp#838518 Also relocate 'password_length' flag and replace cases where password length was hard-coded.
-rw-r--r--nova/api/openstack/contrib/rescue.py13
-rw-r--r--nova/api/openstack/create_instance_helper.py4
-rw-r--r--nova/api/openstack/servers.py20
-rw-r--r--nova/compute/api.py9
-rw-r--r--nova/compute/manager.py12
-rw-r--r--nova/flags.py3
-rw-r--r--nova/tests/api/openstack/contrib/test_rescue.py23
-rw-r--r--nova/tests/api/openstack/contrib/test_volumes.py6
-rw-r--r--nova/tests/api/openstack/test_server_actions.py3
-rw-r--r--nova/tests/api/openstack/test_servers.py45
10 files changed, 108 insertions, 30 deletions
diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py
index 3de128895..2e5dbab73 100644
--- a/nova/api/openstack/contrib/rescue.py
+++ b/nova/api/openstack/contrib/rescue.py
@@ -18,11 +18,14 @@ import webob
from webob import exc
from nova import compute
+from nova import flags
from nova import log as logging
+from nova import utils
from nova.api.openstack import extensions as exts
from nova.api.openstack import faults
+FLAGS = flags.FLAGS
LOG = logging.getLogger("nova.api.contrib.rescue")
@@ -30,7 +33,7 @@ def wrap_errors(fn):
""""Ensure errors are not passed along."""
def wrapped(*args):
try:
- fn(*args)
+ return fn(*args)
except Exception, e:
return faults.Fault(exc.HTTPInternalServerError())
return wrapped
@@ -46,9 +49,13 @@ class Rescue(exts.ExtensionDescriptor):
def _rescue(self, input_dict, req, instance_id):
"""Rescue an instance."""
context = req.environ["nova.context"]
- self.compute_api.rescue(context, instance_id)
+ if input_dict['rescue'] and 'adminPass' in input_dict['rescue']:
+ password = input_dict['rescue']['adminPass']
+ else:
+ password = utils.generate_password(FLAGS.password_length)
+ self.compute_api.rescue(context, instance_id, rescue_password=password)
- return webob.Response(status_int=202)
+ return {'adminPass': password}
@wrap_errors
def _unrescue(self, input_dict, req, instance_id):
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index e27ddf78b..79f17e27f 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -317,14 +317,14 @@ class CreateInstanceHelper(object):
def _get_server_admin_password_old_style(self, server):
""" Determine the admin password for a server on creation """
- return utils.generate_password(16)
+ return utils.generate_password(FLAGS.password_length)
def _get_server_admin_password_new_style(self, server):
""" Determine the admin password for a server on creation """
password = server.get('adminPass')
if password is None:
- return utils.generate_password(16)
+ return utils.generate_password(FLAGS.password_length)
if not isinstance(password, basestring) or password == '':
msg = _("Invalid adminPass")
raise exc.HTTPBadRequest(explanation=msg)
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 5affd1f33..0ef246852 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -477,16 +477,22 @@ class Controller(object):
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
- def rescue(self, req, id):
+ def rescue(self, req, id, body={}):
"""Permit users to rescue the server."""
context = req.environ["nova.context"]
try:
- self.compute_api.rescue(context, id)
+ if 'rescue' in body and body['rescue'] and \
+ 'adminPass' in body['rescue']:
+ password = body['rescue']['adminPass']
+ else:
+ password = utils.generate_password(FLAGS.password_length)
+ self.compute_api.rescue(context, id, rescue_password=password)
except Exception:
readable = traceback.format_exc()
LOG.exception(_("compute.api::rescue %s"), readable)
raise exc.HTTPUnprocessableEntity()
- return webob.Response(status_int=202)
+
+ return {'adminPass': password}
@scheduler_api.redirect_handler
def unrescue(self, req, id):
@@ -618,7 +624,7 @@ class ControllerV10(Controller):
LOG.debug(msg)
raise exc.HTTPBadRequest(explanation=msg)
- password = utils.generate_password(16)
+ password = utils.generate_password(FLAGS.password_length)
try:
self.compute_api.rebuild(context, instance_id, image_id, password)
@@ -760,8 +766,10 @@ class ControllerV11(Controller):
self._validate_metadata(metadata)
self._decode_personalities(personalities)
- password = info["rebuild"].get("adminPass",
- utils.generate_password(16))
+ if 'rebuild' in info and 'adminPass' in info['rebuild']:
+ password = info['rebuild']['adminPass']
+ else:
+ password = utils.generate_password(FLAGS.password_length)
try:
self.compute_api.rebuild(context, instance_id, image_href,
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 4220f47ae..95ea13f61 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -1274,13 +1274,18 @@ class API(base.Base):
self._cast_compute_message('resume_instance', context, instance_id)
@scheduler_api.reroute_compute("rescue")
- def rescue(self, context, instance_id):
+ def rescue(self, context, instance_id, rescue_password=None):
"""Rescue the given instance."""
self.update(context,
instance_id,
vm_state=vm_states.ACTIVE,
task_state=task_states.RESCUING)
- self._cast_compute_message('rescue_instance', context, instance_id)
+
+ rescue_params = {
+ "rescue_password": rescue_password
+ }
+ self._cast_compute_message('rescue_instance', context, instance_id,
+ params=rescue_params)
@scheduler_api.reroute_compute("unrescue")
def unrescue(self, context, instance_id):
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 7915830ec..b5de6f048 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -70,8 +70,6 @@ flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection',
'Driver to use for controlling virtualization')
flags.DEFINE_string('stub_network', False,
'Stub network related code')
-flags.DEFINE_integer('password_length', 12,
- 'Length of generated admin passwords')
flags.DEFINE_string('console_host', socket.gethostname(),
'Console proxy host to use to connect to instances on'
'this host.')
@@ -797,12 +795,18 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
@checks_instance_lock
- def rescue_instance(self, context, instance_id):
- """Rescue an instance on this host."""
+ def rescue_instance(self, context, instance_id, **kwargs):
+ """
+ Rescue an instance on this host.
+ :param rescue_password: password to set on rescue instance
+ """
+
LOG.audit(_('instance %s: rescuing'), instance_id, context=context)
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
+ instance_ref.admin_pass = kwargs.get('rescue_password',
+ utils.generate_password(FLAGS.password_length))
network_info = self._get_instance_nw_info(context, instance_ref)
# NOTE(blamar): None of the virt drivers use the 'callback' param
diff --git a/nova/flags.py b/nova/flags.py
index aa76defe5..971e78807 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -421,6 +421,9 @@ DEFINE_string('root_helper', 'sudo',
DEFINE_bool('use_ipv6', False, 'use ipv6')
+DEFINE_integer('password_length', 12,
+ 'Length of generated instance admin passwords')
+
DEFINE_bool('monkey_patch', False,
'Whether to log monkey patching')
diff --git a/nova/tests/api/openstack/contrib/test_rescue.py b/nova/tests/api/openstack/contrib/test_rescue.py
index f8126d461..403bcfd4c 100644
--- a/nova/tests/api/openstack/contrib/test_rescue.py
+++ b/nova/tests/api/openstack/contrib/test_rescue.py
@@ -16,11 +16,14 @@ import json
import webob
from nova import compute
+from nova import flags
from nova import test
from nova.tests.api.openstack import fakes
+FLAGS = flags.FLAGS
-def rescue(self, context, instance_id):
+
+def rescue(self, context, instance_id, rescue_password=None):
pass
@@ -34,7 +37,19 @@ class RescueTest(test.TestCase):
self.stubs.Set(compute.api.API, "rescue", rescue)
self.stubs.Set(compute.api.API, "unrescue", unrescue)
- def test_rescue(self):
+ def test_rescue_with_preset_password(self):
+ body = {"rescue": {"adminPass": "AABBCC112233"}}
+ req = webob.Request.blank('/v1.1/123/servers/test_inst/action')
+ req.method = "POST"
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ resp = req.get_response(fakes.wsgi_app())
+ self.assertEqual(resp.status_int, 200)
+ resp_json = json.loads(resp.body)
+ self.assertEqual("AABBCC112233", resp_json['adminPass'])
+
+ def test_rescue_generates_password(self):
body = dict(rescue=None)
req = webob.Request.blank('/v1.1/123/servers/test_inst/action')
req.method = "POST"
@@ -43,6 +58,8 @@ class RescueTest(test.TestCase):
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
+ resp_json = json.loads(resp.body)
+ self.assertEqual(FLAGS.password_length, len(resp_json['adminPass']))
def test_unrescue(self):
body = dict(unrescue=None)
@@ -52,4 +69,4 @@ class RescueTest(test.TestCase):
req.headers["content-type"] = "application/json"
resp = req.get_response(fakes.wsgi_app())
- self.assertEqual(resp.status_int, 200)
+ self.assertEqual(resp.status_int, 202)
diff --git a/nova/tests/api/openstack/contrib/test_volumes.py b/nova/tests/api/openstack/contrib/test_volumes.py
index 443ec399f..52b65f5e1 100644
--- a/nova/tests/api/openstack/contrib/test_volumes.py
+++ b/nova/tests/api/openstack/contrib/test_volumes.py
@@ -19,6 +19,7 @@ import webob
import nova
from nova import context
+from nova import flags
from nova import test
from nova.api.openstack.contrib.volumes import BootFromVolumeController
from nova.compute import instance_types
@@ -26,6 +27,9 @@ from nova.tests.api.openstack import fakes
from nova.tests.api.openstack.test_servers import fake_gen_uuid
+FLAGS = flags.FLAGS
+
+
def fake_compute_api_create(cls, context, instance_type, image_href, **kwargs):
inst_type = instance_types.get_instance_type_by_flavor_id(2)
return [{'id': 1,
@@ -70,4 +74,4 @@ class BootFromVolumeTest(test.TestCase):
self.assertEqual(2, int(server['flavor']['id']))
self.assertEqual(u'test_server', server['name'])
self.assertEqual(3, int(server['image']['id']))
- self.assertEqual(16, len(server['adminPass']))
+ self.assertEqual(FLAGS.password_length, len(server['adminPass']))
diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py
index b9ef41465..4a215dd74 100644
--- a/nova/tests/api/openstack/test_server_actions.py
+++ b/nova/tests/api/openstack/test_server_actions.py
@@ -622,7 +622,8 @@ class ServerActionsTestV11(test.TestCase):
self.assertEqual(res.status_int, 202)
body = json.loads(res.body)
self.assertEqual(body['server']['image']['id'], '2')
- self.assertEqual(len(body['server']['adminPass']), 16)
+ self.assertEqual(len(body['server']['adminPass']),
+ FLAGS.password_length)
def test_server_rebuild_rejected_when_building(self):
body = {
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index ee7927c64..a2c8b3b04 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -28,6 +28,7 @@ import webob
from nova import context
from nova import db
from nova import exception
+from nova import flags
from nova import test
from nova import utils
import nova.api.openstack
@@ -49,6 +50,7 @@ from nova.tests.api.openstack import common
from nova.tests.api.openstack import fakes
+FLAGS = flags.FLAGS
FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
NS = "{http://docs.openstack.org/compute/api/v1.1}"
ATOMNS = "{http://www.w3.org/2005/Atom}"
@@ -1575,7 +1577,7 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 202)
server = json.loads(res.body)['server']
- self.assertEqual(16, len(server['adminPass']))
+ self.assertEqual(FLAGS.password_length, len(server['adminPass']))
self.assertEqual('server_test', server['name'])
self.assertEqual(1, server['id'])
self.assertEqual(2, server['flavorId'])
@@ -1776,7 +1778,7 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 202)
server = json.loads(res.body)['server']
- self.assertEqual(16, len(server['adminPass']))
+ self.assertEqual(FLAGS.password_length, len(server['adminPass']))
self.assertEqual(1, server['id'])
self.assertEqual(0, server['progress'])
self.assertEqual('server_test', server['name'])
@@ -1836,7 +1838,7 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 202)
server = json.loads(res.body)['server']
- self.assertEqual(16, len(server['adminPass']))
+ self.assertEqual(FLAGS.password_length, len(server['adminPass']))
self.assertEqual(1, server['id'])
self.assertEqual("BUILD", server["status"])
self.assertEqual(0, server['progress'])
@@ -2547,9 +2549,8 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status, '202 Accepted')
self.assertEqual(self.server_delete_called, True)
- def test_rescue_accepted(self):
+ def test_rescue_generates_password(self):
self.flags(allow_admin_api=True)
- body = {}
self.called = False
@@ -2564,7 +2565,33 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(self.called, True)
- self.assertEqual(res.status_int, 202)
+ self.assertEqual(res.status_int, 200)
+ res_body = json.loads(res.body)
+ self.assertTrue('adminPass' in res_body)
+ self.assertEqual(FLAGS.password_length, len(res_body['adminPass']))
+
+ def test_rescue_with_preset_password(self):
+ self.flags(allow_admin_api=True)
+
+ self.called = False
+
+ def rescue_mock(*args, **kwargs):
+ self.called = True
+
+ self.stubs.Set(nova.compute.api.API, 'rescue', rescue_mock)
+ req = webob.Request.blank('/v1.0/servers/1/rescue')
+ req.method = 'POST'
+ body = {"rescue": {"adminPass": "AABBCC112233"}}
+ req.body = json.dumps(body)
+ req.content_type = 'application/json'
+
+ res = req.get_response(fakes.wsgi_app())
+
+ self.assertEqual(self.called, True)
+ self.assertEqual(res.status_int, 200)
+ res_body = json.loads(res.body)
+ self.assertTrue('adminPass' in res_body)
+ self.assertEqual('AABBCC112233', res_body['adminPass'])
def test_rescue_raises_handled(self):
self.flags(allow_admin_api=True)
@@ -3621,7 +3648,8 @@ class TestServerInstanceCreation(test.TestCase):
self.assertEquals(response.status_int, 202)
response = json.loads(response.body)
self.assertTrue('adminPass' in response['server'])
- self.assertEqual(16, len(response['server']['adminPass']))
+ self.assertEqual(FLAGS.password_length,
+ len(response['server']['adminPass']))
def test_create_instance_admin_pass_xml(self):
request, response, dummy = \
@@ -3630,7 +3658,8 @@ class TestServerInstanceCreation(test.TestCase):
dom = minidom.parseString(response.body)
server = dom.childNodes[0]
self.assertEquals(server.nodeName, 'server')
- self.assertEqual(16, len(server.getAttribute('adminPass')))
+ self.assertEqual(FLAGS.password_length,
+ len(server.getAttribute('adminPass')))
class TestGetKernelRamdiskFromImage(test.TestCase):