diff --git a/openstack_dashboard/api/__init__.py b/openstack_dashboard/api/__init__.py index 9483b39414..b7ea554a12 100644 --- a/openstack_dashboard/api/__init__.py +++ b/openstack_dashboard/api/__init__.py @@ -38,6 +38,7 @@ from openstack_dashboard.api import keystone from openstack_dashboard.api import network from openstack_dashboard.api import neutron from openstack_dashboard.api import nova +from openstack_dashboard.api import placement from openstack_dashboard.api import swift @@ -49,5 +50,6 @@ __all__ = [ "network", "neutron", "nova", + "placement", "swift", ] diff --git a/openstack_dashboard/api/placement.py b/openstack_dashboard/api/placement.py new file mode 100644 index 0000000000..532d96465b --- /dev/null +++ b/openstack_dashboard/api/placement.py @@ -0,0 +1,110 @@ +# 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 keystoneauth1 import adapter +from keystoneauth1 import identity +from keystoneauth1 import session + +from openstack_dashboard.api import base + +from horizon.utils.memoized import memoized + + +class Adapter(adapter.LegacyJsonAdapter): + def __init__(self, *args, **kwargs): + self.api_version = kwargs.pop('api_version', None) + super().__init__(*args, **kwargs) + + def request(self, url, method, **kwargs): + kwargs.setdefault('headers', kwargs.get('headers', {})) + if self.api_version is not None: + kwargs['headers']['OpenStack-API-Version'] = self.api_version + resp, body = super().request(url, method, **kwargs) + return resp, body + + +@memoized +def make_adapter(request): + auth = identity.Token( + auth_url=base.url_for(request, 'identity'), + token=request.user.token.id, + project_id=request.user.project_id, + project_name=request.user.project_name, + project_domain_name=request.user.domain_id, + ) + return Adapter(session.Session(auth=auth), api_version="placement 1.6") + + +def _get_json(request, path): + adapter = make_adapter(request) + uri = base.url_for(request, 'placement') + path + response, body = adapter.get(uri) + return response.json() + + +def get_versions(request): + versions = _get_json(request, '/') + return versions + + +def resource_providers(request): + providers = _get_json(request, '/resource_providers') + return providers['resource_providers'] + + +def get_providers_uuids(request): + providers = resource_providers(request) + return [p['uuid'] for p in providers] + + +def resource_provider_inventories(request, uuid): + return _get_json( + request, f'/resource_providers/{uuid}/inventories')['inventories'] + + +def resource_provider_usages(request, uuid): + return _get_json(request, f'/resource_providers/{uuid}/usages')['usages'] + + +def resource_provider_aggregates(request, uuid): + return _get_json( + request, f'/resource_providers/{uuid}/aggregates')['aggregates'] + + +def resource_provider_traits(request, uuid): + return _get_json(request, f'/resource_providers/{uuid}/traits')['traits'] + + +def get_providers(request): + providers = resource_providers(request) + for p in providers: + inventories = resource_provider_inventories(request, p['uuid']) + usages = resource_provider_usages(request, p['uuid']) + vcpus = inventories.get('VCPU') + pcpus = inventories.get('PCPU') + p['inventories'] = inventories + p['usages'] = usages + p['aggregates'] = resource_provider_aggregates(request, p['uuid']) + p['traits'] = resource_provider_traits(request, p['uuid']) + p['vcpus_used'] = usages.get('VCPU') + p['vcpus_reserved'] = vcpus['reserved'] if vcpus is not None else None + p['vcpus'] = vcpus['total'] if vcpus is not None else None + p['pcpus_used'] = usages.get('PCPU') + p['pcpus_reserved'] = pcpus['reserved'] if pcpus is not None else None + p['pcpus'] = pcpus['total'] if pcpus is not None else None + p['memory_mb_used'] = usages['MEMORY_MB'] + p['memory_mb_reserved'] = inventories['MEMORY_MB']['reserved'] + p['memory_mb'] = inventories['MEMORY_MB']['total'] + p['disk_gb_used'] = usages['DISK_GB'] + p['disk_gb_reserved'] = inventories['DISK_GB']['reserved'] + p['disk_gb'] = inventories['DISK_GB']['total'] + return providers diff --git a/openstack_dashboard/dashboards/admin/hypervisors/tables.py b/openstack_dashboard/dashboards/admin/hypervisors/tables.py index 0a36139be2..83e35fb94b 100644 --- a/openstack_dashboard/dashboards/admin/hypervisors/tables.py +++ b/openstack_dashboard/dashboards/admin/hypervisors/tables.py @@ -22,36 +22,24 @@ class AdminHypervisorsTable(tables.DataTable): hostname = tables.WrappingColumn("hypervisor_hostname", link="horizon:admin:hypervisors:detail", verbose_name=_("Hostname")) - hypervisor_type = tables.Column("hypervisor_type", verbose_name=_("Type")) - - vcpus_used = tables.Column("vcpus_used", - verbose_name=_("VCPUs (used)")) - - vcpus = tables.Column("vcpus", - verbose_name=_("VCPUs (total)")) - memory_used = tables.Column('memory_mb_used', verbose_name=_("RAM (used)"), attrs={'data-type': 'size'}, filters=(sizeformat.mb_float_format,)) - memory = tables.Column('memory_mb', verbose_name=_("RAM (total)"), attrs={'data-type': 'size'}, filters=(sizeformat.mb_float_format,)) - local_used = tables.Column('local_gb_used', verbose_name=_("Local Storage (used)"), attrs={'data-type': 'size'}, filters=(sizeformat.diskgbformat,)) - local = tables.Column('local_gb', verbose_name=_("Local Storage (total)"), attrs={'data-type': 'size'}, filters=(sizeformat.diskgbformat,)) - running_vms = tables.Column("running_vms", verbose_name=_("Instances")) @@ -78,3 +66,51 @@ class AdminHypervisorInstancesTable(tables.DataTable): class Meta(object): name = "hypervisor_instances" verbose_name = _("Hypervisor Instances") + + +class AdminProvidersTable(tables.DataTable): + name = tables.WrappingColumn("name", + verbose_name=_("Resource Provider Name")) + vcpus_used = tables.Column("vcpus_used", + verbose_name=_("VCPUs (used)")) + vcpus_reserved = tables.Column("vcpus_reserved", + verbose_name=_("VCPUs (reserved)")) + vcpus = tables.Column("vcpus", + verbose_name=_("VCPUs (total)")) + pcpus_used = tables.Column("pcpus_used", + verbose_name=_("PCPUs (used)")) + pcpus_reserved = tables.Column("pcpus_reserved", + verbose_name=_("PCPUs (reserved)")) + pcpus = tables.Column("pcpus", + verbose_name=_("PCPUs (total)")) + memory_used = tables.Column('memory_mb_used', + verbose_name=_("RAM (used)"), + attrs={'data-type': 'size'}, + filters=(sizeformat.mb_float_format,)) + memory_reserved = tables.Column('memory_mb_reserved', + verbose_name=_("RAM (reserved)"), + attrs={'data-type': 'size'}, + filters=(sizeformat.mb_float_format,)) + memory = tables.Column('memory_mb', + verbose_name=_("RAM (total)"), + attrs={'data-type': 'size'}, + filters=(sizeformat.mb_float_format,)) + disk_used = tables.Column('disk_gb_used', + verbose_name=_("Storage (used)"), + attrs={'data-type': 'size'}, + filters=(sizeformat.diskgbformat,)) + disk_reserved = tables.Column('disk_gb_reserved', + verbose_name=_("Storage (reserved)"), + attrs={'data-type': 'size'}, + filters=(sizeformat.diskgbformat,)) + disk = tables.Column('disk_gb', + verbose_name=_("Storage (total)"), + attrs={'data-type': 'size'}, + filters=(sizeformat.diskgbformat,)) + + def get_object_id(self, provider): + return provider['uuid'] + + class Meta(object): + name = "providers" + verbose_name = _("Resource Providers") diff --git a/openstack_dashboard/dashboards/admin/hypervisors/tabs.py b/openstack_dashboard/dashboards/admin/hypervisors/tabs.py index 4b08c0a936..6b8bdf89ad 100644 --- a/openstack_dashboard/dashboards/admin/hypervisors/tabs.py +++ b/openstack_dashboard/dashboards/admin/hypervisors/tabs.py @@ -17,6 +17,7 @@ from horizon import tabs from horizon.utils import functions as utils from openstack_dashboard.api import nova +from openstack_dashboard.api import placement from openstack_dashboard.dashboards.admin.hypervisors.compute \ import tabs as cmp_tabs from openstack_dashboard.dashboards.admin.hypervisors import tables @@ -40,7 +41,24 @@ class HypervisorTab(tabs.TableTab): return hypervisors +class ProviderTab(tabs.TableTab): + table_classes = (tables.AdminProvidersTable,) + name = _("Resource Provider") + slug = "provider" + template_name = "horizon/common/_detail_table.html" + + def get_providers_data(self): + providers = [] + try: + providers = placement.get_providers(self.request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve providers information.')) + + return providers + + class HypervisorHostTabs(tabs.TabGroup): slug = "hypervisor_info" - tabs = (HypervisorTab, cmp_tabs.ComputeHostTab) + tabs = (HypervisorTab, cmp_tabs.ComputeHostTab, ProviderTab) sticky = True diff --git a/openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html b/openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html index 4421794b6e..7b4f750895 100644 --- a/openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html +++ b/openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html @@ -6,14 +6,6 @@ {% block main %}

