Need ability to evacuate host in syspanel
Implement host evacuate in the hypervisors panel. An extra tab was added to show in the first one hypervisors and on the second one compute host. on each compute host that is down an evacuate host button was added. If the user press the button a modal windows is shown to request the needed data to perform the evacuation. blueprint evacuate-host Co-Authored-By: Leandro Costantino <leandro.i.costantino@intel.com> Co-Authored-By: David Lyle <david.lyle@hp.com> Change-Id: I57a16f99fddd84c287429085c7e90beb59a17aa3
This commit is contained in:
parent
c268f36350
commit
a286e558c6
@ -120,6 +120,24 @@ class Server(base.APIResourceWrapper):
|
||||
return getattr(self, 'OS-EXT-AZ:availability_zone', "")
|
||||
|
||||
|
||||
class Hypervisor(base.APIDictWrapper):
|
||||
"""Simple wrapper around novaclient.hypervisors.Hypervisor."""
|
||||
|
||||
_attrs = ['manager', '_loaded', '_info', 'hypervisor_hostname', 'id',
|
||||
'servers']
|
||||
|
||||
@property
|
||||
def servers(self):
|
||||
# if hypervisor doesn't have servers, the attribute is not present
|
||||
servers = []
|
||||
try:
|
||||
servers = self._apidict.servers
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return servers
|
||||
|
||||
|
||||
class NovaUsage(base.APIResourceWrapper):
|
||||
"""Simple wrapper around contrib/simple_usage.py."""
|
||||
|
||||
@ -707,6 +725,32 @@ def hypervisor_search(request, query, servers=True):
|
||||
return novaclient(request).hypervisors.search(query, servers)
|
||||
|
||||
|
||||
def evacuate_host(request, host, target=None, on_shared_storage=False):
|
||||
# TODO(jmolle) This should be change for nova atomic api host_evacuate
|
||||
hypervisors = novaclient(request).hypervisors.search(host, True)
|
||||
response = []
|
||||
err_code = None
|
||||
for hypervisor in hypervisors:
|
||||
hyper = Hypervisor(hypervisor)
|
||||
# if hypervisor doesn't have servers, the attribute is not present
|
||||
for server in hyper.servers:
|
||||
try:
|
||||
novaclient(request).servers.evacuate(server['uuid'],
|
||||
target,
|
||||
on_shared_storage)
|
||||
except nova_exceptions.ClientException as err:
|
||||
err_code = err.code
|
||||
msg = _("Name: %(name)s ID: %(uuid)s")
|
||||
msg = msg % {'name': server['name'], 'uuid': server['uuid']}
|
||||
response.append(msg)
|
||||
|
||||
if err_code:
|
||||
msg = _('Failed to evacuate instances: %s') % ', '.join(response)
|
||||
raise nova_exceptions.ClientException(err_code, msg)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def tenant_absolute_limits(request, reserved=False):
|
||||
limits = novaclient(request).limits.get(reserved=reserved).absolute
|
||||
limits_dict = {}
|
||||
@ -723,8 +767,8 @@ def availability_zone_list(request, detailed=False):
|
||||
return novaclient(request).availability_zones.list(detailed=detailed)
|
||||
|
||||
|
||||
def service_list(request):
|
||||
return novaclient(request).services.list()
|
||||
def service_list(request, binary=None):
|
||||
return novaclient(request).services.list(binary=binary)
|
||||
|
||||
|
||||
def aggregate_details_list(request):
|
||||
|
@ -0,0 +1,68 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
class EvacuateHostForm(forms.SelfHandlingForm):
|
||||
|
||||
current_host = forms.CharField(label=_("Current Host"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
target_host = forms.ChoiceField(label=_("Target Host"),
|
||||
help_text=_("Choose a Host to evacuate servers to."))
|
||||
|
||||
on_shared_storage = forms.BooleanField(label=_("Shared Storage"),
|
||||
initial=False, required=False)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(EvacuateHostForm, self).__init__(request, *args, **kwargs)
|
||||
initial = kwargs.get('initial', {})
|
||||
self.fields['target_host'].choices = \
|
||||
self.populate_host_choices(request, initial)
|
||||
|
||||
def populate_host_choices(self, request, initial):
|
||||
hosts = initial.get('hosts')
|
||||
current_host = initial.get('current_host')
|
||||
host_list = sorted([(host, host)
|
||||
for host in hosts
|
||||
if host != current_host])
|
||||
if host_list:
|
||||
host_list.insert(0, ("", _("Select a target host")))
|
||||
else:
|
||||
host_list.insert(0, ("", _("No other hosts available.")))
|
||||
return host_list
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
current_host = data['current_host']
|
||||
target_host = data['target_host']
|
||||
on_shared_storage = data['on_shared_storage']
|
||||
api.nova.evacuate_host(request, current_host,
|
||||
target_host, on_shared_storage)
|
||||
|
||||
msg = _('Starting evacuation from %(current)s to %(target)s.') % \
|
||||
{'current': current_host, 'target': target_host}
|
||||
messages.success(request, msg)
|
||||
return True
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:hypervisors:index')
|
||||
msg = _('Failed to evacuate host: %s.') % data['current_host']
|
||||
exceptions.handle(request, message=msg, redirect=redirect)
|
||||
return False
|
@ -0,0 +1,69 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.template import defaultfilters as filters
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
from horizon.utils import filters as utils_filters
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
class EvacuateHost(tables.LinkAction):
|
||||
name = "evacuate"
|
||||
data_type_singular = _("Host")
|
||||
data_type_plural = _("Hosts")
|
||||
verbose_name = _("Evacuate Host")
|
||||
url = "horizon:admin:hypervisors:compute:evacuate_host"
|
||||
classes = ("ajax-modal", "btn-migrate")
|
||||
policy_rules = (("compute", "compute_extension:evacuate"),)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(EvacuateHost, self).__init__(**kwargs)
|
||||
self.name = kwargs.get('name', self.name)
|
||||
self.action_present = kwargs.get('action_present', _("Evacuate"))
|
||||
self.action_past = kwargs.get('action_past', _("Evacuated"))
|
||||
|
||||
def allowed(self, request, instance):
|
||||
if not api.nova.extension_supported('AdminActions', request):
|
||||
return False
|
||||
|
||||
return self.datum.state == "down"
|
||||
|
||||
|
||||
class ComputeHostFilterAction(tables.FilterAction):
|
||||
def filter(self, table, services, filter_string):
|
||||
q = filter_string.lower()
|
||||
|
||||
return filter(lambda service: q in service.type.lower(), services)
|
||||
|
||||
|
||||
class ComputeHostTable(tables.DataTable):
|
||||
host = tables.Column('host', verbose_name=_('Host'))
|
||||
zone = tables.Column('zone', verbose_name=_('Zone'))
|
||||
status = tables.Column('status', verbose_name=_('Status'))
|
||||
state = tables.Column('state', verbose_name=_('State'))
|
||||
updated_at = tables.Column('updated_at',
|
||||
verbose_name=_('Updated At'),
|
||||
filters=(utils_filters.parse_isotime,
|
||||
filters.timesince))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.host
|
||||
|
||||
class Meta:
|
||||
name = "compute_host"
|
||||
verbose_name = _("Compute Host")
|
||||
table_actions = (ComputeHostFilterAction,)
|
||||
multi_select = False
|
||||
row_actions = (EvacuateHost,)
|
@ -0,0 +1,35 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard.api import nova
|
||||
from openstack_dashboard.dashboards.admin.hypervisors.compute import tables
|
||||
|
||||
|
||||
class ComputeHostTab(tabs.TableTab):
|
||||
table_classes = (tables.ComputeHostTable,)
|
||||
name = _("Compute Host")
|
||||
slug = "compute_host"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_compute_host_data(self):
|
||||
try:
|
||||
services = nova.service_list(self.tab_group.request)
|
||||
return [service for service in services
|
||||
if service.binary == 'nova-compute']
|
||||
except Exception:
|
||||
msg = _('Unable to get nova services list.')
|
||||
exceptions.handle(self.tab_group.request, msg)
|
@ -0,0 +1,97 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http
|
||||
from mox import IsA # noqa
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
|
||||
class EvacuateHostViewTest(test.BaseAdminViewTests):
|
||||
@test.create_stubs({api.nova: ('hypervisor_list',
|
||||
'hypervisor_stats',
|
||||
'service_list')})
|
||||
def test_index(self):
|
||||
hypervisor = self.hypervisors.list().pop().hypervisor_hostname
|
||||
services = [service for service in self.services.list()
|
||||
if service.binary == 'nova-compute']
|
||||
api.nova.service_list(IsA(http.HttpRequest),
|
||||
binary='nova-compute').AndReturn(services)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:admin:hypervisors:compute:evacuate_host',
|
||||
args=[hypervisor])
|
||||
res = self.client.get(url)
|
||||
self.assertTemplateUsed(res,
|
||||
'admin/hypervisors/compute/evacuate_host.html')
|
||||
|
||||
@test.create_stubs({api.nova: ('hypervisor_list',
|
||||
'hypervisor_stats',
|
||||
'service_list',
|
||||
'evacuate_host')})
|
||||
def test_successful_post(self):
|
||||
hypervisor = self.hypervisors.list().pop().hypervisor_hostname
|
||||
services = [service for service in self.services.list()
|
||||
if service.binary == 'nova-compute']
|
||||
|
||||
api.nova.service_list(IsA(http.HttpRequest),
|
||||
binary='nova-compute').AndReturn(services)
|
||||
api.nova.evacuate_host(IsA(http.HttpRequest),
|
||||
services[1].host,
|
||||
services[0].host,
|
||||
False).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:admin:hypervisors:compute:evacuate_host',
|
||||
args=[hypervisor])
|
||||
|
||||
form_data = {'current_host': services[1].host,
|
||||
'target_host': services[0].host,
|
||||
'on_shared_storage': False}
|
||||
|
||||
res = self.client.post(url, form_data)
|
||||
dest_url = reverse('horizon:admin:hypervisors:index')
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertMessageCount(success=1)
|
||||
self.assertRedirectsNoFollow(res, dest_url)
|
||||
|
||||
@test.create_stubs({api.nova: ('hypervisor_list',
|
||||
'hypervisor_stats',
|
||||
'service_list',
|
||||
'evacuate_host')})
|
||||
def test_failing_nova_call_post(self):
|
||||
hypervisor = self.hypervisors.list().pop().hypervisor_hostname
|
||||
services = [service for service in self.services.list()
|
||||
if service.binary == 'nova-compute']
|
||||
|
||||
api.nova.service_list(IsA(http.HttpRequest),
|
||||
binary='nova-compute').AndReturn(services)
|
||||
api.nova.evacuate_host(IsA(http.HttpRequest),
|
||||
services[1].host,
|
||||
services[0].host,
|
||||
False).AndRaise(self.exceptions.nova)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:admin:hypervisors:compute:evacuate_host',
|
||||
args=[hypervisor])
|
||||
|
||||
form_data = {'current_host': services[1].host,
|
||||
'target_host': services[0].host,
|
||||
'on_shared_storage': False}
|
||||
|
||||
res = self.client.post(url, form_data)
|
||||
dest_url = reverse('horizon:admin:hypervisors:index')
|
||||
self.assertMessageCount(error=1)
|
||||
self.assertRedirectsNoFollow(res, dest_url)
|
@ -0,0 +1,24 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import patterns # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.hypervisors.compute import views
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'openstack_dashboard.dashboards.admin.hypervisors.compute.views',
|
||||
url(r'^(?P<compute_host>[^/]+)/evacuate_host$',
|
||||
views.EvacuateHostView.as_view(),
|
||||
name='evacuate_host'),
|
||||
)
|
@ -0,0 +1,53 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.hypervisors.compute \
|
||||
import forms as project_forms
|
||||
|
||||
|
||||
class EvacuateHostView(forms.ModalFormView):
|
||||
form_class = project_forms.EvacuateHostForm
|
||||
template_name = 'admin/hypervisors/compute/evacuate_host.html'
|
||||
context_object_name = 'compute_host'
|
||||
success_url = reverse_lazy("horizon:admin:hypervisors:index")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EvacuateHostView, self).get_context_data(**kwargs)
|
||||
context["compute_host"] = self.kwargs['compute_host']
|
||||
return context
|
||||
|
||||
def get_active_compute_hosts_names(self, *args, **kwargs):
|
||||
try:
|
||||
services = api.nova.service_list(self.request,
|
||||
binary='nova-compute')
|
||||
return [service.host for service in services
|
||||
if service.state == 'up']
|
||||
except Exception:
|
||||
redirect = reverse("horizon:admin:hypervisors:index")
|
||||
msg = _('Unable to retrieve compute host information.')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(EvacuateHostView, self).get_initial()
|
||||
hosts = self.get_active_compute_hosts_names()
|
||||
current_host = self.kwargs['compute_host']
|
||||
initial.update({'current_host': current_host,
|
||||
'hosts': hosts})
|
||||
return initial
|
44
openstack_dashboard/dashboards/admin/hypervisors/tabs.py
Normal file
44
openstack_dashboard/dashboards/admin/hypervisors/tabs.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard.api import nova
|
||||
from openstack_dashboard.dashboards.admin.hypervisors.compute \
|
||||
import tabs as cmp_tabs
|
||||
from openstack_dashboard.dashboards.admin.hypervisors import tables
|
||||
|
||||
|
||||
class HypervisorTab(tabs.TableTab):
|
||||
table_classes = (tables.AdminHypervisorsTable,)
|
||||
name = _("Hypervisor")
|
||||
slug = "hypervisor"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_hypervisors_data(self):
|
||||
hypervisors = []
|
||||
try:
|
||||
hypervisors = nova.hypervisor_list(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve hypervisor information.'))
|
||||
|
||||
return hypervisors
|
||||
|
||||
|
||||
class HypervisorHostTabs(tabs.TabGroup):
|
||||
slug = "hypervisor_info"
|
||||
tabs = (HypervisorTab, cmp_tabs.ComputeHostTab)
|
||||
sticky = True
|
@ -0,0 +1,25 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}evacuate_host_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:hypervisors:compute:evacuate_host' compute_host %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Evacuate Host" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "Evacuate the servers from the selected down host to an active target host." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Evacuate Host" %}" />
|
||||
<a href="{% url 'horizon:admin:hypervisors:index' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Evacuate Host" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Evacuate Host") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/hypervisors/compute/_evacuate_host.html' %}
|
||||
{% endblock %}
|
@ -31,5 +31,9 @@
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
{{ table.render }}
|
||||
<div class="row-fluid">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -21,18 +21,39 @@ from openstack_dashboard.test import helpers as test
|
||||
|
||||
|
||||
class HypervisorViewTest(test.BaseAdminViewTests):
|
||||
@test.create_stubs({api.nova: ('hypervisor_list',
|
||||
'hypervisor_stats')})
|
||||
@test.create_stubs({api.nova: ('extension_supported',
|
||||
'hypervisor_list',
|
||||
'hypervisor_stats',
|
||||
'service_list')})
|
||||
def test_index(self):
|
||||
hypervisors = self.hypervisors.list()
|
||||
services = self.services.list()
|
||||
stats = self.hypervisors.stats
|
||||
api.nova.extension_supported('AdminActions',
|
||||
IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(True)
|
||||
api.nova.hypervisor_list(IsA(http.HttpRequest)).AndReturn(hypervisors)
|
||||
api.nova.hypervisor_stats(IsA(http.HttpRequest)).AndReturn(stats)
|
||||
api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:admin:hypervisors:index'))
|
||||
self.assertTemplateUsed(res, 'admin/hypervisors/index.html')
|
||||
self.assertItemsEqual(res.context['table'].data, hypervisors)
|
||||
|
||||
hypervisors_tab = res.context['tab_group'].get_tab('hypervisor')
|
||||
self.assertItemsEqual(hypervisors_tab._tables['hypervisors'].data,
|
||||
hypervisors)
|
||||
|
||||
host_tab = res.context['tab_group'].get_tab('compute_host')
|
||||
host_table = host_tab._tables['compute_host']
|
||||
compute_services = [service for service in services
|
||||
if service.binary == 'nova-compute']
|
||||
self.assertItemsEqual(host_table.data, compute_services)
|
||||
actions_host_up = host_table.get_row_actions(host_table.data[0])
|
||||
self.assertEqual(0, len(actions_host_up))
|
||||
actions_host_down = host_table.get_row_actions(host_table.data[1])
|
||||
self.assertEqual(1, len(actions_host_down))
|
||||
self.assertEqual('evacuate', actions_host_down[0].name)
|
||||
|
||||
|
||||
class HypervisorDetailViewTest(test.BaseAdminViewTests):
|
||||
|
@ -12,9 +12,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import include # noqa
|
||||
from django.conf.urls import patterns # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.hypervisors.compute \
|
||||
import urls as compute_urls
|
||||
from openstack_dashboard.dashboards.admin.hypervisors import views
|
||||
|
||||
|
||||
@ -23,5 +26,6 @@ urlpatterns = patterns(
|
||||
url(r'^(?P<hypervisor>[^/]+)/$',
|
||||
views.AdminDetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^$', views.AdminIndexView.as_view(), name='index')
|
||||
url(r'^$', views.AdminIndexView.as_view(), name='index'),
|
||||
url(r'', include(compute_urls, namespace='compute')),
|
||||
)
|
||||
|
@ -16,14 +16,18 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from horizon import tabs
|
||||
from horizon.utils import functions as utils
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.hypervisors \
|
||||
import tables as project_tables
|
||||
from openstack_dashboard.dashboards.admin.hypervisors \
|
||||
import tabs as project_tabs
|
||||
|
||||
|
||||
class AdminIndexView(tables.DataTableView):
|
||||
table_class = project_tables.AdminHypervisorsTable
|
||||
class AdminIndexView(tabs.TabbedTableView):
|
||||
tab_group_class = project_tabs.HypervisorHostTabs
|
||||
template_name = 'admin/hypervisors/index.html'
|
||||
|
||||
def get_data(self):
|
||||
|
@ -679,8 +679,20 @@ def data(TEST):
|
||||
"host": "devstack001",
|
||||
"disabled_reason": None,
|
||||
})
|
||||
|
||||
service_3 = services.Service(services.ServiceManager(None), {
|
||||
"status": "enabled",
|
||||
"binary": "nova-compute",
|
||||
"zone": "nova",
|
||||
"state": "down",
|
||||
"updated_at": "2013-07-08T04:20:51.000000",
|
||||
"host": "devstack002",
|
||||
"disabled_reason": None,
|
||||
})
|
||||
|
||||
TEST.services.add(service_1)
|
||||
TEST.services.add(service_2)
|
||||
TEST.services.add(service_3)
|
||||
|
||||
# Aggregates
|
||||
aggregate_1 = aggregates.Aggregate(aggregates.AggregateManager(None), {
|
||||
|
Loading…
Reference in New Issue
Block a user