Files
nova/nova/api/openstack/compute/server_shares.py
T
Stephen Finucane 08dd30d3fc api: Add new, simpler api_version decorator
Get rid of the whole API version switching madness and make our schema
generation _significantly_ simpler.

This looks a lot larger than it actually is. In most cases, this is
simply 's/wsgi.Controller.api_version/wsgi.api_version/'.

Change-Id: I180bfad84c38653709c216282099d9b3fb64c5a7
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
2025-05-13 12:04:50 +01:00

264 lines
10 KiB
Python

# 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 webob
from oslo_db import exception as db_exc
from oslo_utils import uuidutils
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import server_shares as schema
from nova.api.openstack.compute.views import server_shares
from nova.api.openstack import wsgi
from nova.api import validation
from nova.compute import api as compute
from nova.compute import vm_states
from nova import context as nova_context
from nova import exception
from nova import objects
from nova.objects import fields
from nova.policies import server_shares as ss_policies
from nova.share import manila
from nova.virt import hardware as hw
def _get_instance_mapping(context, server_id):
try:
return objects.InstanceMapping.get_by_instance_uuid(context, server_id)
except exception.InstanceMappingNotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
class ServerSharesController(wsgi.Controller):
_view_builder_class = server_shares.ViewBuilder
def __init__(self):
super(ServerSharesController, self).__init__()
self.compute_api = compute.API()
self.manila = manila.API()
def _get_instance_from_server_uuid(self, context, server_id):
instance = common.get_instance(self.compute_api, context, server_id)
return instance
def _check_instance_in_valid_state(self, context, server_id, action):
instance = self._get_instance_from_server_uuid(context, server_id)
if (
(action == "create share" and
instance.vm_state not in vm_states.STOPPED) or
(action == "delete share" and
instance.vm_state not in vm_states.STOPPED and
instance.vm_state not in vm_states.ERROR)
):
exc = exception.InstanceInvalidState(
attr="vm_state",
instance_uuid=instance.uuid,
state=instance.vm_state,
method=action,
)
common.raise_http_conflict_for_instance_invalid_state(
exc, action, server_id
)
return instance
@wsgi.api_version("2.97")
@wsgi.response(200)
@wsgi.expected_errors((400, 403, 404))
@validation.query_schema(schema.index_query)
@validation.response_body_schema(schema.index_response)
def index(self, req, server_id):
context = req.environ["nova.context"]
# Get instance mapping to query the required cell database
im = _get_instance_mapping(context, server_id)
context.can(ss_policies.POLICY_ROOT % 'index',
target={'project_id': im.project_id})
with nova_context.target_cell(context, im.cell_mapping) as cctxt:
# Ensure the instance exists
self._get_instance_from_server_uuid(cctxt, server_id)
db_shares = objects.ShareMappingList.get_by_instance_uuid(
cctxt, server_id
)
return self._view_builder._list_view(db_shares)
@wsgi.api_version("2.97")
@wsgi.response(201)
@wsgi.expected_errors((400, 403, 404, 409))
@validation.schema(schema.create, '2.97')
@validation.response_body_schema(schema.show_response)
def create(self, req, server_id, body):
def _try_create_share_mapping(context, share_mapping):
"""Block the request if the share is already created.
Prevent race conditions of requests that would hit the
share_mapping.create() almost at the same time.
Prevent user from using the same tag twice on the same instance.
"""
try:
objects.ShareMapping.get_by_instance_uuid_and_share_id(
context,
share_mapping.instance_uuid, share_mapping.share_id
)
raise exception.ShareMappingAlreadyExists(
share_id=share_mapping.share_id, tag=share_mapping.tag
)
except exception.ShareNotFound:
pass
try:
share_mapping.create()
except db_exc.DBDuplicateEntry:
raise exception.ShareMappingAlreadyExists(
share_id=share_mapping.share_id, tag=share_mapping.tag
)
def _check_manila_share(manila_share_data):
"""Check that the targeted share in manila has
correct export location, status 'available' and a supported
protocol.
"""
if manila_share_data.status != 'available':
raise exception.ShareStatusIncorect(
share_id=share_id, status=manila_share_data.status
)
if manila_share_data.export_location is None:
raise exception.ShareMissingExportLocation(share_id=share_id)
if (
manila_share_data.share_proto
not in fields.ShareMappingProto.ALL
):
raise exception.ShareProtocolNotSupported(
share_proto=manila_share_data.share_proto
)
context = req.environ["nova.context"]
# Get instance mapping to query the required cell database
im = _get_instance_mapping(context, server_id)
context.can(
ss_policies.POLICY_ROOT % 'create',
target={'project_id': im.project_id}
)
share_dict = body['share']
share_id = share_dict.get('share_id')
with nova_context.target_cell(context, im.cell_mapping) as cctxt:
instance = self._check_instance_in_valid_state(
cctxt,
server_id,
"create share"
)
try:
hw.check_shares_supported(cctxt, instance)
manila_share_data = self.manila.get(cctxt, share_id)
_check_manila_share(manila_share_data)
share_mapping = objects.ShareMapping(cctxt)
share_mapping.uuid = uuidutils.generate_uuid()
share_mapping.instance_uuid = server_id
share_mapping.share_id = manila_share_data.id
share_mapping.status = fields.ShareMappingStatus.ATTACHING
share_mapping.tag = share_dict.get('tag', manila_share_data.id)
share_mapping.export_location = (
manila_share_data.export_location)
share_mapping.share_proto = manila_share_data.share_proto
_try_create_share_mapping(cctxt, share_mapping)
self.compute_api.allow_share(cctxt, instance, share_mapping)
view = self._view_builder._show_view(cctxt, share_mapping)
except (exception.ShareNotFound) as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
except (exception.ShareStatusIncorect) as e:
raise webob.exc.HTTPConflict(explanation=e.format_message())
except (exception.ShareMissingExportLocation) as e:
raise webob.exc.HTTPConflict(explanation=e.format_message())
except (exception.ShareProtocolNotSupported) as e:
raise webob.exc.HTTPConflict(explanation=e.format_message())
except (exception.ShareMappingAlreadyExists) as e:
raise webob.exc.HTTPConflict(explanation=e.format_message())
except (exception.ForbiddenSharesNotSupported) as e:
raise webob.exc.HTTPForbidden(explanation=e.format_message())
except (exception.ForbiddenSharesNotConfiguredCorrectly) as e:
raise webob.exc.HTTPConflict(explanation=e.format_message())
return view
@wsgi.api_version("2.97")
@wsgi.response(200)
@wsgi.expected_errors((400, 403, 404))
@validation.query_schema(schema.show_query)
@validation.response_body_schema(schema.show_response)
def show(self, req, server_id, id):
context = req.environ["nova.context"]
# Get instance mapping to query the required cell database
im = _get_instance_mapping(context, server_id)
context.can(
ss_policies.POLICY_ROOT % 'show',
target={'project_id': im.project_id}
)
with nova_context.target_cell(context, im.cell_mapping) as cctxt:
try:
# Ensure the instance exists
self._get_instance_from_server_uuid(cctxt, server_id)
share = objects.ShareMapping.get_by_instance_uuid_and_share_id(
cctxt,
server_id,
id
)
view = self._view_builder._show_view(cctxt, share)
except (exception.ShareNotFound) as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
return view
@wsgi.api_version("2.97")
@wsgi.response(200)
@wsgi.expected_errors((400, 403, 404, 409))
def delete(self, req, server_id, id):
context = req.environ["nova.context"]
# Get instance mapping to query the required cell database
im = _get_instance_mapping(context, server_id)
context.can(
ss_policies.POLICY_ROOT % 'delete',
target={'project_id': im.project_id}
)
with nova_context.target_cell(context, im.cell_mapping) as cctxt:
instance = self._check_instance_in_valid_state(
cctxt,
server_id,
"delete share"
)
try:
# Ensure the instance exists
self._get_instance_from_server_uuid(cctxt, server_id)
share_mapping = (
objects.ShareMapping.get_by_instance_uuid_and_share_id(
cctxt, server_id, id
)
)
share_mapping.status = fields.ShareMappingStatus.DETACHING
share_mapping.save()
self.compute_api.deny_share(cctxt, instance, share_mapping)
except (exception.ShareNotFound) as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())