remove personality extension

This removes personality extension, as well as the rebuild extension
point for servers.

We add an operation flag to translate extension because personality
extension uses different kwarg names depending on the operation. This
is done with a set of constants which should prevent typos silently
causing failures.

A few variables are renamed in servers.py to avoid multiline
statements.

Part of bp:api-no-more-extensions

Change-Id: I47deae0279f85d9a355d1248b6e90af732406514
This commit is contained in:
Sean Dague 2016-06-22 17:11:24 -04:00
parent f26739394f
commit 76b58b8f89
12 changed files with 81 additions and 193 deletions

View File

@ -115,7 +115,10 @@ hardcoded_extensions = [
{'name': 'PreserveEphemeralOnRebuild',
'description': ('Allow preservation of the '
'ephemeral partition on rebuild.'),
'alias': 'os-preserve-ephemeral-rebuild'}
'alias': 'os-preserve-ephemeral-rebuild'},
{'name': 'Personality',
'description': 'Personality support.',
'alias': 'os-personality'},
]
# V2.1 does not support XML but we need to keep an entry in the

View File

@ -22,6 +22,12 @@ API_DISK_CONFIG = "OS-DCF:diskConfig"
API_ACCESS_V4 = "accessIPv4"
API_ACCESS_V6 = "accessIPv6"
# possible ops
CREATE = 'create'
UPDATE = 'update'
REBUILD = 'rebuild'
RESIZE = 'resize'
def disk_config_from_api(value):
if value == 'AUTO':
@ -33,7 +39,20 @@ def disk_config_from_api(value):
raise exc.HTTPBadRequest(explanation=msg)
def translate_attributes(server_dict, operation_kwargs):
def get_injected_files(personality):
"""Create a list of injected files from the personality attribute.
At this time, injected_files must be formatted as a list of
(file_path, file_content) pairs for compatibility with the
underlying compute service.
"""
injected_files = []
for item in personality:
injected_files.append((item['path'], item['contents']))
return injected_files
def translate_attributes(op, server_dict, operation_kwargs):
"""Translate REST attributes on create to server object kwargs.
Our REST API is relatively fixed, but internal representations
@ -45,6 +64,10 @@ def translate_attributes(server_dict, operation_kwargs):
It's done in a common function as this is used for create / resize
/ rebuild / update
The ``op`` is the operation that we are transforming, because
there are times when we translate differently for different
operations. (Yes, it's a little nuts, but legacy... )
The ``server_dict`` is a representation of the server in
question. During ``create`` and ``update`` operations this will
actually be the ``server`` element of the request body.
@ -57,6 +80,7 @@ def translate_attributes(server_dict, operation_kwargs):
why it's important to only set operation_kwargs if there is
something to set. Input validation will ensure that we are only
operating on appropriate attributes for each operation.
"""
# Disk config
auto_disk_config_raw = server_dict.pop(API_DISK_CONFIG, None)
@ -72,7 +96,21 @@ def translate_attributes(server_dict, operation_kwargs):
# This is only ever expected during rebuild operations, and only
# does anything with Ironic driver. It also demonstrates the lack
# of understanding of the word ephemeral.
if 'preserve_ephemeral' in server_dict:
if 'preserve_ephemeral' in server_dict and op == REBUILD:
preserve = strutils.bool_from_string(
server_dict.pop('preserve_ephemeral'), strict=True)
operation_kwargs['preserve_ephemeral'] = preserve
# yes, we use different kwargs, this goes all the way back to
# commit cebc98176926f57016a508d5c59b11f55dfcf2b3.
if 'personality' in server_dict:
if op == REBUILD:
operation_kwargs['files_to_inject'] = get_injected_files(
server_dict.pop('personality'))
# NOTE(sdague): the deprecated hooks infrastructure doesn't
# function if injected files is not defined as a list. Once hooks
# are removed, this should go back inside the personality
# conditional above.
if op == CREATE:
operation_kwargs['injected_files'] = get_injected_files(
server_dict.pop('personality', []))

View File

@ -1,69 +0,0 @@
# Copyright 2014 NEC Corporation. All rights reserved.
#
# 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 nova.api.openstack.compute.schemas import personality
from nova.api.openstack import extensions
ALIAS = "os-personality"
class Personality(extensions.V21APIExtensionBase):
"""Personality support."""
name = "Personality"
alias = ALIAS
version = 1
def get_controller_extensions(self):
return []
def get_resources(self):
return []
def _get_injected_files(self, personality):
"""Create a list of injected files from the personality attribute.
At this time, injected_files must be formatted as a list of
(file_path, file_content) pairs for compatibility with the
underlying compute service.
"""
injected_files = []
for item in personality:
injected_files.append((item['path'], item['contents']))
return injected_files
# Extend server in both create & rebuild
#
# TODO(sdague): it looks weird that server_create is different
# than server_rebuild, right? Well you can totally blame hooks for
# that. By accident this means that server personalities are
# always creating the injected_files kwarg, even if there is
# nothing in it. Hooks totally needs injected_files to be a
# thing. Once hooks are removed from tree, this function can be
# made to look like the rebuild one.
def server_create(self, server_dict, create_kwargs, body_deprecated):
create_kwargs['injected_files'] = self._get_injected_files(
server_dict.get('personality', []))
def server_rebuild(self, server_dict, create_kwargs):
if 'personality' in server_dict:
create_kwargs['files_to_inject'] = self._get_injected_files(
server_dict['personality'])
# Extend schema with the new allowed parameters
def get_server_create_schema(self, version):
return personality.server_create
def get_server_rebuild_schema(self, version):
return personality.server_create

