Show an error message on instance launch error

Since instance creation happens asynchronously, all that currently
happens on the Horizon side is the state changes from Creating to Error.

This change checks the instance state during the Ajax request on the
backend and returns an Ajax error message popup if the status is
'ERROR'.

Change-Id: I259e27b7b6c73a38098514a701c235feb81af143
Closes-bug: 1236168.
This commit is contained in:
Kieran Spear 2013-10-07 13:17:05 +11:00
parent 9c08d883ea
commit 2dddc39a0b
3 changed files with 113 additions and 1 deletions

View File

@ -71,7 +71,7 @@ class Server(base.APIResourceWrapper):
"""
_attrs = ['addresses', 'attrs', 'id', 'image', 'links',
'metadata', 'name', 'private_ip', 'public_ip', 'status', 'uuid',
'image_name', 'VirtualInterfaces', 'flavor', 'key_name',
'image_name', 'VirtualInterfaces', 'flavor', 'key_name', 'fault',
'tenant_id', 'user_id', 'OS-EXT-STS:power_state',
'OS-EXT-STS:task_state', 'OS-EXT-SRV-ATTR:instance_name',
'OS-EXT-SRV-ATTR:host', 'created']

View File

@ -431,6 +431,29 @@ class SimpleDisassociateIP(tables.Action):
return shortcuts.redirect("horizon:project:instances:index")
def instance_fault_to_friendly_message(instance):
fault = getattr(instance, 'fault', {})
message = fault.get('message', _("Unknown"))
default_message = _("Please try again later [Error: %s].") % message
fault_map = {
'NoValidHost': _("There is not enough capacity for this "
"flavor in the selected availability zone. "
"Try again later or select a different availability "
"zone.")
}
return fault_map.get(message, default_message)
def get_instance_error(instance):
if instance.status.lower() != 'error':
return None
message = instance_fault_to_friendly_message(instance)
preamble = _('Failed to launch instance "%s"'
) % instance.name or instance.id
message = string_concat(preamble, ': ', message)
return message
class UpdateRow(tables.Row):
ajax = True
@ -438,6 +461,9 @@ class UpdateRow(tables.Row):
instance = api.nova.server_get(request, instance_id)
instance.full_flavor = api.nova.flavor_get(request,
instance.flavor["id"])
error = get_instance_error(instance)
if error:
messages.error(request, error)
return instance

View File

@ -18,6 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import uuid
from django.core.urlresolvers import reverse # noqa
@ -2043,3 +2044,88 @@ class InstanceTests(test.TestCase):
password=password,
confirm_password=password)
self.assertRedirectsNoFollow(res, INDEX_URL)
class InstanceAjaxTests(test.TestCase):
@test.create_stubs({api.nova: ("server_get",
"flavor_get",
"extension_supported"),
api.neutron: ("is_extension_supported",)})
def test_row_update(self):
server = self.servers.first()
instance_id = server.id
flavor_id = server.flavor["id"]
flavors = self.flavors.list()
full_flavors = SortedDict([(f.id, f) for f in flavors])
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest))\
.MultipleTimes().AndReturn(True)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'security-group')\
.MultipleTimes().AndReturn(True)
api.nova.server_get(IsA(http.HttpRequest), instance_id)\
.AndReturn(server)
api.nova.flavor_get(IsA(http.HttpRequest), flavor_id)\
.AndReturn(full_flavors[flavor_id])
self.mox.ReplayAll()
params = {'action': 'row_update',
'table': 'instances',
'obj_id': instance_id,
}
res = self.client.get('?'.join((INDEX_URL, urlencode(params))),
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertContains(res, server.name)
@test.create_stubs({api.nova: ("server_get",
"flavor_get",
"extension_supported"),
api.neutron: ("is_extension_supported",)})
def test_row_update_instance_error(self):
server = self.servers.first()
instance_id = server.id
flavor_id = server.flavor["id"]
flavors = self.flavors.list()
full_flavors = SortedDict([(f.id, f) for f in flavors])
server.status = 'ERROR'
server.fault = {"message": "NoValidHost",
"code": 500,
"details": "No valid host was found. \n "
"File \"/mnt/stack/nova/nova/"
"scheduler/filter_scheduler.py\", "
"line 105, in schedule_run_instance\n "
"raise exception.NoValidHost"
"(reason=\"\")\n",
"created": "2013-10-07T00:08:32Z"}
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest))\
.MultipleTimes().AndReturn(True)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'security-group')\
.MultipleTimes().AndReturn(True)
api.nova.server_get(IsA(http.HttpRequest), instance_id)\
.AndReturn(server)
api.nova.flavor_get(IsA(http.HttpRequest), flavor_id)\
.AndReturn(full_flavors[flavor_id])
self.mox.ReplayAll()
params = {'action': 'row_update',
'table': 'instances',
'obj_id': instance_id,
}
res = self.client.get('?'.join((INDEX_URL, urlencode(params))),
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertContains(res, server.name)
self.assertTrue(res.has_header('X-Horizon-Messages'))
messages = json.loads(res['X-Horizon-Messages'])
self.assertEqual(len(messages), 1)
# (Pdb) messages
# [[u'error', u'Failed to launch instance "server_1": \
# There is not enough capacity for this flavor in the \
# selected availability zone. Try again later or select \
# a different availability zone.', u'']]
self.assertEqual(messages[0][0], 'error')
self.assertTrue(messages[0][1].startswith('Failed'))