horizon/openstack_dashboard/api/nova.py
KeithMnemonic 9637d73374 Fix "prev" link pagination for instances with identical timestamps
This patch resolves an issue with the "prev" link when instances
have identical "created_at" values. This can occur when creating
instance using the "min/max count" option. The reverse sort does not
work correctly as the server list returned from nova is not an exact
reverse as the forward sort. It looks like the combination of sort_keys
must be unique to ensure the forward and reverse pagination properly.
As a workaround 'uuid' (server ID) is added to 'sort_keys'.
In addition, 'display_name' is added before 'uuid' in 'sort_keys'
to list servers in the alphabetical order (which sounds natural).

Closes-Bug #1856243
Change-Id: I73234b2c69ce8ea648b4a9721abe4f5670031909
2019-12-25 20:12:03 +09:00

1179 lines
38 KiB
Python

# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 OpenStack Foundation
# Copyright 2012 Nebula, Inc.
# Copyright (c) 2012 X.commerce, a business unit of eBay Inc.
#
# 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 __future__ import absolute_import
import collections
import logging
from operator import attrgetter
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from novaclient import api_versions
from novaclient import exceptions as nova_exceptions
from novaclient.v2 import instance_action as nova_instance_action
from novaclient.v2 import servers as nova_servers
from horizon import exceptions as horizon_exceptions
from horizon.utils import memoized
from openstack_dashboard.api import _nova
from openstack_dashboard.api import base
from openstack_dashboard.api import cinder
from openstack_dashboard.contrib.developer.profiler import api as profiler
from openstack_dashboard.utils import settings as utils
LOG = logging.getLogger(__name__)
# API static values
INSTANCE_ACTIVE_STATE = 'ACTIVE'
VOLUME_STATE_AVAILABLE = "available"
DEFAULT_QUOTA_NAME = 'default'
# python-novaclient 16.0.0 removed the list_extensions module and the
# GET /extensions API is deprecated since Newton. Furthermore, the ability
# to enable/disable compute API extensions was also removed in Newton.
# Therefore we hard-code the list of extensions here until the
# OPENSTACK_NOVA_EXTENSIONS_BLACKLIST setting is no longer used.
EXTENSIONS = (
'AccessIPs',
'AdminActions',
'AdminPassword',
'Agents',
'Aggregates',
'AssistedVolumeSnapshots',
'AttachInterfaces',
'AvailabilityZone',
'BareMetalExtStatus',
'BareMetalNodes',
'BlockDeviceMapping',
'BlockDeviceMappingV2Boot',
'CellCapacities',
'Cells',
'Certificates',
'Cloudpipe',
'CloudpipeUpdate',
'ConfigDrive',
'ConsoleAuthTokens',
'ConsoleOutput',
'Consoles',
'CreateBackup',
'Createserverext',
'DeferredDelete',
'DiskConfig',
'Evacuate',
'ExtendedAvailabilityZone',
'ExtendedEvacuateFindHost',
'ExtendedFloatingIps',
'ExtendedHypervisors',
'ExtendedIps',
'ExtendedIpsMac',
'ExtendedNetworks',
'ExtendedQuotas',
'ExtendedRescueWithImage',
'ExtendedServerAttributes',
'ExtendedServices',
'ExtendedServicesDelete',
'ExtendedStatus',
'ExtendedStatus',
'ExtendedVolumes',
'FixedIPs',
'FlavorAccess',
'FlavorDisabled',
'FlavorExtraData',
'FlavorExtraSpecs',
'FlavorManage',
'FlavorRxtx',
'FlavorSwap',
'FloatingIpDns',
'FloatingIpPools',
'FloatingIps',
'FloatingIpsBulk',
'Fping',
'HideServerAddresses',
'Hosts',
'HypervisorStatus',
'Hypervisors',
'ImageSize',
'InstanceActions',
'Keypairs',
'LockServer',
'MigrateServer',
'Migrations',
'Multinic',
'MultipleCreate',
'NetworkAssociationSupport',
'Networks',
'OSInstanceUsageAuditLog',
'OSTenantNetworks',
'PauseServer',
'Personality',
'PreserveEphemeralOnRebuild',
'QuotaClasses',
'Quotas',
'Rescue',
'SchedulerHints',
'SecurityGroupDefaultRules',
'SecurityGroups',
'ServerDiagnostics',
'ServerExternalEvents',
'ServerGroupQuotas',
'ServerGroups',
'ServerListMultiStatus',
'ServerPassword',
'ServerSortKeys',
'ServerStartStop',
'ServerUsage',
'Services',
'Shelve',
'SimpleTenantUsage',
'SuspendServer',
'UsedLimits',
'UsedLimitsForAdmin',
'UserData',
'UserQuotas',
'VirtualInterfaces',
'VolumeAttachmentUpdate',
'Volumes'
)
get_microversion = _nova.get_microversion
server_get = _nova.server_get
Server = _nova.Server
def is_feature_available(request, features):
return bool(get_microversion(request, features))
class VolumeMultiattachNotSupported(horizon_exceptions.HorizonException):
status_code = 400
class VNCConsole(base.APIDictWrapper):
"""Wrapper for the "console" dictionary.
Returned by the novaclient.servers.get_vnc_console method.
"""
_attrs = ['url', 'type']
class SPICEConsole(base.APIDictWrapper):
"""Wrapper for the "console" dictionary.
Returned by the novaclient.servers.get_spice_console method.
"""
_attrs = ['url', 'type']
class RDPConsole(base.APIDictWrapper):
"""Wrapper for the "console" dictionary.
Returned by the novaclient.servers.get_rdp_console method.
"""
_attrs = ['url', 'type']
class SerialConsole(base.APIDictWrapper):
"""Wrapper for the "console" dictionary.
Returned by the novaclient.servers.get_serial_console method.
"""
_attrs = ['url', 'type']
class MKSConsole(base.APIDictWrapper):
"""Wrapper for the "console" dictionary.
Returned by the novaclient.servers.get_mks_console method.
"""
_attrs = ['url', 'type']
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."""
_attrs = ['start', 'server_usages', 'stop', 'tenant_id',
'total_local_gb_usage', 'total_memory_mb_usage',
'total_vcpus_usage', 'total_hours']
def get_summary(self):
return {'instances': self.total_active_instances,
'memory_mb': self.memory_mb,
'vcpus': self.vcpus,
'vcpu_hours': self.vcpu_hours,
'local_gb': self.local_gb,
'disk_gb_hours': self.disk_gb_hours,
'memory_mb_hours': self.memory_mb_hours}
@property
def total_active_instances(self):
return sum(1 for s in self.server_usages if s['ended_at'] is None)
@property
def vcpus(self):
return sum(s['vcpus'] for s in self.server_usages
if s['ended_at'] is None)
@property
def vcpu_hours(self):
return getattr(self, "total_vcpus_usage", 0)
@property
def local_gb(self):
return sum(s['local_gb'] for s in self.server_usages
if s['ended_at'] is None)
@property
def memory_mb(self):
return sum(s['memory_mb'] for s in self.server_usages
if s['ended_at'] is None)
@property
def disk_gb_hours(self):
return getattr(self, "total_local_gb_usage", 0)
@property
def memory_mb_hours(self):
return getattr(self, "total_memory_mb_usage", 0)
class FlavorExtraSpec(object):
def __init__(self, flavor_id, key, val):
self.flavor_id = flavor_id
self.id = key
self.key = key
self.value = val
class QuotaSet(base.QuotaSet):
# We don't support nova-network, so we exclude nova-network relatd
# quota fields from the response.
ignore_quotas = {
"floating_ips",
"fixed_ips",
"security_groups",
"security_group_rules",
}
def upgrade_api(request, client, version):
"""Ugrade the nova API to the specified version if possible."""
min_ver, max_ver = api_versions._get_server_version_range(client)
if min_ver <= api_versions.APIVersion(version) <= max_ver:
client = _nova.novaclient(request, version)
return client
@profiler.trace
def server_vnc_console(request, instance_id, console_type='novnc'):
nc = _nova.novaclient(request)
console = nc.servers.get_vnc_console(instance_id, console_type)
return VNCConsole(console['console'])
@profiler.trace
def server_spice_console(request, instance_id, console_type='spice-html5'):
nc = _nova.novaclient(request)
console = nc.servers.get_spice_console(instance_id, console_type)
return SPICEConsole(console['console'])
@profiler.trace
def server_rdp_console(request, instance_id, console_type='rdp-html5'):
nc = _nova.novaclient(request)
console = nc.servers.get_rdp_console(instance_id, console_type)
return RDPConsole(console['console'])
@profiler.trace
def server_serial_console(request, instance_id, console_type='serial'):
nc = _nova.novaclient(request)
console = nc.servers.get_serial_console(instance_id, console_type)
return SerialConsole(console['console'])
@profiler.trace
def server_mks_console(request, instance_id, console_type='mks'):
microver = get_microversion(request, "remote_console_mks")
nc = _nova.novaclient(request, microver)
console = nc.servers.get_mks_console(instance_id, console_type)
return MKSConsole(console['remote_console'])
@profiler.trace
def flavor_create(request, name, memory, vcpu, disk, flavorid='auto',
ephemeral=0, swap=0, metadata=None, is_public=True,
rxtx_factor=1):
flavor = _nova.novaclient(request).flavors.create(name, memory, vcpu, disk,
flavorid=flavorid,
ephemeral=ephemeral,
swap=swap,
is_public=is_public,
rxtx_factor=rxtx_factor)
if (metadata):
flavor_extra_set(request, flavor.id, metadata)
return flavor
@profiler.trace
def flavor_delete(request, flavor_id):
_nova.novaclient(request).flavors.delete(flavor_id)
@profiler.trace
def flavor_get(request, flavor_id, get_extras=False):
flavor = _nova.novaclient(request).flavors.get(flavor_id)
if get_extras:
flavor.extras = flavor_get_extras(request, flavor.id, True, flavor)
return flavor
@profiler.trace
@memoized.memoized
def flavor_list(request, is_public=True, get_extras=False):
"""Get the list of available instance sizes (flavors)."""
flavors = _nova.novaclient(request).flavors.list(is_public=is_public)
if get_extras:
for flavor in flavors:
flavor.extras = flavor_get_extras(request, flavor.id, True, flavor)
return flavors
@profiler.trace
def update_pagination(entities, page_size, marker, reversed_order=False):
has_more_data = has_prev_data = False
if len(entities) > page_size:
has_more_data = True
entities.pop()
if marker is not None:
has_prev_data = True
# first page condition when reached via prev back
elif reversed_order and marker is not None:
has_more_data = True
# last page condition
elif marker is not None:
has_prev_data = True
# restore the original ordering here
if reversed_order:
entities.reverse()
return entities, has_more_data, has_prev_data
@profiler.trace
@memoized.memoized
def flavor_list_paged(request, is_public=True, get_extras=False, marker=None,
paginate=False, sort_key="name", sort_dir="desc",
reversed_order=False):
"""Get the list of available instance sizes (flavors)."""
has_more_data = False
has_prev_data = False
if paginate:
if reversed_order:
sort_dir = 'desc' if sort_dir == 'asc' else 'asc'
page_size = utils.get_page_size(request)
flavors = _nova.novaclient(request).flavors.list(is_public=is_public,
marker=marker,
limit=page_size + 1,
sort_key=sort_key,
sort_dir=sort_dir)
flavors, has_more_data, has_prev_data = update_pagination(
flavors, page_size, marker, reversed_order)
else:
flavors = _nova.novaclient(request).flavors.list(is_public=is_public)
if get_extras:
for flavor in flavors:
flavor.extras = flavor_get_extras(request, flavor.id, True, flavor)
return (flavors, has_more_data, has_prev_data)
@profiler.trace
@memoized.memoized
def flavor_access_list(request, flavor=None):
"""Get the list of access instance sizes (flavors)."""
return _nova.novaclient(request).flavor_access.list(flavor=flavor)
@profiler.trace
def add_tenant_to_flavor(request, flavor, tenant):
"""Add a tenant to the given flavor access list."""
return _nova.novaclient(request).flavor_access.add_tenant_access(
flavor=flavor, tenant=tenant)
@profiler.trace
def remove_tenant_from_flavor(request, flavor, tenant):
"""Remove a tenant from the given flavor access list."""
return _nova.novaclient(request).flavor_access.remove_tenant_access(
flavor=flavor, tenant=tenant)
@profiler.trace
def flavor_get_extras(request, flavor_id, raw=False, flavor=None):
"""Get flavor extra specs."""
if flavor is None:
flavor = _nova.novaclient(request).flavors.get(flavor_id)
extras = flavor.get_keys()
if raw:
return extras
return [FlavorExtraSpec(flavor_id, key, value) for
key, value in extras.items()]
@profiler.trace
def flavor_extra_delete(request, flavor_id, keys):
"""Unset the flavor extra spec keys."""
flavor = _nova.novaclient(request).flavors.get(flavor_id)
return flavor.unset_keys(keys)
@profiler.trace
def flavor_extra_set(request, flavor_id, metadata):
"""Set the flavor extra spec keys."""
flavor = _nova.novaclient(request).flavors.get(flavor_id)
if (not metadata): # not a way to delete keys
return None
return flavor.set_keys(metadata)
@profiler.trace
def snapshot_create(request, instance_id, name):
return _nova.novaclient(request).servers.create_image(instance_id, name)
@profiler.trace
def keypair_create(request, name, key_type='ssh'):
microversion = get_microversion(request, 'key_types')
return _nova.novaclient(request, microversion).\
keypairs.create(name, key_type=key_type)
@profiler.trace
def keypair_import(request, name, public_key, key_type='ssh'):
microversion = get_microversion(request, 'key_types')
return _nova.novaclient(request, microversion).\
keypairs.create(name, public_key, key_type)
@profiler.trace
def keypair_delete(request, name):
_nova.novaclient(request).keypairs.delete(name)
@profiler.trace
def keypair_list(request):
microversion = get_microversion(request, 'key_type_list')
return _nova.novaclient(request, microversion).keypairs.list()
@profiler.trace
def keypair_get(request, name):
return _nova.novaclient(request).keypairs.get(name)
@profiler.trace
def server_create(request, name, image, flavor, key_name, user_data,
security_groups, block_device_mapping=None,
block_device_mapping_v2=None, nics=None,
availability_zone=None, instance_count=1, admin_pass=None,
disk_config=None, config_drive=None, meta=None,
scheduler_hints=None, description=None):
microversion = get_microversion(request, ("instance_description",
"auto_allocated_network"))
nova_client = _nova.novaclient(request, version=microversion)
# NOTE(amotoki): Handling auto allocated network
# Nova API 2.37 or later, it accepts a special string 'auto' for nics
# which means nova uses a network that is available for a current project
# if one exists and otherwise it creates a network automatically.
# This special handling is processed here as JS side assumes 'nics'
# is a list and it is easiest to handle it here.
if nics:
is_auto_allocate = any(nic.get('net-id') == '__auto_allocate__'
for nic in nics)
if is_auto_allocate:
nics = 'auto'
kwargs = {}
if description is not None:
kwargs['description'] = description
return Server(nova_client.servers.create(
name.strip(), image, flavor, userdata=user_data,
security_groups=security_groups,
key_name=key_name, block_device_mapping=block_device_mapping,
block_device_mapping_v2=block_device_mapping_v2,
nics=nics, availability_zone=availability_zone,
min_count=instance_count, admin_pass=admin_pass,
disk_config=disk_config, config_drive=config_drive,
meta=meta, scheduler_hints=scheduler_hints, **kwargs), request)
@profiler.trace
def server_delete(request, instance_id):
_nova.novaclient(request).servers.delete(instance_id)
# Session is available and consistent for the current view
# among Horizon django servers even in load-balancing setup,
# so only the view listing the servers will recognize it as
# own DeleteInstance action performed. Note that dict is passed
# by reference in python. Quote from django's developer manual:
# " You can read it and write to request.session at any point
# in your view. You can edit it multiple times."
request.session['server_deleted'] = instance_id
def get_novaclient_with_locked_status(request):
microversion = get_microversion(request, "locked_attribute")
return _nova.novaclient(request, version=microversion)
@profiler.trace
def server_list_paged(request,
search_opts=None,
detailed=True,
sort_dir="desc"):
has_more_data = False
has_prev_data = False
nova_client = get_novaclient_with_locked_status(request)
page_size = utils.get_page_size(request)
search_opts = {} if search_opts is None else search_opts
marker = search_opts.get('marker', None)
if not search_opts.get('all_tenants', False):
search_opts['project_id'] = request.user.tenant_id
if search_opts.pop('paginate', False):
reversed_order = sort_dir == "asc"
LOG.debug("Notify received on deleted server: %r",
('server_deleted' in request.session))
deleted = request.session.pop('server_deleted',
None)
view_marker = 'possibly_deleted' if deleted and marker else 'ok'
search_opts['marker'] = deleted if deleted else marker
search_opts['limit'] = page_size + 1
# NOTE(amotoki): It looks like the 'sort_keys' must be unique to make
# the pagination in the nova API works as expected. Multiple servers
# can have a same 'created_at' as its resolution is a second.
# To ensure the uniqueness we add 'uuid' to the sort keys.
# 'display_name' is added before 'uuid' to list servers in the
# alphabetical order.
sort_keys = ['created_at', 'display_name', 'uuid']
servers = [Server(s, request)
for s in nova_client.servers.list(detailed, search_opts,
sort_keys=sort_keys,
sort_dirs=[sort_dir] * 3)]
if view_marker == 'possibly_deleted':
if not servers:
view_marker = 'head_deleted'
reversed_order = False
servers = [Server(s, request)
for s in
nova_client.servers.list(detailed,
search_opts,
sort_keys=sort_keys,
sort_dirs=['desc'] * 3)]
if not servers:
view_marker = 'tail_deleted'
reversed_order = True
servers = [Server(s, request)
for s in
nova_client.servers.list(detailed,
search_opts,
sort_keys=sort_keys,
sort_dirs=['asc'] * 3)]
(servers, has_more_data, has_prev_data) = update_pagination(
servers, page_size, marker, reversed_order)
has_prev_data = (False
if view_marker == 'head_deleted'
else has_prev_data)
has_more_data = (False
if view_marker == 'tail_deleted'
else has_more_data)
else:
servers = [Server(s, request)
for s in nova_client.servers.list(detailed, search_opts)]
return (servers, has_more_data, has_prev_data)
@profiler.trace
def server_list(request, search_opts=None, detailed=True):
(servers, has_more_data, _) = server_list_paged(request,
search_opts,
detailed)
return (servers, has_more_data)
@profiler.trace
def server_console_output(request, instance_id, tail_length=None):
"""Gets console output of an instance."""
nc = _nova.novaclient(request)
return nc.servers.get_console_output(instance_id, length=tail_length)
@profiler.trace
def server_pause(request, instance_id):
_nova.novaclient(request).servers.pause(instance_id)
@profiler.trace
def server_unpause(request, instance_id):
_nova.novaclient(request).servers.unpause(instance_id)
@profiler.trace
def server_suspend(request, instance_id):
_nova.novaclient(request).servers.suspend(instance_id)
@profiler.trace
def server_resume(request, instance_id):
_nova.novaclient(request).servers.resume(instance_id)
@profiler.trace
def server_shelve(request, instance_id):
_nova.novaclient(request).servers.shelve(instance_id)
@profiler.trace
def server_unshelve(request, instance_id):
_nova.novaclient(request).servers.unshelve(instance_id)
@profiler.trace
def server_reboot(request, instance_id, soft_reboot=False):
hardness = nova_servers.REBOOT_HARD
if soft_reboot:
hardness = nova_servers.REBOOT_SOFT
_nova.novaclient(request).servers.reboot(instance_id, hardness)
@profiler.trace
def server_rebuild(request, instance_id, image_id, password=None,
disk_config=None, description=None):
kwargs = {}
if description:
kwargs['description'] = description
nc = _nova.get_novaclient_with_instance_desc(request)
return nc.servers.rebuild(instance_id, image_id, password, disk_config,
**kwargs)
@profiler.trace
def server_update(request, instance_id, name, description=None):
nc = _nova.get_novaclient_with_instance_desc(request)
return nc.servers.update(instance_id, name=name.strip(),
description=description)
@profiler.trace
def server_migrate(request, instance_id):
_nova.novaclient(request).servers.migrate(instance_id)
@profiler.trace
def server_live_migrate(request, instance_id, host, block_migration=False,
disk_over_commit=False):
_nova.novaclient(request).servers.live_migrate(instance_id, host,
block_migration,
disk_over_commit)
@profiler.trace
def server_resize(request, instance_id, flavor, disk_config=None, **kwargs):
_nova.novaclient(request).servers.resize(instance_id, flavor,
disk_config, **kwargs)
@profiler.trace
def server_confirm_resize(request, instance_id):
_nova.novaclient(request).servers.confirm_resize(instance_id)
@profiler.trace
def server_revert_resize(request, instance_id):
_nova.novaclient(request).servers.revert_resize(instance_id)
@profiler.trace
def server_start(request, instance_id):
_nova.novaclient(request).servers.start(instance_id)
@profiler.trace
def server_stop(request, instance_id):
_nova.novaclient(request).servers.stop(instance_id)
@profiler.trace
def server_lock(request, instance_id):
microversion = get_microversion(request, "locked_attribute")
_nova.novaclient(request, version=microversion).servers.lock(instance_id)
@profiler.trace
def server_unlock(request, instance_id):
microversion = get_microversion(request, "locked_attribute")
_nova.novaclient(request, version=microversion).servers.unlock(instance_id)
@profiler.trace
def server_metadata_update(request, instance_id, metadata):
_nova.novaclient(request).servers.set_meta(instance_id, metadata)
@profiler.trace
def server_metadata_delete(request, instance_id, keys):
_nova.novaclient(request).servers.delete_meta(instance_id, keys)
@profiler.trace
def server_rescue(request, instance_id, password=None, image=None):
_nova.novaclient(request).servers.rescue(instance_id,
password=password,
image=image)
@profiler.trace
def server_unrescue(request, instance_id):
_nova.novaclient(request).servers.unrescue(instance_id)
@profiler.trace
def tenant_quota_get(request, tenant_id):
return QuotaSet(_nova.novaclient(request).quotas.get(tenant_id))
@profiler.trace
def tenant_quota_update(request, tenant_id, **kwargs):
if kwargs:
_nova.novaclient(request).quotas.update(tenant_id, **kwargs)
@profiler.trace
def default_quota_get(request, tenant_id):
return QuotaSet(_nova.novaclient(request).quotas.defaults(tenant_id))
@profiler.trace
def default_quota_update(request, **kwargs):
_nova.novaclient(request).quota_classes.update(DEFAULT_QUOTA_NAME,
**kwargs)
def _get_usage_marker(usage):
marker = None
if hasattr(usage, 'server_usages') and usage.server_usages:
marker = usage.server_usages[-1].get('instance_id')
return marker
def _get_usage_list_marker(usage_list):
marker = None
if usage_list:
marker = _get_usage_marker(usage_list[-1])
return marker
def _merge_usage(usage, next_usage):
usage.server_usages.extend(next_usage.server_usages)
usage.total_hours += next_usage.total_hours
usage.total_memory_mb_usage += next_usage.total_memory_mb_usage
usage.total_vcpus_usage += next_usage.total_vcpus_usage
usage.total_local_gb_usage += next_usage.total_local_gb_usage
def _merge_usage_list(usages, next_usage_list):
for next_usage in next_usage_list:
if next_usage.tenant_id in usages:
_merge_usage(usages[next_usage.tenant_id], next_usage)
else:
usages[next_usage.tenant_id] = next_usage
@profiler.trace
def usage_get(request, tenant_id, start, end):
client = upgrade_api(request, _nova.novaclient(request), '2.40')
usage = client.usage.get(tenant_id, start, end)
if client.api_version >= api_versions.APIVersion('2.40'):
# If the number of instances used to calculate the usage is greater
# than max_limit, the usage will be split across multiple requests
# and the responses will need to be merged back together.
marker = _get_usage_marker(usage)
while marker:
next_usage = client.usage.get(tenant_id, start, end, marker=marker)
marker = _get_usage_marker(next_usage)
if marker:
_merge_usage(usage, next_usage)
return NovaUsage(usage)
@profiler.trace
def usage_list(request, start, end):
client = upgrade_api(request, _nova.novaclient(request), '2.40')
usage_list = client.usage.list(start, end, True)
if client.api_version >= api_versions.APIVersion('2.40'):
# If the number of instances used to calculate the usage is greater
# than max_limit, the usage will be split across multiple requests
# and the responses will need to be merged back together.
usages = collections.OrderedDict()
_merge_usage_list(usages, usage_list)
marker = _get_usage_list_marker(usage_list)
while marker:
next_usage_list = client.usage.list(start, end, True,
marker=marker)
marker = _get_usage_list_marker(next_usage_list)
if marker:
_merge_usage_list(usages, next_usage_list)
usage_list = usages.values()
return [NovaUsage(u) for u in usage_list]
@profiler.trace
def get_password(request, instance_id, private_key=None):
return _nova.novaclient(request).servers.get_password(instance_id,
private_key)
@profiler.trace
def instance_volume_attach(request, volume_id, instance_id, device):
# If we have a multiattach volume, we need to use microversion>=2.60.
volume = cinder.volume_get(request, volume_id)
if volume.multiattach:
version = get_microversion(request, 'multiattach')
if version:
client = _nova.novaclient(request, version)
else:
raise VolumeMultiattachNotSupported(
_('Multiattach volumes are not yet supported.'))
else:
client = _nova.novaclient(request)
return client.volumes.create_server_volume(
instance_id, volume_id, device)
@profiler.trace
def instance_volume_detach(request, instance_id, att_id):
return _nova.novaclient(request).volumes.delete_server_volume(instance_id,
att_id)
@profiler.trace
def instance_volumes_list(request, instance_id):
volumes = _nova.novaclient(request).volumes.get_server_volumes(instance_id)
for volume in volumes:
volume_data = cinder.cinderclient(request).volumes.get(volume.id)
volume.name = cinder.Volume(volume_data).name
return volumes
@profiler.trace
def hypervisor_list(request):
return _nova.novaclient(request).hypervisors.list()
@profiler.trace
def hypervisor_stats(request):
return _nova.novaclient(request).hypervisors.statistics()
@profiler.trace
def hypervisor_search(request, query, servers=True):
return _nova.novaclient(request).hypervisors.search(query, servers)
@profiler.trace
def evacuate_host(request, host, target=None, on_shared_storage=False):
# TODO(jmolle) This should be change for nova atomic api host_evacuate
hypervisors = _nova.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:
_nova.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
@profiler.trace
def migrate_host(request, host, live_migrate=False, disk_over_commit=False,
block_migration=False):
nc = _nova.novaclient(request)
hypervisors = nc.hypervisors.search(host, True)
response = []
err_code = None
for hyper in hypervisors:
for server in getattr(hyper, "servers", []):
try:
if live_migrate:
instance = server_get(request, server['uuid'])
# Checking that instance can be live-migrated
if instance.status in ["ACTIVE", "PAUSED"]:
nc.servers.live_migrate(
server['uuid'],
None,
block_migration,
disk_over_commit
)
else:
nc.servers.migrate(server['uuid'])
else:
nc.servers.migrate(server['uuid'])
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 migrate instances: %s') % ', '.join(response)
raise nova_exceptions.ClientException(err_code, msg)
return True
@profiler.trace
def tenant_absolute_limits(request, reserved=False, tenant_id=None):
# Nova does not allow to specify tenant_id for non-admin users
# even if tenant_id matches a tenant_id of the user.
if tenant_id == request.user.tenant_id:
tenant_id = None
limits = _nova.novaclient(request).limits.get(reserved=reserved,
tenant_id=tenant_id).absolute
limits_dict = {}
for limit in limits:
if limit.value < 0:
# Workaround for nova bug 1370867 that absolute_limits
# returns negative value for total.*Used instead of 0.
# For such case, replace negative values with 0.
if limit.name.startswith('total') and limit.name.endswith('Used'):
limits_dict[limit.name] = 0
else:
# -1 is used to represent unlimited quotas
limits_dict[limit.name] = float("inf")
else:
limits_dict[limit.name] = limit.value
return limits_dict
@profiler.trace
def availability_zone_list(request, detailed=False):
nc = _nova.novaclient(request)
zones = nc.availability_zones.list(detailed=detailed)
zones.sort(key=attrgetter('zoneName'))
return zones
@profiler.trace
def server_group_list(request):
return _nova.novaclient(request).server_groups.list()
@profiler.trace
def server_group_create(request, **kwargs):
microversion = get_microversion(request, "servergroup_soft_policies")
nc = _nova.novaclient(request, version=microversion)
return nc.server_groups.create(**kwargs)
@profiler.trace
def server_group_delete(request, servergroup_id):
_nova.novaclient(request).server_groups.delete(servergroup_id)
@profiler.trace
def server_group_get(request, servergroup_id):
microversion = get_microversion(request, "servergroup_user_info")
return _nova.novaclient(request, version=microversion).server_groups.get(
servergroup_id)
@profiler.trace
def service_list(request, binary=None):
return _nova.novaclient(request).services.list(binary=binary)
@profiler.trace
def service_enable(request, host, binary):
return _nova.novaclient(request).services.enable(host, binary)
@profiler.trace
def service_disable(request, host, binary, reason=None):
if reason:
return _nova.novaclient(request).services.disable_log_reason(
host, binary, reason)
else:
return _nova.novaclient(request).services.disable(host, binary)
@profiler.trace
def aggregate_details_list(request):
result = []
c = _nova.novaclient(request)
for aggregate in c.aggregates.list():
result.append(c.aggregates.get_details(aggregate.id))
return result
@profiler.trace
def aggregate_create(request, name, availability_zone=None):
return _nova.novaclient(request).aggregates.create(name, availability_zone)
@profiler.trace
def aggregate_delete(request, aggregate_id):
return _nova.novaclient(request).aggregates.delete(aggregate_id)
@profiler.trace
def aggregate_get(request, aggregate_id):
return _nova.novaclient(request).aggregates.get(aggregate_id)
@profiler.trace
def aggregate_update(request, aggregate_id, values):
_nova.novaclient(request).aggregates.update(aggregate_id, values)
@profiler.trace
def aggregate_set_metadata(request, aggregate_id, metadata):
return _nova.novaclient(request).aggregates.set_metadata(aggregate_id,
metadata)
@profiler.trace
def add_host_to_aggregate(request, aggregate_id, host):
_nova.novaclient(request).aggregates.add_host(aggregate_id, host)
@profiler.trace
def remove_host_from_aggregate(request, aggregate_id, host):
_nova.novaclient(request).aggregates.remove_host(aggregate_id, host)
@profiler.trace
def interface_attach(request,
server, port_id=None, net_id=None, fixed_ip=None):
return _nova.novaclient(request).servers.interface_attach(
server, port_id, net_id, fixed_ip)
@profiler.trace
def interface_detach(request, server, port_id):
return _nova.novaclient(request).servers.interface_detach(server, port_id)
@profiler.trace
@memoized.memoized
def list_extensions(request):
"""List all nova extension names, except the ones in the blacklist."""
blacklist = set(settings.OPENSTACK_NOVA_EXTENSIONS_BLACKLIST)
return tuple(
extension for extension in EXTENSIONS if extension not in blacklist
)
@profiler.trace
@memoized.memoized
def extension_supported(extension_name, request):
"""Determine if nova supports a given extension name.
Example values for the extension_name include AdminActions, ConsoleOutput,
etc.
"""
return extension_name in list_extensions(request)
@profiler.trace
def can_set_server_password():
return utils.get_dict_config('OPENSTACK_HYPERVISOR_FEATURES',
'can_set_password')
@profiler.trace
def instance_action_list(request, instance_id):
return nova_instance_action.InstanceActionManager(
_nova.novaclient(request)).list(instance_id)
@profiler.trace
def can_set_mount_point():
"""Return the Hypervisor's capability of setting mount points."""
return utils.get_dict_config('OPENSTACK_HYPERVISOR_FEATURES',
'can_set_mount_point')
@profiler.trace
def requires_keypair():
return utils.get_dict_config('OPENSTACK_HYPERVISOR_FEATURES',
'requires_keypair')
def can_set_quotas():
return utils.get_dict_config('OPENSTACK_HYPERVISOR_FEATURES',
'enable_quotas')