View File

@ -1,30 +0,0 @@
# Copyright 2014 NEC Corporation. All rights reserved.
#
# 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.
server_create = {
'personality': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'path': {'type': 'string'},
'contents': {
'type': 'string',
'format': 'base64'
}
},
'additionalProperties': False,
}
}
}

View File

@ -46,6 +46,7 @@ base_create = {
'OS-DCF:diskConfig': parameter_types.disk_config,
'accessIPv4': parameter_types.accessIPv4,
'accessIPv6': parameter_types.accessIPv6,
'personality': parameter_types.personality,
},
'required': ['name', 'flavorRef'],
'additionalProperties': False,
@ -107,6 +108,7 @@ base_rebuild = {
'OS-DCF:diskConfig': parameter_types.disk_config,
'accessIPv4': parameter_types.accessIPv4,
'accessIPv6': parameter_types.accessIPv6,
'personality': parameter_types.personality,
},
'required': ['imageRef'],
'additionalProperties': False,

View File

@ -60,8 +60,6 @@ class ServersController(wsgi.Controller):
EXTENSION_CREATE_NAMESPACE = 'nova.api.v21.extensions.server.create'
EXTENSION_REBUILD_NAMESPACE = 'nova.api.v21.extensions.server.rebuild'
_view_builder_class = views_servers.ViewBuilderV21
schema_server_create = schema_servers.base_create
@ -159,17 +157,6 @@ class ServersController(wsgi.Controller):
if not list(self.create_extension_manager):
LOG.debug("Did not find any server create extensions")
# Look for implementation of extension point of server rebuild
self.rebuild_extension_manager = \
stevedore.enabled.EnabledExtensionManager(
namespace=self.EXTENSION_REBUILD_NAMESPACE,
check_func=_check_load_extension('server_rebuild'),
invoke_on_load=True,
invoke_kwds={"extension_info": self.extension_info},
propagate_map_exceptions=True)
if not list(self.rebuild_extension_manager):
LOG.debug("Did not find any server rebuild extensions")
# Look for API schema of server create extension
self.create_schema_manager = \
stevedore.enabled.EnabledExtensionManager(
@ -190,26 +177,6 @@ class ServersController(wsgi.Controller):
else:
LOG.debug("Did not find any server create schemas")
# Look for API schema of server rebuild extension
self.rebuild_schema_manager = \
stevedore.enabled.EnabledExtensionManager(
namespace=self.EXTENSION_REBUILD_NAMESPACE,
check_func=_check_load_extension('get_server_rebuild_schema'),
invoke_on_load=True,
invoke_kwds={"extension_info": self.extension_info},
propagate_map_exceptions=True)
if list(self.rebuild_schema_manager):
self.rebuild_schema_manager.map(self._rebuild_extension_schema,
self.schema_server_rebuild_v219,
'2.19')
self.rebuild_schema_manager.map(self._rebuild_extension_schema,
self.schema_server_rebuild, '2.1')
self.rebuild_schema_manager.map(self._rebuild_extension_schema,
self.schema_server_rebuild_v20,
'2.0')
else:
LOG.debug("Did not find any server rebuild schemas")
@extensions.expected_errors((400, 403))
def index(self, req):
"""Returns a list of server names and ids for a given user."""
@ -524,7 +491,8 @@ class ServersController(wsgi.Controller):
availability_zone = create_kwargs.pop("availability_zone", None)
helpers.translate_attributes(server_dict, create_kwargs)
helpers.translate_attributes(helpers.CREATE,
server_dict, create_kwargs)
target = {
'project_id': context.project_id,
@ -748,15 +716,17 @@ class ServersController(wsgi.Controller):
update_dict = {}
ctxt.can(server_policies.SERVERS % 'update')
if 'name' in body['server']:
server = body['server']
if 'name' in server:
update_dict['display_name'] = common.normalize_name(
body['server']['name'])
server['name'])
if 'description' in body['server']:
if 'description' in server:
# This is allowed to be None (remove description)
update_dict['display_description'] = body['server']['description']
update_dict['display_description'] = server['description']
helpers.translate_attributes(body['server'], update_dict)
helpers.translate_attributes(helpers.UPDATE, server, update_dict)
instance = self._get_server(ctxt, req, id, is_detail=True)
try:
@ -920,10 +890,10 @@ class ServersController(wsgi.Controller):
resize_dict = body['resize']
flavor_ref = str(resize_dict["flavorRef"])
resize_kwargs = {}
helpers.translate_attributes(resize_dict, resize_kwargs)
kwargs = {}
helpers.translate_attributes(helpers.RESIZE, resize_dict, kwargs)
self._resize(req, id, flavor_ref, **resize_kwargs)
self._resize(req, id, flavor_ref, **kwargs)
@wsgi.response(202)
@extensions.expected_errors((400, 403, 404, 409))
@ -950,21 +920,17 @@ class ServersController(wsgi.Controller):
'metadata': 'metadata',
}
rebuild_kwargs = {}
kwargs = {}
if list(self.rebuild_extension_manager):
self.rebuild_extension_manager.map(self._rebuild_extension_point,
rebuild_dict, rebuild_kwargs)
helpers.translate_attributes(rebuild_dict, rebuild_kwargs)
helpers.translate_attributes(helpers.REBUILD, rebuild_dict, kwargs)
for request_attribute, instance_attribute in attr_map.items():
try:
if request_attribute == 'name':
rebuild_kwargs[instance_attribute] = common.normalize_name(
kwargs[instance_attribute] = common.normalize_name(
rebuild_dict[request_attribute])
else:
rebuild_kwargs[instance_attribute] = rebuild_dict[
kwargs[instance_attribute] = rebuild_dict[
request_attribute]
except (KeyError, TypeError):
pass
@ -974,7 +940,7 @@ class ServersController(wsgi.Controller):
instance,
image_href,
password,
**rebuild_kwargs)
**kwargs)
except exception.InstanceIsLocked as e:
raise exc.HTTPConflict(explanation=e.format_message())
except exception.InstanceInvalidState as state_error:

