Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync from testing branch for release 7.1 #50

Open
wants to merge 8 commits into
base: product
Choose a base branch
from
119 changes: 119 additions & 0 deletions nova/api/openstack/compute/contrib/eayun_user_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright 2012 OpenStack Foundation
#
# 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 base64

from webob import exc

from nova import compute
from nova import exception
from nova import policy
from nova.i18n import _
from nova.api.openstack import wsgi
from nova.api.openstack import extensions


authorize = extensions.extension_authorizer('compute', 'eayun-userdata')


class EayunUserDataController(wsgi.Controller):
"""EayunStack userdata controller."""
def __init__(self, ext_mgr=None):
super(EayunUserDataController, self).__init__()
self.compute_api = compute.API()
self.ext_mgr = ext_mgr

def _validate_user_data(self, user_data):
"""Check if the user_data is encoded properly."""
if not user_data:
return
try:
base64.b64decode(user_data)
except:
expl = _('Userdata content cannot be decoded')
raise exc.HTTPBadRequest(explanation=expl)

def _get_userdata(self, instance):
user_data = {'user_data': instance.get("user_data", None)}

return user_data

def _is_valid_body(self, body, entity_name):
if not (body and entity_name in body):
return False

def is_dict(d):
return type(d) is dict

return is_dict(body)

def show(self, req, id):
"""Return userdata by server id."""
context = req.environ['nova.context']
authorize(context)

try:
instance = self.compute_api.get(context, id,
want_objects=True)
req.cache_db_instance(instance)
except exception.NotFound:
msg = _("Instance could not be found")
raise exc.HTTPNotFound(explanation=msg)

return self._get_userdata(instance)

def update(self, req, id, body):
"""Update userdata by server id."""
if not self._is_valid_body(body, 'user_data'):
raise exc.HTTPUnprocessableEntity()

context = req.environ['nova.context']
authorize(context)
update_dict = {}

user_data = body['user_data']
self._validate_user_data(user_data)
update_dict['user_data'] = user_data

try:
instance = self.compute_api.get(context, id,
want_objects=True)
except exception.NotFound:
msg = _("Instance could not be found")
raise exc.HTTPNotFound(explanation=msg)

req.cache_db_instance(instance)
policy.enforce(context, 'compute_extension:eayun-userdata:update',
instance)
instance.update(update_dict)
instance.save()

return self._get_userdata(instance)


class Eayun_user_data(extensions.ExtensionDescriptor):
"""Add userdata extension for v2 API."""

name = "EayunUserData"
alias = "eayun-userdata"
namespace = ("www.eayun.cn")
updated = "2017-01-10T00:00:00Z"

def get_resources(self):
resources = []
res = extensions.ResourceExtension('eayun-userdata',
EayunUserDataController(
self.ext_mgr))
resources.append(res)
return resources
67 changes: 50 additions & 17 deletions nova/api/openstack/compute/contrib/volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,20 +450,40 @@ def update(self, req, server_id, id, body):
authorize(context)
authorize_attach(context, action='update')

if not self.is_valid_body(body, 'volumeAttachment'):
msg = _("volumeAttachment not specified")
if not (self.is_valid_body(body, 'volumeAttachment') or
self.is_valid_body(body, 'volumeQoSSpecs')):
msg = _("volumeAttachment or volumeQoSSpecs not specified")
raise exc.HTTPBadRequest(explanation=msg)

old_volume_id = id
old_volume = self.volume_api.get(context, old_volume_id)

try:
new_volume_id = body['volumeAttachment']['volumeId']
except KeyError:
msg = _("volumeId must be specified.")
if (self.is_valid_body(body, 'volumeAttachment') and
self.is_valid_body(body, 'volumeQoSSpecs')):
msg = _("volumeAttachment and volumeQoSSpecs cannot be "
"specified simultaneously")
raise exc.HTTPBadRequest(explanation=msg)
self._validate_volume_id(new_volume_id)
new_volume = self.volume_api.get(context, new_volume_id)

if self.is_valid_body(body, 'volumeAttachment'):
update_volume_qos = False
else:
# Volume QoS Specs should be updated by admin users.
if context.is_admin:
update_volume_qos = True
else:
msg = _("volumeQoSSpecs can only be called by admin users")
raise exc.HTTPMethodNotAllowed(explanation=msg)

if update_volume_qos:
qos_specs = body['volumeQoSSpecs']['qos_specs']
else:
old_volume_id = id
old_volume = self.volume_api.get(context, old_volume_id)

try:
new_volume_id = body['volumeAttachment']['volumeId']
except KeyError:
msg = _("volumeId must be specified.")
raise exc.HTTPBadRequest(explanation=msg)
self._validate_volume_id(new_volume_id)
new_volume = self.volume_api.get(context, new_volume_id)