{% trans "Hypervisor Summary" %}

-
-
-
{% trans "VCPU Usage" %}
-
- {% blocktrans with used=stats.vcpus_used|intcomma available=stats.vcpus|intcomma %}Used {{ used }} of {{ available }} {% endblocktrans %} -
-
-
{% trans "Memory Usage" %}
@@ -30,6 +22,47 @@
+
+

{% trans "Resource Providers Summary" %}

+ {% for provider in providers %} +

{{ provider.name }}

+
+
+
{% trans "VCPU Usage" %}
+ {% if provider.inventories.VCPU != None %} +
+ {% blocktrans with used=provider.usages.VCPU|intcomma available=provider.inventories.VCPU.total|intcomma %}Used {{ used }} of {{ available }} {% endblocktrans %} +
+ {% endif %} +
+ +
+
+
{% trans "PCPU Usage" %}
+ {% if provider.inventories.PCPU != None %} +
+ {% blocktrans with used=provider.usages.PCPU|intcomma available=provider.inventories.PCPU.total|intcomma %}Used {{ used }} of {{ available }} {% endblocktrans %} +
+ {% endif %} +
+ +
+
+
{% trans "Memory Usage" %}
+
+ {% blocktrans with used=provider.usages.MEMORY_MB|mb_float_format available=provider.inventories.MEMORY_MB.total|mb_float_format %}Used {{ used }} of {{ available }} {% endblocktrans %} +
+
+ +
+
+
{% trans "Local Disk Usage" %}
+
+ {% blocktrans with used=provider.usages.DISK_GB|diskgbformat available=provider.inventories.DISK_GB.total|diskgbformat %}Used {{ used }} of {{ available }} {% endblocktrans %} +
+
+ {% endfor %} +
{{ tab_group.render }} diff --git a/openstack_dashboard/dashboards/admin/hypervisors/tests.py b/openstack_dashboard/dashboards/admin/hypervisors/tests.py index e146f84fc6..46640d4e43 100644 --- a/openstack_dashboard/dashboards/admin/hypervisors/tests.py +++ b/openstack_dashboard/dashboards/admin/hypervisors/tests.py @@ -76,7 +76,7 @@ class HypervisorViewTest(test.BaseAdminViewTests): self.mock_service_list.side_effect = self.exceptions.nova resp = self.client.get(reverse('horizon:admin:hypervisors:index')) - self.assertMessageCount(resp, error=1, warning=0) + self.assertMessageCount(resp, error=2, warning=0) self.mock_hypervisor_list.assert_called_once_with( test.IsHttpRequest()) diff --git a/openstack_dashboard/dashboards/admin/hypervisors/views.py b/openstack_dashboard/dashboards/admin/hypervisors/views.py index 643b076956..6447ccfd06 100644 --- a/openstack_dashboard/dashboards/admin/hypervisors/views.py +++ b/openstack_dashboard/dashboards/admin/hypervisors/views.py @@ -37,7 +37,11 @@ class AdminIndexView(tabs.TabbedTableView): except Exception: exceptions.handle(self.request, _('Unable to retrieve hypervisor statistics.')) - + try: + context["providers"] = api.placement.get_providers(self.request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve providers statistics.')) return context