View File

@ -377,3 +377,18 @@ flavor_param_positive = copy.deepcopy(volume_size)
flavor_param_non_negative = copy.deepcopy(volume_size)
flavor_param_non_negative['minimum'] = 0
personality = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'path': {'type': 'string'},
'contents': {
'type': 'string',
'format': 'base64'
}
},
'additionalProperties': False,
}
}

View File

@ -68,7 +68,6 @@ from nova.policies import networks
from nova.policies import networks_associate
from nova.policies import pause_server
from nova.policies import pci
from nova.policies import personality
from nova.policies import quota_class_sets
from nova.policies import quota_sets
from nova.policies import remote_consoles
@ -154,7 +153,6 @@ def list_rules():
networks_associate.list_rules(),
pause_server.list_rules(),
pci.list_rules(),
personality.list_rules(),
quota_class_sets.list_rules(),
quota_sets.list_rules(),
remote_consoles.list_rules(),

View File

@ -1,32 +0,0 @@
# Copyright 2016 Cloudbase Solutions Srl
# All Rights Reserved.
#
# 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 oslo_policy import policy
from nova.policies import base
POLICY_ROOT = 'os_compute_api:os-personality:%s'
personality_policies = [
policy.RuleDefault(
name=POLICY_ROOT % 'discoverable',
check_str=base.RULE_ANY),
]
def list_rules():
return personality_policies

View File

@ -466,7 +466,6 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
"os_compute_api:os-networks-associate:discoverable",
"os_compute_api:os-pause-server:discoverable",
"os_compute_api:os-pci:discoverable",
"os_compute_api:os-personality:discoverable",
"os_compute_api:os-quota-sets:discoverable",
"os_compute_api:os-quota-class-sets:discoverable",
"os_compute_api:os-rescue:discoverable",

View File

@ -20,3 +20,6 @@ upgrade:
* nova.api.v21.extensions.server.update - allowed accepting
additional parameters on server update requests.
* nova.api.v21.extensions.server.rebuild - allowed accepting
additional parameters on server rebuild requests.

View File

@ -122,7 +122,6 @@ nova.api.v21.extensions =
# NOTE(cyeoh): this is intentionally disabled until microversions is active.
# See https://bugs.launchpad.net/nova/+bug/1426241
# pci = nova.api.openstack.compute.pci:Pci
personality = nova.api.openstack.compute.personality:Personality
quota_classes = nova.api.openstack.compute.quota_classes:QuotaClasses
quota_sets = nova.api.openstack.compute.quota_sets:QuotaSets
remote_consoles = nova.api.openstack.compute.remote_consoles:RemoteConsoles
@ -157,14 +156,10 @@ nova.api.v21.extensions.server.create =
config_drive = nova.api.openstack.compute.config_drive:ConfigDrive
keypairs_create = nova.api.openstack.compute.keypairs:Keypairs
multiple_create = nova.api.openstack.compute.multiple_create:MultipleCreate
personality = nova.api.openstack.compute.personality:Personality
scheduler_hints = nova.api.openstack.compute.scheduler_hints:SchedulerHints
security_groups = nova.api.openstack.compute.security_groups:SecurityGroups
user_data = nova.api.openstack.compute.user_data:UserData
nova.api.v21.extensions.server.rebuild =
personality = nova.api.openstack.compute.personality:Personality
nova.api.v21.test_extensions =
basic = nova.tests.unit.api.openstack.compute.basic:Basic
microversions = nova.tests.unit.api.openstack.compute.microversions:Microversions