try:
instance = self.compute_api.get(context, server_id,
Expand All @@ -476,28 +496,41 @@ def update(self, req, server_id, id, body):
found = False
try:
for bdm in bdms:
if bdm.volume_id != old_volume_id:
if bdm.volume_id != id:
continue
try:
self.compute_api.swap_volume(context, instance, old_volume,
new_volume)
if update_volume_qos:
self.compute_api.update_volume_qos(context, instance,
id, qos_specs)
else:
self.compute_api.swap_volume(context, instance,
old_volume, new_volume)

found = True
break
except exception.VolumeUnattached:
# The volume is not attached. Treat it as NotFound
# by falling through.
pass
except exception.VolumeQoSSpecsUpdateFailed as e:
raise exc.HTTPInternalServerError(
explanation=e.format_message())
except exception.InstanceIsLocked as e:
raise exc.HTTPConflict(explanation=e.format_message())
except exception.InstanceInvalidState as state_error:
action = ('update_volume_qos' if update_volume_qos
else 'swap_volume')
common.raise_http_conflict_for_instance_invalid_state(state_error,
'swap_volume')
action)

if not found:
msg = _("volume_id not found: %s") % old_volume_id
msg = _("volume_id not found: %s") % id
raise exc.HTTPNotFound(explanation=msg)
else:
return webob.Response(status_int=202)
# update_volume_qos is a synchronous call, so status code of 204
# should be returned on success.
status_int = 204 if update_volume_qos else 202
return webob.Response(status_int=status_int)

def delete(self, req, server_id, id):
"""Detach a volume from an instance."""
Expand Down
21 changes: 16 additions & 5 deletions nova/api/openstack/compute/views/flavors.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,29 @@ def show(self, request, flavor):

def index(self, request, flavors):
"""Return the 'index' view of flavors."""
return self._list_view(self.basic, request, flavors)
coll_name = self._collection_name
return self._list_view(self.basic, request, flavors, coll_name)

def detail(self, request, flavors):
"""Return the 'detail' view of flavors."""
return self._list_view(self.show, request, flavors)
coll_name = self._collection_name + '/detail'
return self._list_view(self.show, request, flavors, coll_name)

def _list_view(self, func, request, flavors):
"""Provide a view for a list of flavors."""
def _list_view(self, func, request, flavors, coll_name):
"""Provide a view for a list of flavors.

:param func: Function used to format the flavor data
:param request: API request
:param flavors: List of flavors in dictionary format
:param coll_name: Name of collection, used to generate the next link
for a pagination query

:returns: Flavor reply data in dictionary format
"""
flavor_list = [func(request, flavor)["flavor"] for flavor in flavors]
flavors_links = self._get_collection_links(request,
flavors,
self._collection_name,
coll_name,
"flavorid")
flavors_dict = dict(flavors=flavor_list)

Expand Down
23 changes: 16 additions & 7 deletions nova/api/openstack/compute/views/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,28 @@ def show(self, request, image):
def detail(self, request, images):
"""Show a list of images with details."""
list_func = self.show
return self._list_view(list_func, request, images)
coll_name = self._collection_name + '/detail'
return self._list_view(list_func, request, images, coll_name)

def index(self, request, images):
"""Show a list of images with basic attributes."""
list_func = self.basic
return self._list_view(list_func, request, images)
coll_name = self._collection_name
return self._list_view(list_func, request, images, coll_name)

def _list_view(self, list_func, request, images):
"""Provide a view for a list of images."""
def _list_view(self, list_func, request, images, coll_name):
"""Provide a view for a list of images.

:param list_func: Function used to format the image data
:param request: API request
:param images: List of images in dictionary format
:param coll_name: Name of collection, used to generate the next link
for a pagination query

:returns: Image reply data in dictionary format
"""
image_list = [list_func(request, image)["image"] for image in images]
images_links = self._get_collection_links(request,
images,
self._collection_name)
images_links = self._get_collection_links(request, images, coll_name)
images_dict = dict(images=image_list)

if images_links:
Expand Down
28 changes: 28 additions & 0 deletions nova/compute/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2992,6 +2992,34 @@ def swap_volume(self, context, instance, old_volume, new_volume):
self.volume_api.roll_detaching(context, old_volume['id'])
self.volume_api.unreserve_volume(context, new_volume['id'])

@wrap_check_policy
@check_instance_lock
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
vm_states.SUSPENDED, vm_states.STOPPED,
vm_states.RESIZED, vm_states.SOFT_DELETED])
def update_volume_qos(self, context, instance, volume_id, qos_specs):
"""Update one volume attached to an instance."""
volume = self.volume_api.get(context, volume_id)
if volume['attach_status'] == 'detached':
raise exception.VolumeUnattached(volume_id=volume_id)
# The caller likely got the instance from volume['instance_uuid']
# in the first place, but let's sanity check.
if volume['instance_uuid'] != instance['uuid']:
msg = _("Volume is attached to a different instance.")
raise exception.InvalidVolume(reason=msg)

try:
self.compute_rpcapi.update_volume_qos(
context, instance=instance,
volume_id=volume_id,
qos_specs=qos_specs)
except Exception: # pylint: disable=W0702
LOG.exception(_LE("Failed to update QoS Specs of the volume "
"%(volume_id)s of instance %(instance_id)s"),
{"volume_id": volume_id,
"instance_id": instance['uuid']})
raise exception.VolumeQoSSpecsUpdateFailed()

@wrap_check_policy
@check_instance_lock
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
Expand Down
Loading