Files
nova/nova/api/openstack/compute/server_shares.py
T
René Ribaud 5e508a09b3 Attach Manila shares via virtiofs (API)
This patch introduce the REST API modifications to attach/detach a share
and list/show share mappings.

Manila is the OpenStack Shared Filesystems service.
These series of patches implement changes required in Nova to allow the shares
provided by Manila to be associated with and attached to instances using
virtiofs.

Implements: blueprint libvirt-virtiofs-attach-manila-shares

Change-Id: I0255a5697cd4ea148bd91c4f6fd183841d69a333
2024-11-27 14:09:42 +01:00

263 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.Controller.api_version("2.97")
@wsgi.response(200)
@wsgi.expected_errors((400, 403, 404))
@validation.query_schema(schema.index_query)
@validation.response_body_schema(schema.share_list_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.Controller.api_version("2.97")
@wsgi.response(201)
@wsgi.expected_errors((400, 403, 404, 409))
@validation.schema(schema.create, min_version='2.97')
@validation.response_body_schema(schema.share_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.Controller.api_version("2.97")
@wsgi.response(200)
@wsgi.expected_errors((400, 403, 404))
@validation.query_schema(schema.show_query)
@validation.response_body_schema(schema.share_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.Controller.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())