Merge "Use Placement API along with the hypervisor stats"

This commit is contained in:
Zuul 2024-01-04 13:45:55 +00:00 committed by Gerrit Code Review
commit 108c8c2043
7 changed files with 226 additions and 23 deletions

View File

@ -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",
]

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -6,14 +6,6 @@
{% block main %}
<div class="quota-dynamic">
<h3>{% trans "Hypervisor Summary" %}</h3>
<div class="col-sm-4 d3_quota_bar">
<div class="pie-chart-usage" data-used="{% widthratio stats.vcpus_used stats.vcpus 100 %}"></div>
<div class="h5">{% trans "VCPU Usage" %}</div>
<div class="h6">
{% blocktrans with used=stats.vcpus_used|intcomma available=stats.vcpus|intcomma %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</div>
</div>
<div class="col-sm-4 d3_quota_bar">
<div class="pie-chart-usage" data-used="{% widthratio stats.memory_mb_used stats.memory_mb 100 %}"></div>
<div class="h5">{% trans "Memory Usage" %}</div>
@ -30,6 +22,47 @@
</div>
</div>
</div>
<div class="quota-dynamic">
<h3>{% trans "Resource Providers Summary" %}</h3>
{% for provider in providers %}
<h4>{{ provider.name }}</h4>
<div class="col-sm-4 d3_quota_bar col-lg-3 col-md-2 col-xs-4">
<div class="pie-chart-usage" data-used="{% widthratio provider.usages.VCPU provider.inventories.VCPU.total 100 %}"></div>
<div class="h5">{% trans "VCPU Usage" %}</div>
{% if provider.inventories.VCPU != None %}
<div class="h6">
{% blocktrans with used=provider.usages.VCPU|intcomma available=provider.inventories.VCPU.total|intcomma %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</div>
{% endif %}
</div>
<div class="col-sm-4 d3_quota_bar col-lg-3 col-md-2 col-xs-4">
<div class="pie-chart-usage" data-used="{% widthratio provider.usages.PCPU provider.inventories.PCPU.total 100 %}"></div>
<div class="h5">{% trans "PCPU Usage" %}</div>
{% if provider.inventories.PCPU != None %}
<div class="h6">
{% blocktrans with used=provider.usages.PCPU|intcomma available=provider.inventories.PCPU.total|intcomma %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</div>
{% endif %}
</div>
<div class="col-sm-4 d3_quota_bar col-lg-3 col-md-2 col-xs-4">
<div class="pie-chart-usage" data-used="{% widthratio provider.usages.MEMORY_MB provider.inventories.MEMORY_MB.total 100 %}"></div>
<div class="h5">{% trans "Memory Usage" %}</div>
<div class="h6">
{% blocktrans with used=provider.usages.MEMORY_MB|mb_float_format available=provider.inventories.MEMORY_MB.total|mb_float_format %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</div>
</div>
<div class="col-sm-4 d3_quota_bar col-lg-3 col-md-2 col-xs-4">
<div class="pie-chart-usage" data-used="{% widthratio provider.usages.DISK_GB provider.inventories.DISK_GB.total 100 %}"></div>
<div class="h5">{% trans "Local Disk Usage" %}</div>
<div class="h6">
{% blocktrans with used=provider.usages.DISK_GB|diskgbformat available=provider.inventories.DISK_GB.total|diskgbformat %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</div>
</div>
{% endfor %}
</div>
<div class="row-fluid">
<div class="col-sm-12">
{{ tab_group.render }}

View File

@ -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())

View File

@ -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