Files
nova/nova/api/openstack/compute/extension_info.py
T
Kevin_Zheng f41acdd57e Use plain routes list for server-metadata endpoint instead of stevedore
This patch adds server-metadata related routes by a plain list,
instead of using stevedore. After all the Nova API endpoints moves to the
plain routes list, the usage of stevedore for API loading will be removed
from Nova.

Partial-implement-blueprint api-no-more-extensions-pike

Change-Id: I88ca06d63016ffd1e38b61e6de0b75f88ecdb138
2017-05-03 02:45:17 +00:00

413 lines
17 KiB
Python

# Copyright 2013 IBM Corp.
#
# 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.
import copy
from oslo_log import log as logging
import webob.exc
from nova.api.openstack.compute import admin_actions
from nova.api.openstack.compute import admin_password
from nova.api.openstack.compute import config_drive
from nova.api.openstack.compute import console_output
from nova.api.openstack.compute import create_backup
from nova.api.openstack.compute import deferred_delete
from nova.api.openstack.compute import evacuate
from nova.api.openstack.compute import extended_availability_zone
from nova.api.openstack.compute import extended_server_attributes
from nova.api.openstack.compute import extended_status
from nova.api.openstack.compute import extended_volumes
from nova.api.openstack.compute import hide_server_addresses
from nova.api.openstack.compute import lock_server
from nova.api.openstack.compute import migrate_server
from nova.api.openstack.compute import multinic
from nova.api.openstack.compute import pause_server
from nova.api.openstack.compute import rescue
from nova.api.openstack.compute import scheduler_hints
from nova.api.openstack.compute import server_usage
from nova.api.openstack.compute import servers
from nova.api.openstack.compute import shelve
from nova.api.openstack.compute import suspend_server
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova import exception
from nova.policies import extensions as ext_policies
ALIAS = 'extensions'
LOG = logging.getLogger(__name__)
# NOTE(cyeoh): The following mappings are currently incomplete
# Having a v2.1 extension loaded can imply that several v2 extensions
# should also appear to be loaded (although they no longer do in v2.1)
v21_to_v2_extension_list_mapping = {
'os-quota-sets': [{'name': 'UserQuotas', 'alias': 'os-user-quotas',
'description': 'Project user quota support.'},
{'name': 'ExtendedQuotas',
'alias': 'os-extended-quotas',
'description': ('Adds ability for admins to delete'
' quota and optionally force the update Quota'
' command.')}],
'os-cells': [{'name': 'CellCapacities', 'alias': 'os-cell-capacities',
'description': ('Adding functionality to get cell'
' capacities.')}],
'os-baremetal-nodes': [{'name': 'BareMetalExtStatus',
'alias': 'os-baremetal-ext-status',
'description': ('Add extended status in'
' Baremetal Nodes v2 API.')}],
'os-block-device-mapping': [{'name': 'BlockDeviceMappingV2Boot',
'alias': 'os-block-device-mapping-v2-boot',
'description': ('Allow boot with the new BDM'
' data format.')}],
'os-cloudpipe': [{'name': 'CloudpipeUpdate',
'alias': 'os-cloudpipe-update',
'description': ('Adds the ability to set the vpn'
' ip/port for cloudpipe instances.')}],
'servers': [{'name': 'Createserverext', 'alias': 'os-create-server-ext',
'description': ('Extended support to the Create Server'
' v1.1 API.')},
{'name': 'ExtendedIpsMac', 'alias': 'OS-EXT-IPS-MAC',
'description': 'Adds mac address parameter to the ip list.'},
{'name': 'ExtendedIps', 'alias': 'OS-EXT-IPS',
'description': 'Adds type parameter to the ip list.'},
{'name': 'ServerListMultiStatus',
'alias': 'os-server-list-multi-status',
'description': ('Allow to filter the servers by a set of'
' status values.')},
{'name': 'ServerSortKeys', 'alias': 'os-server-sort-keys',
'description': 'Add sorting support in get Server v2 API.'},
{'name': 'ServerStartStop', 'alias': 'os-server-start-stop',
'description': 'Start/Stop instance compute API support.'}],
'flavors': [{'name': 'FlavorDisabled', 'alias': 'OS-FLV-DISABLED',
'description': ('Support to show the disabled status'
' of a flavor.')},
{'name': 'FlavorExtraData', 'alias': 'OS-FLV-EXT-DATA',
'description': 'Provide additional data for flavors.'},
{'name': 'FlavorSwap', 'alias': 'os-flavor-swap',
'description': ('Support to show the swap status of a'
' flavor.')}],
'os-services': [{'name': 'ExtendedServicesDelete',
'alias': 'os-extended-services-delete',
'description': 'Extended services deletion support.'},
{'name': 'ExtendedServices', 'alias':
'os-extended-services',
'description': 'Extended services support.'}],
'os-evacuate': [{'name': 'ExtendedEvacuateFindHost',
'alias': 'os-extended-evacuate-find-host',
'description': ('Enables server evacuation without'
' target host. Scheduler will select one to target.')}],
'os-floating-ips': [{'name': 'ExtendedFloatingIps',
'alias': 'os-extended-floating-ips',
'description': ('Adds optional fixed_address to the add'
' floating IP command.')}],
'os-hypervisors': [{'name': 'ExtendedHypervisors',
'alias': 'os-extended-hypervisors',
'description': 'Extended hypervisors support.'},
{'name': 'HypervisorStatus',
'alias': 'os-hypervisor-status',
'description': 'Show hypervisor status.'}],
'os-networks': [{'name': 'ExtendedNetworks',
'alias': 'os-extended-networks',
'description': 'Adds additional fields to networks.'}],
'os-rescue': [{'name': 'ExtendedRescueWithImage',
'alias': 'os-extended-rescue-with-image',
'description': ('Allow the user to specify the image to'
' use for rescue.')}],
'os-extended-status': [{'name': 'ExtendedStatus',
'alias': 'OS-EXT-STS',
'description': 'Extended Status support.'}],
'os-used-limits': [{'name': 'UsedLimitsForAdmin',
'alias': 'os-used-limits-for-admin',
'description': ('Provide data to admin on limited'
' resources used by other tenants.')}],
'os-volumes': [{'name': 'VolumeAttachmentUpdate',
'alias': 'os-volume-attachment-update',
'description': ('Support for updating a volume'
' attachment.')}],
'os-server-groups': [{'name': 'ServerGroupQuotas',
'alias': 'os-server-group-quotas',
'description': 'Adds quota support to server groups.'}],
}
# v2.1 plugins which should never appear in the v2 extension list
# This should be the v2.1 alias, not the V2.0 alias
v2_extension_suppress_list = ['servers', 'images', 'versions', 'flavors',
'os-block-device-mapping-v1', 'os-consoles',
'extensions', 'image-metadata', 'ips', 'limits',
'server-metadata', 'server-migrations',
'os-server-tags'
]
# v2.1 plugins which should appear under a different name in v2
v21_to_v2_alias_mapping = {
'image-size': 'OS-EXT-IMG-SIZE',
'os-remote-consoles': 'os-consoles',
'os-disk-config': 'OS-DCF',
'os-extended-availability-zone': 'OS-EXT-AZ',
'os-extended-server-attributes': 'OS-EXT-SRV-ATTR',
'os-multinic': 'NMN',
'os-scheduler-hints': 'OS-SCH-HNT',
'os-server-usage': 'OS-SRV-USG',
'os-instance-usage-audit-log': 'os-instance_usage_audit_log',
}
# NOTE(sdague): this is the list of extension metadata that we display
# to the user for features that we provide. This exists for legacy
# purposes because applications were once asked to look for these
# things to decide if a feature is enabled. As we remove extensions
# completely from the code we're going to have a static list here to
# keep the surface metadata the same.
hardcoded_extensions = [
{'name': 'Aggregates',
'alias': 'os-aggregates',
'description': 'Admin-only aggregate administration.'
},
{'name': 'DiskConfig',
'alias': 'os-disk-config',
'description': 'Disk Management Extension.'},
{'name': 'AccessIPs',
'description': 'Access IPs support.',
'alias': 'os-access-ips'},
{'name': 'PreserveEphemeralOnRebuild',
'description': ('Allow preservation of the '
'ephemeral partition on rebuild.'),
'alias': 'os-preserve-ephemeral-rebuild'},
{'name': 'Personality',
'description': 'Personality support.',
'alias': 'os-personality'},
{'name': 'Flavors',
'description': 'Flavors Extension.',
'alias': 'flavors'},
{'name': 'FlavorManage',
'description': 'Flavor create/delete API support.',
'alias': 'os-flavor-manage'},
{'name': 'FlavorRxtx',
'description': 'Support to show the rxtx status of a flavor.',
'alias': 'os-flavor-rxtx'},
{'name': 'FlavorExtraSpecs',
'description': 'Flavors extra specs support.',
'alias': 'os-flavor-extra-specs'},
{'name': 'FlavorAccess',
'description': 'Flavor access support.',
'alias': 'os-flavor-access'},
{'name': 'Keypairs',
'description': 'Keypair Support.',
'alias': 'os-keypairs'},
{'name': 'ServerMetadata',
'description': 'Server metadata Support.',
'alias': 'server-metadata'},
]
# TODO(alex_xu): This is a list of unused extension objs. Add those
# extension objs here for building a compatible extension API. Finally,
# we should remove those extension objs, and add corresponding entries
# in the 'hardcoded_extensions'.
unused_extension_objs = [
admin_actions.AdminActions,
admin_password.AdminPassword,
config_drive.ConfigDrive,
console_output.ConsoleOutput,
create_backup.CreateBackup,
deferred_delete.DeferredDelete,
evacuate.Evacuate,
extended_availability_zone.ExtendedAvailabilityZone,
extended_server_attributes.ExtendedServerAttributes,
extended_status.ExtendedStatus,
extended_volumes.ExtendedVolumes,
hide_server_addresses.HideServerAddresses,
lock_server.LockServer,
migrate_server.MigrateServer,
multinic.Multinic,
pause_server.PauseServer,
rescue.Rescue,
scheduler_hints.SchedulerHints,
server_usage.ServerUsage,
servers.Servers,
shelve.Shelve,
suspend_server.SuspendServer
]
# V2.1 does not support XML but we need to keep an entry in the
# /extensions information returned to the user for backwards
# compatibility
FAKE_XML_URL = "http://docs.openstack.org/compute/ext/fake_xml"
FAKE_UPDATED_DATE = "2014-12-03T00:00:00Z"
class FakeExtension(object):
def __init__(self, name, alias, description=""):
self.name = name
self.alias = alias
self.__doc__ = description
self.version = -1
class ExtensionInfoController(wsgi.Controller):
def __init__(self, extension_info):
self.extension_info = extension_info
def _translate(self, ext):
ext_data = {}
ext_data["name"] = ext.name
ext_data["alias"] = ext.alias
ext_data["description"] = ext.__doc__
ext_data["namespace"] = FAKE_XML_URL
ext_data["updated"] = FAKE_UPDATED_DATE
ext_data["links"] = []
return ext_data
def _create_fake_ext(self, name, alias, description=""):
return FakeExtension(name, alias, description)
def _add_vif_extension(self, all_extensions):
vif_extension = {}
vif_extension_info = {'name': 'ExtendedVIFNet',
'alias': 'OS-EXT-VIF-NET',
'description': 'Adds network id parameter'
' to the virtual interface list.'}
vif_extension[vif_extension_info["alias"]] = self._create_fake_ext(
vif_extension_info["name"], vif_extension_info["alias"],
vif_extension_info["description"])
all_extensions.update(vif_extension)
def _get_extensions(self, context):
"""Filter extensions list based on policy."""
all_extensions = dict()
for item in hardcoded_extensions:
all_extensions[item['alias']] = self._create_fake_ext(
item['name'],
item['alias'],
item['description']
)
for ext_cls in unused_extension_objs:
ext = ext_cls(None)
all_extensions[ext.alias] = ext
for alias, ext in self.extension_info.get_extensions().items():
all_extensions[alias] = ext
# Add fake v2 extensions to list
extra_exts = {}
for alias in all_extensions:
if alias in v21_to_v2_extension_list_mapping:
for extra_ext in v21_to_v2_extension_list_mapping[alias]:
extra_exts[extra_ext["alias"]] = self._create_fake_ext(
extra_ext["name"], extra_ext["alias"],
extra_ext["description"])
all_extensions.update(extra_exts)
# Suppress extensions which we don't want to see in v2
for suppress_ext in v2_extension_suppress_list:
try:
del all_extensions[suppress_ext]
except KeyError:
pass
# v2.1 to v2 extension name mapping
for rename_ext in v21_to_v2_alias_mapping:
if rename_ext in all_extensions:
new_name = v21_to_v2_alias_mapping[rename_ext]
mod_ext = copy.deepcopy(
all_extensions.pop(rename_ext))
mod_ext.alias = new_name
all_extensions[new_name] = mod_ext
return all_extensions
@extensions.expected_errors(())
def index(self, req):
context = req.environ['nova.context']
context.can(ext_policies.BASE_POLICY_NAME)
all_extensions = self._get_extensions(context)
# NOTE(gmann): This is for v2.1 compatible mode where
# extension list should show all extensions as shown by v2.
# Here we add VIF extension which has been removed from v2.1 list.
if req.is_legacy_v2():
self._add_vif_extension(all_extensions)
sorted_ext_list = sorted(
all_extensions.items())
extensions = []
for _alias, ext in sorted_ext_list:
extensions.append(self._translate(ext))
return dict(extensions=extensions)
@extensions.expected_errors(404)
def show(self, req, id):
context = req.environ['nova.context']
context.can(ext_policies.BASE_POLICY_NAME)
try:
# NOTE(dprince): the extensions alias is used as the 'id' for show
ext = self._get_extensions(context)[id]
except KeyError:
raise webob.exc.HTTPNotFound()
return dict(extension=self._translate(ext))
class ExtensionInfo(extensions.V21APIExtensionBase):
"""Extension information."""
name = "Extensions"
alias = ALIAS
version = 1
def get_resources(self):
resources = [
extensions.ResourceExtension(
ALIAS, ExtensionInfoController(self.extension_info),
member_name='extension')]
return resources
def get_controller_extensions(self):
return []
class LoadedExtensionInfo(object):
"""Keep track of all loaded API extensions."""
def __init__(self):
self.extensions = {}
def register_extension(self, ext):
if not self._check_extension(ext):
return False
alias = ext.alias
if alias in self.extensions:
raise exception.NovaException("Found duplicate extension: %s"
% alias)
self.extensions[alias] = ext
return True
def _check_extension(self, extension):
"""Checks for required methods in extension objects."""
try:
extension.is_valid()
except AttributeError:
LOG.exception("Exception loading extension")
return False
return True
def get_extensions(self):
return self.extensions