Security service API
Added security service controller to Manila v1 API. Partially implements bp: join-tenant-network Change-Id: Ic11feb44547bf438d925261b587edc828eac31c1
This commit is contained in:
parent
55f42ea27c
commit
d68efa454c
|
@ -312,3 +312,18 @@ class MetadataTemplate(xmlutil.TemplateBuilder):
|
|||
elem.set('key', 0)
|
||||
elem.text = 1
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
|
||||
|
||||
|
||||
def remove_invalid_options(context, search_options, allowed_search_options):
|
||||
"""Remove search options that are not valid for non-admin API/context."""
|
||||
if context.is_admin:
|
||||
# Allow all options
|
||||
return
|
||||
# Otherwise, strip out all unknown options
|
||||
unknown_options = [opt for opt in search_options
|
||||
if opt not in allowed_search_options]
|
||||
bad_options = ", ".join(unknown_options)
|
||||
log_msg = _("Removing options '%(bad_options)s' from query") % locals()
|
||||
LOG.debug(log_msg)
|
||||
for opt in unknown_options:
|
||||
del search_options[opt]
|
||||
|
|
|
@ -26,6 +26,7 @@ import manila.api.openstack
|
|||
from manila.api.v1 import limits
|
||||
from manila.api import versions
|
||||
|
||||
from manila.api.v1 import security_service
|
||||
from manila.api.v1 import share_metadata
|
||||
from manila.api.v1 import share_snapshots
|
||||
from manila.api.v1 import shares
|
||||
|
@ -80,3 +81,8 @@ class APIRouter(manila.api.openstack.APIRouter):
|
|||
self.resources['limits'] = limits.create_resource()
|
||||
mapper.resource("limit", "limits",
|
||||
controller=self.resources['limits'])
|
||||
|
||||
self.resources["security_services"] = \
|
||||
security_service.create_resource()
|
||||
mapper.resource("security-service", "security-services",
|
||||
controller=self.resources['security_services'])
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 Mirantis Inc.
|
||||
# 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.
|
||||
|
||||
"""The security service api."""
|
||||
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from manila.api import common
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.views import security_service as security_service_views
|
||||
from manila.api import xmlutil
|
||||
from manila.common import constants
|
||||
from manila import db
|
||||
from manila import exception
|
||||
from manila.openstack.common import log as logging
|
||||
from manila import policy
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_security_service(elem):
|
||||
attrs = ['id', 'name', 'description', 'type', 'server', 'domain', 'sid',
|
||||
'dns_ip', 'status', 'updated_at', 'created_at']
|
||||
for attr in attrs:
|
||||
elem.set(attr)
|
||||
|
||||
|
||||
class SecurityServiceTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('security_service',
|
||||
selector='security_service')
|
||||
make_security_service(root)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class SecurityServicesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('security_services')
|
||||
elem = xmlutil.SubTemplateElement(root, 'security_service',
|
||||
selector='security_services')
|
||||
make_security_service(elem)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class SecurityServiceController(wsgi.Controller):
|
||||
"""The Shares API controller for the OpenStack API."""
|
||||
|
||||
_view_builder_class = security_service_views.ViewBuilder
|
||||
|
||||
@wsgi.serializers(xml=SecurityServiceTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given security service."""
|
||||
context = req.environ['manila.context']
|
||||
try:
|
||||
security_service = db.security_service_get(context, id)
|
||||
policy.check_policy(context, 'show', security_service)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
return self._view_builder.detail(req, security_service)
|
||||
|
||||
def delete(self, req, id):
|
||||
"""Delete a security service."""
|
||||
context = req.environ['manila.context']
|
||||
|
||||
LOG.audit(_("Delete security service with id: %s"),
|
||||
id, context=context)
|
||||
|
||||
try:
|
||||
security_service = db.security_service_get(context, id)
|
||||
policy.check_policy(context, 'show', security_service)
|
||||
db.security_service_delete(context, id)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
except exception.InvalidShare:
|
||||
raise exc.HTTPForbidden()
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.serializers(xml=SecurityServicesTemplate)
|
||||
def index(self, req):
|
||||
"""Returns a summary list of security services."""
|
||||
return self._get_security_services(req, is_detail=False)
|
||||
|
||||
@wsgi.serializers(xml=SecurityServicesTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of security services."""
|
||||
return self._get_security_services(req, is_detail=True)
|
||||
|
||||
def _get_security_services(self, req, is_detail):
|
||||
"""Returns a list of security services, transformed through view
|
||||
builder.
|
||||
"""
|
||||
context = req.environ['manila.context']
|
||||
policy.check_policy(context, 'get_all_security_services')
|
||||
|
||||
search_opts = {}
|
||||
search_opts.update(req.GET)
|
||||
|
||||
common.remove_invalid_options(
|
||||
context, search_opts, self._get_security_services_search_options())
|
||||
if 'all_tenants' in search_opts:
|
||||
security_services = db.security_service_get_all(context)
|
||||
del search_opts['all_tenants']
|
||||
else:
|
||||
security_services = db.security_service_get_all_by_project(
|
||||
context, context.project_id)
|
||||
|
||||
if search_opts:
|
||||
results = []
|
||||
not_found = object()
|
||||
for service in security_services:
|
||||
for opt, value in search_opts.iteritems():
|
||||
if service.get(opt, not_found) != value:
|
||||
break
|
||||
else:
|
||||
results.append(service)
|
||||
security_services = results
|
||||
|
||||
limited_list = common.limited(security_services, req)
|
||||
|
||||
if is_detail:
|
||||
security_services = self._view_builder.detail_list(
|
||||
req, limited_list)
|
||||
else:
|
||||
security_services = self._view_builder.summary_list(
|
||||
req, limited_list)
|
||||
return security_services
|
||||
|
||||
def _get_security_services_search_options(self):
|
||||
return ('status', 'name', 'id')
|
||||
|
||||
@wsgi.serializers(xml=SecurityServicesTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a security service."""
|
||||
context = req.environ['manila.context']
|
||||
|
||||
if not body or 'security_service' not in body:
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
security_service_data = body['security_service']
|
||||
valid_update_keys = (
|
||||
'description',
|
||||
'name'
|
||||
)
|
||||
|
||||
try:
|
||||
security_service = db.security_service_get(context, id)
|
||||
policy.check_policy(context, 'show', security_service)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
if security_service['status'].lower() in ['new', 'inactive']:
|
||||
update_dict = security_service_data
|
||||
else:
|
||||
update_dict = dict([(key, security_service_data[key])
|
||||
for key in valid_update_keys
|
||||
if key in security_service_data])
|
||||
|
||||
security_service = db.security_service_update(context, id, update_dict)
|
||||
return self._view_builder.detail(req, security_service)
|
||||
|
||||
@wsgi.serializers(xml=SecurityServicesTemplate)
|
||||
def create(self, req, body):
|
||||
"""Creates a new security service."""
|
||||
context = req.environ['manila.context']
|
||||
policy.check_policy(context, 'create')
|
||||
|
||||
if not self.is_valid_body(body, 'security_service'):
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
security_service_args = body['security_service']
|
||||
security_srv_type = security_service_args.get('type')
|
||||
allowed_types = constants.SECURITY_SERVICES_ALLOWED_TYPES
|
||||
if security_srv_type not in allowed_types:
|
||||
raise exception.InvalidInput(
|
||||
reason=(_("Invalid type %(type)s specified for security "
|
||||
"service. Valid types are %(types)s") %
|
||||
{'type': security_srv_type,
|
||||
'types': ','.join(allowed_types)}))
|
||||
security_service_args['project_id'] = context.project_id
|
||||
security_service = db.security_service_create(
|
||||
context, security_service_args)
|
||||
|
||||
return self._view_builder.detail(req, security_service)
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(SecurityServiceController())
|
|
@ -19,9 +19,7 @@ import webob
|
|||
from webob import exc
|
||||
|
||||
from manila.api import common
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.v1 import shares
|
||||
from manila.api.views import share_snapshots as snapshot_views
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
|
@ -113,7 +111,7 @@ class ShareSnapshotsController(wsgi.Controller):
|
|||
search_opts['display_name'] = search_opts['name']
|
||||
del search_opts['name']
|
||||
|
||||
shares.remove_invalid_options(context, search_opts,
|
||||
common.remove_invalid_options(context, search_opts,
|
||||
self._get_snapshots_search_options())
|
||||
|
||||
snapshots = self.share_api.get_all_snapshots(context,
|
||||
|
|
|
@ -19,7 +19,6 @@ import webob
|
|||
from webob import exc
|
||||
|
||||
from manila.api import common
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.views import shares as share_views
|
||||
from manila.api import xmlutil
|
||||
|
@ -39,21 +38,6 @@ def make_share(elem):
|
|||
elem.set(attr)
|
||||
|
||||
|
||||
def remove_invalid_options(context, search_options, allowed_search_options):
|
||||
"""Remove search options that are not valid for non-admin API/context."""
|
||||
if context.is_admin:
|
||||
# Allow all options
|
||||
return
|
||||
# Otherwise, strip out all unknown options
|
||||
unknown_options = [opt for opt in search_options
|
||||
if opt not in allowed_search_options]
|
||||
bad_options = ", ".join(unknown_options)
|
||||
log_msg = _("Removing options '%(bad_options)s' from query") % locals()
|
||||
LOG.debug(log_msg)
|
||||
for opt in unknown_options:
|
||||
del search_options[opt]
|
||||
|
||||
|
||||
class ShareTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('share', selector='share')
|
||||
|
@ -130,8 +114,8 @@ class ShareController(wsgi.Controller):
|
|||
search_opts['display_name'] = search_opts['name']
|
||||
del search_opts['name']
|
||||
|
||||
remove_invalid_options(context, search_opts,
|
||||
self._get_share_search_options())
|
||||
common.remove_invalid_options(
|
||||
context, search_opts, self._get_share_search_options())
|
||||
|
||||
shares = self.share_api.get_all(context, search_opts=search_opts)
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack LLC.
|
||||
# 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 manila.api import common
|
||||
|
||||
|
||||
class ViewBuilder(common.ViewBuilder):
|
||||
"""Model a server API response as a python dictionary."""
|
||||
|
||||
_collection_name = 'security_services'
|
||||
|
||||
def summary_list(self, request, security_services):
|
||||
"""Show a list of security services without many details."""
|
||||
return self._list_view(self.summary, request, security_services)
|
||||
|
||||
def detail_list(self, request, security_services):
|
||||
"""Detailed view of a list of security services."""
|
||||
return self._list_view(self.detail, request, security_services)
|
||||
|
||||
def summary(self, request, security_service):
|
||||
"""Generic, non-detailed view of an security service."""
|
||||
return {
|
||||
'security_service': {
|
||||
'id': security_service.get('id'),
|
||||
'name': security_service.get('name'),
|
||||
'status': security_service.get('status')
|
||||
}
|
||||
}
|
||||
|
||||
def detail(self, request, security_service):
|
||||
"""Detailed view of a single security service."""
|
||||
return {
|
||||
'security_service': {
|
||||
'id': security_service.get('id'),
|
||||
'name': security_service.get('name'),
|
||||
'created_at': security_service.get('created_at'),
|
||||
'updated_at': security_service.get('updated_at'),
|
||||
'status': security_service.get('status'),
|
||||
'description': security_service.get('description'),
|
||||
'dns_ip': security_service.get('dns_ip'),
|
||||
'server': security_service.get('server'),
|
||||
'domain': security_service.get('domain'),
|
||||
'sid': security_service.get('sid'),
|
||||
'type': security_service.get('type')
|
||||
}
|
||||
}
|
||||
|
||||
def _list_view(self, func, request, security_services):
|
||||
"""Provide a view for a list of security services."""
|
||||
security_services_list = [func(request, service)['security_service']
|
||||
for service in security_services]
|
||||
security_services_dict = dict(security_services=security_services_list)
|
||||
return security_services_dict
|
|
@ -20,3 +20,5 @@ STATUS_DELETED = 'DELETED'
|
|||
STATUS_ERROR = 'ERROR'
|
||||
STATUS_ACTIVE = 'ACTIVE'
|
||||
STATUS_INACTIVE = 'INACTIVE'
|
||||
|
||||
SECURITY_SERVICES_ALLOWED_TYPES = ['active_directory', 'ldap', 'kerberos']
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
# under the License.
|
||||
|
||||
"""Policy Engine For Manila"""
|
||||
import functools
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
|
@ -103,3 +104,27 @@ def check_is_admin(roles):
|
|||
credentials = {'roles': roles}
|
||||
|
||||
return policy.enforce(match_list, target, credentials)
|
||||
|
||||
|
||||
def wrap_check_policy(func):
|
||||
"""Check policy corresponding to the wrapped methods prior to execution.
|
||||
|
||||
This decorator requires the first 3 args of the wrapped function
|
||||
to be (self, context, share).
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapped(self, context, target_obj, *args, **kwargs):
|
||||
check_policy(context, func.__name__, target_obj)
|
||||
return func(self, context, target_obj, *args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def check_policy(context, action, target_obj=None):
|
||||
target = {
|
||||
'project_id': context.project_id,
|
||||
'user_id': context.user_id,
|
||||
}
|
||||
target.update(target_obj or {})
|
||||
_action = 'share:%s' % action
|
||||
enforce(context, _action, target)
|
||||
|
|
|
@ -20,16 +20,12 @@
|
|||
Handles all requests relating to shares.
|
||||
"""
|
||||
|
||||
import functools
|
||||
|
||||
from manila.db import base
|
||||
from manila import exception
|
||||
from manila.image import glance
|
||||
from manila.openstack.common import excutils
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common import rpc
|
||||
from manila.openstack.common import timeutils
|
||||
import manila.policy
|
||||
from manila import policy
|
||||
from manila import quota
|
||||
from manila.scheduler import rpcapi as scheduler_rpcapi
|
||||
from manila.share import rpcapi as share_rpcapi
|
||||
|
@ -44,30 +40,6 @@ GB = 1048576 * 1024
|
|||
QUOTAS = quota.QUOTAS
|
||||
|
||||
|
||||
def wrap_check_policy(func):
|
||||
"""Check policy corresponding to the wrapped methods prior to execution.
|
||||
|
||||
This decorator requires the first 3 args of the wrapped function
|
||||
to be (self, context, share).
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapped(self, context, target_obj, *args, **kwargs):
|
||||
check_policy(context, func.__name__, target_obj)
|
||||
return func(self, context, target_obj, *args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def check_policy(context, action, target_obj=None):
|
||||
target = {
|
||||
'project_id': context.project_id,
|
||||
'user_id': context.user_id,
|
||||
}
|
||||
target.update(target_obj or {})
|
||||
_action = 'share:%s' % action
|
||||
manila.policy.enforce(context, _action, target)
|
||||
|
||||
|
||||
class API(base.Base):
|
||||
"""API for interacting with the share manager."""
|
||||
|
||||
|
@ -79,7 +51,7 @@ class API(base.Base):
|
|||
def create(self, context, share_proto, size, name, description,
|
||||
snapshot=None, availability_zone=None, metadata=None):
|
||||
"""Create new share."""
|
||||
check_policy(context, 'create')
|
||||
policy.check_policy(context, 'create')
|
||||
|
||||
self._check_metadata_properties(context, metadata)
|
||||
|
||||
|
@ -190,7 +162,7 @@ class API(base.Base):
|
|||
|
||||
return share
|
||||
|
||||
@wrap_check_policy
|
||||
@policy.wrap_check_policy
|
||||
def delete(self, context, share):
|
||||
"""Delete share."""
|
||||
if context.is_admin and context.project_id != share['project_id']:
|
||||
|
@ -231,7 +203,7 @@ class API(base.Base):
|
|||
|
||||
def create_snapshot(self, context, share, name, description,
|
||||
force=False):
|
||||
check_policy(context, 'create_snapshot', share)
|
||||
policy.check_policy(context, 'create_snapshot', share)
|
||||
|
||||
if ((not force) and (share['status'] != "available")):
|
||||
msg = _("must be available")
|
||||
|
@ -291,7 +263,7 @@ class API(base.Base):
|
|||
self.share_rpcapi.create_snapshot(context, share, snapshot)
|
||||
return snapshot
|
||||
|
||||
@wrap_check_policy
|
||||
@policy.wrap_check_policy
|
||||
def delete_snapshot(self, context, snapshot, force=False):
|
||||
if not force and snapshot['status'] not in ["available", "error"]:
|
||||
msg = _("Share Snapshot status must be available or ")
|
||||
|
@ -302,21 +274,21 @@ class API(base.Base):
|
|||
share = self.db.share_get(context, snapshot['share_id'])
|
||||
self.share_rpcapi.delete_snapshot(context, snapshot, share['host'])
|
||||
|
||||
@wrap_check_policy
|
||||
@policy.wrap_check_policy
|
||||
def update(self, context, share, fields):
|
||||
return self.db.share_update(context, share['id'], fields)
|
||||
|
||||
@wrap_check_policy
|
||||
@policy.wrap_check_policy
|
||||
def snapshot_update(self, context, snapshot, fields):
|
||||
return self.db.share_snapshot_update(context, snapshot['id'], fields)
|
||||
|
||||
def get(self, context, share_id):
|
||||
rv = self.db.share_get(context, share_id)
|
||||
check_policy(context, 'get', rv)
|
||||
policy.check_policy(context, 'get', rv)
|
||||
return rv
|
||||
|
||||
def get_all(self, context, search_opts={}):
|
||||
check_policy(context, 'get_all')
|
||||
policy.check_policy(context, 'get_all')
|
||||
|
||||
search_opts = search_opts or {}
|
||||
|
||||
|
@ -343,12 +315,12 @@ class API(base.Base):
|
|||
return shares
|
||||
|
||||
def get_snapshot(self, context, snapshot_id):
|
||||
check_policy(context, 'get_snapshot')
|
||||
policy.check_policy(context, 'get_snapshot')
|
||||
rv = self.db.share_snapshot_get(context, snapshot_id)
|
||||
return dict(rv.iteritems())
|
||||
|
||||
def get_all_snapshots(self, context, search_opts=None):
|
||||
check_policy(context, 'get_all_snapshots')
|
||||
policy.check_policy(context, 'get_all_snapshots')
|
||||
|
||||
search_opts = search_opts or {}
|
||||
|
||||
|
@ -382,7 +354,7 @@ class API(base.Base):
|
|||
if share['status'] not in ["available"]:
|
||||
msg = _("Share status must be available")
|
||||
raise exception.InvalidShare(reason=msg)
|
||||
check_policy(ctx, 'allow_access')
|
||||
policy.check_policy(ctx, 'allow_access')
|
||||
values = {'share_id': share['id'],
|
||||
'access_type': access_type,
|
||||
'access_to': access_to}
|
||||
|
@ -392,7 +364,7 @@ class API(base.Base):
|
|||
|
||||
def deny_access(self, ctx, share, access):
|
||||
"""Deny access to share."""
|
||||
check_policy(ctx, 'deny_access')
|
||||
policy.check_policy(ctx, 'deny_access')
|
||||
#First check state of the target share
|
||||
if not share['host']:
|
||||
msg = _("Share host is None")
|
||||
|
@ -415,7 +387,7 @@ class API(base.Base):
|
|||
|
||||
def access_get_all(self, context, share):
|
||||
"""Returns all access rules for share."""
|
||||
check_policy(context, 'access_get_all')
|
||||
policy.check_policy(context, 'access_get_all')
|
||||
rules = self.db.share_access_get_all_for_share(context, share['id'])
|
||||
return [{'id': rule.id,
|
||||
'access_type': rule.access_type,
|
||||
|
@ -424,17 +396,17 @@ class API(base.Base):
|
|||
|
||||
def access_get(self, context, access_id):
|
||||
"""Returns access rule with the id."""
|
||||
check_policy(context, 'access_get')
|
||||
policy.check_policy(context, 'access_get')
|
||||
rule = self.db.share_access_get(context, access_id)
|
||||
return rule
|
||||
|
||||
@wrap_check_policy
|
||||
@policy.wrap_check_policy
|
||||
def get_share_metadata(self, context, share):
|
||||
"""Get all metadata associated with a share."""
|
||||
rv = self.db.share_metadata_get(context, share['id'])
|
||||
return dict(rv.iteritems())
|
||||
|
||||
@wrap_check_policy
|
||||
@policy.wrap_check_policy
|
||||
def delete_share_metadata(self, context, share, key):
|
||||
"""Delete the given metadata item from a share."""
|
||||
self.db.share_metadata_delete(context, share['id'], key)
|
||||
|
@ -458,7 +430,7 @@ class API(base.Base):
|
|||
LOG.warn(msg)
|
||||
raise exception.InvalidShareMetadataSize(message=msg)
|
||||
|
||||
@wrap_check_policy
|
||||
@policy.wrap_check_policy
|
||||
def update_share_metadata(self, context, share, metadata, delete=False):
|
||||
"""Updates or creates share metadata.
|
||||
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
# Copyright 2012 NetApp
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
import webob
|
||||
|
||||
from manila.api.v1 import security_service
|
||||
from manila.common import constants
|
||||
from manila import db
|
||||
from manila import exception
|
||||
from manila import test
|
||||
from manila.tests.api import fakes
|
||||
|
||||
|
||||
class ShareApiTest(test.TestCase):
|
||||
"""Share Api Test."""
|
||||
def setUp(self):
|
||||
super(ShareApiTest, self).setUp()
|
||||
self.controller = security_service.SecurityServiceController()
|
||||
self.maxDiff = None
|
||||
self.security_service = {
|
||||
"created_at": "fake-time",
|
||||
"updated_at": "fake-time-2",
|
||||
"id": 1,
|
||||
"name": "fake-name",
|
||||
"description": "Fake Security Service Desc",
|
||||
"type": constants.SECURITY_SERVICES_ALLOWED_TYPES[0],
|
||||
"dns_ip": "1.1.1.1",
|
||||
"server": "fake-server",
|
||||
"domain": "fake-domain",
|
||||
"sid": "fake-sid",
|
||||
"status": "new"
|
||||
}
|
||||
security_service.policy.check_policy = mock.Mock()
|
||||
|
||||
def test_security_service_show(self):
|
||||
db.security_service_get = mock.Mock(return_value=self.security_service)
|
||||
req = fakes.HTTPRequest.blank('/security-services/1')
|
||||
res_dict = self.controller.show(req, '1')
|
||||
expected = self.security_service.copy()
|
||||
expected.update()
|
||||
self.assertEqual(res_dict, {'security_service': self.security_service})
|
||||
|
||||
def test_security_service_show_not_found(self):
|
||||
db.security_service_get = mock.Mock(side_effect=exception.NotFound)
|
||||
req = fakes.HTTPRequest.blank('/shares/1')
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.show,
|
||||
req, '1')
|
||||
|
||||
def test_security_service_create(self):
|
||||
sec_service = self.security_service.copy()
|
||||
db.security_service_create = mock.Mock(
|
||||
return_value=sec_service)
|
||||
req = fakes.HTTPRequest.blank('/security-services')
|
||||
res_dict = self.controller.create(
|
||||
req, {"security_service": sec_service})
|
||||
expected = self.security_service.copy()
|
||||
self.assertEqual(res_dict, {'security_service': expected})
|
||||
|
||||
def test_security_service_create_invalid_types(self):
|
||||
sec_service = self.security_service.copy()
|
||||
sec_service['type'] = 'invalid'
|
||||
req = fakes.HTTPRequest.blank('/security-services')
|
||||
self.assertRaises(exception.InvalidInput, self.controller.create, req,
|
||||
{"security_service": sec_service})
|
||||
|
||||
def test_create_security_service_no_body(self):
|
||||
body = {}
|
||||
req = fakes.HTTPRequest.blank('/security-services')
|
||||
self.assertRaises(webob.exc.HTTPUnprocessableEntity,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
|
||||
def test_security_service_delete(self):
|
||||
db.security_service_delete = mock.Mock()
|
||||
db.security_service_get = mock.Mock()
|
||||
req = fakes.HTTPRequest.blank('/shares/1')
|
||||
resp = self.controller.delete(req, 1)
|
||||
db.security_service_delete.assert_called_once_with(
|
||||
req.environ['manila.context'], 1)
|
||||
self.assertEqual(resp.status_int, 202)
|
||||
|
||||
def test_security_service_delete_not_found(self):
|
||||
db.security_service_get = mock.Mock(side_effect=exception.NotFound)
|
||||
req = fakes.HTTPRequest.blank('/security_services/1')
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.delete,
|
||||
req,
|
||||
1)
|
||||
|
||||
def test_security_service_update_name(self):
|
||||
new = self.security_service.copy()
|
||||
updated = self.security_service.copy()
|
||||
updated['name'] = 'new'
|
||||
db.security_service_get = mock.Mock(return_value=new)
|
||||
db.security_service_update = mock.Mock(return_value=updated)
|
||||
body = {"security_service": {"name": "new"}}
|
||||
req = fakes.HTTPRequest.blank('/security_service/1')
|
||||
res_dict = self.controller.update(req, 1, body)['security_service']
|
||||
self.assertEqual(res_dict['name'], updated['name'])
|
||||
|
||||
def test_security_service_update_description(self):
|
||||
new = self.security_service.copy()
|
||||
updated = self.security_service.copy()
|
||||
updated['description'] = 'new'
|
||||
db.security_service_get = mock.Mock(return_value=new)
|
||||
db.security_service_update = mock.Mock(return_value=updated)
|
||||
body = {"security_service": {"description": "new"}}
|
||||
req = fakes.HTTPRequest.blank('/security_service/1')
|
||||
res_dict = self.controller.update(req, 1, body)['security_service']
|
||||
self.assertEqual(res_dict['description'], updated['description'])
|
||||
|
||||
def test_security_service_list(self):
|
||||
db.security_service_get_all_by_project = mock.Mock(
|
||||
return_value=[self.security_service.copy()])
|
||||
req = fakes.HTTPRequest.blank('/security_services')
|
||||
res_dict = self.controller.index(req)
|
||||
expected = {'security_services': [
|
||||
{'id': self.security_service['id'],
|
||||
'name': self.security_service['name'],
|
||||
'status': self.security_service['status']
|
||||
}
|
||||
]}
|
||||
self.assertEqual(res_dict, expected)
|
|
@ -17,6 +17,7 @@ import datetime
|
|||
|
||||
import webob
|
||||
|
||||
from manila.api import common
|
||||
from manila.api.v1 import shares
|
||||
from manila import context
|
||||
from manila import exception
|
||||
|
@ -268,7 +269,7 @@ class ShareApiTest(test.TestCase):
|
|||
expected_opts = {'a': 'a', 'c': 'c'}
|
||||
allowed_opts = ['a', 'c']
|
||||
self.mox.ReplayAll()
|
||||
shares.remove_invalid_options(ctx, search_opts, allowed_opts)
|
||||
common.remove_invalid_options(ctx, search_opts, allowed_opts)
|
||||
self.assertEqual(search_opts, expected_opts)
|
||||
|
||||
def test_remove_invalid_options_admin(self):
|
||||
|
@ -277,5 +278,5 @@ class ShareApiTest(test.TestCase):
|
|||
expected_opts = {'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd'}
|
||||
allowed_opts = ['a', 'c']
|
||||
self.mox.ReplayAll()
|
||||
shares.remove_invalid_options(ctx, search_opts, allowed_opts)
|
||||
common.remove_invalid_options(ctx, search_opts, allowed_opts)
|
||||
self.assertEqual(search_opts, expected_opts)
|
||||
|
|
|
@ -162,8 +162,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
'share_proto': share['share_proto'],
|
||||
'export_location': share['export_location']}
|
||||
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'create_snapshot', share)
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'create_snapshot', share)
|
||||
self.mox.StubOutWithMock(quota.QUOTAS, 'reserve')
|
||||
quota.QUOTAS.reserve(self.context, snapshots=1, gigabytes=1).\
|
||||
AndReturn('reservation')
|
||||
|
@ -182,8 +182,9 @@ class ShareAPITestCase(test.TestCase):
|
|||
share = fake_share('fakeid')
|
||||
snapshot = fake_snapshot('fakesnapshotid', share_id=share['id'],
|
||||
status='available')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'delete_snapshot', snapshot)
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(
|
||||
self.context, 'delete_snapshot', snapshot)
|
||||
self.mox.StubOutWithMock(db_driver, 'share_snapshot_update')
|
||||
db_driver.share_snapshot_update(self.context, snapshot['id'],
|
||||
{'status': 'deleting'})
|
||||
|
@ -198,8 +199,9 @@ class ShareAPITestCase(test.TestCase):
|
|||
def test_delete_snapshot_wrong_status(self):
|
||||
snapshot = fake_snapshot('fakesnapshotid', share_id='fakeshareid',
|
||||
status='creating')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'delete_snapshot', snapshot)
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(
|
||||
self.context, 'delete_snapshot', snapshot)
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exception.InvalidShareSnapshot,
|
||||
self.api.delete_snapshot, self.context, snapshot)
|
||||
|
@ -207,8 +209,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
def test_create_snapshot_if_share_not_available(self):
|
||||
share = fake_share('fakeid',
|
||||
status='error')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'create_snapshot', share)
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'create_snapshot', share)
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exception.InvalidShare, self.api.create_snapshot,
|
||||
self.context, share, 'fakename', 'fakedesc')
|
||||
|
@ -246,8 +248,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
|
||||
def test_get_snapshot(self):
|
||||
fake_get_snap = {'fake_key': 'fake_val'}
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'get_snapshot')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'get_snapshot')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_snapshot_get')
|
||||
db_driver.share_snapshot_get(self.context,
|
||||
'fakeid').AndReturn(fake_get_snap)
|
||||
|
@ -338,24 +340,24 @@ class ShareAPITestCase(test.TestCase):
|
|||
def test_get(self):
|
||||
self.mox.StubOutWithMock(db_driver, 'share_get')
|
||||
db_driver.share_get(self.context, 'fakeid').AndReturn('fakeshare')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'get', 'fakeshare')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'get', 'fakeshare')
|
||||
self.mox.ReplayAll()
|
||||
result = self.api.get(self.context, 'fakeid')
|
||||
self.assertEqual(result, 'fakeshare')
|
||||
|
||||
def test_get_all_admin_not_all_tenants(self):
|
||||
ctx = context.RequestContext('fakeuid', 'fakepid', id_admin=True)
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(ctx, 'get_all')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(ctx, 'get_all')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_get_all_by_project')
|
||||
db_driver.share_get_all_by_project(ctx, 'fakepid')
|
||||
self.mox.ReplayAll()
|
||||
self.api.get_all(ctx)
|
||||
|
||||
def test_get_all_admin_all_tenants(self):
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'get_all')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'get_all')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_get_all')
|
||||
db_driver.share_get_all(self.context)
|
||||
self.mox.ReplayAll()
|
||||
|
@ -363,8 +365,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
|
||||
def test_get_all_not_admin(self):
|
||||
ctx = context.RequestContext('fakeuid', 'fakepid', id_admin=False)
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(ctx, 'get_all')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(ctx, 'get_all')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_get_all_by_project')
|
||||
db_driver.share_get_all_by_project(ctx, 'fakepid')
|
||||
self.mox.ReplayAll()
|
||||
|
@ -374,8 +376,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
search_opts = {'size': 'fakesize'}
|
||||
fake_objs = [{'name': 'fakename1'}, search_opts]
|
||||
ctx = context.RequestContext('fakeuid', 'fakepid', id_admin=False)
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(ctx, 'get_all')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(ctx, 'get_all')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_get_all_by_project')
|
||||
db_driver.share_get_all_by_project(ctx,
|
||||
'fakepid').AndReturn(fake_objs)
|
||||
|
@ -385,8 +387,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
|
||||
def test_get_all_snapshots_admin_not_all_tenants(self):
|
||||
ctx = context.RequestContext('fakeuid', 'fakepid', id_admin=True)
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(ctx, 'get_all_snapshots')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(ctx, 'get_all_snapshots')
|
||||
self.mox.StubOutWithMock(db_driver,
|
||||
'share_snapshot_get_all_by_project')
|
||||
db_driver.share_snapshot_get_all_by_project(ctx, 'fakepid')
|
||||
|
@ -394,8 +396,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
self.api.get_all_snapshots(ctx)
|
||||
|
||||
def test_get_all_snapshots_admin_all_tenants(self):
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'get_all_snapshots')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'get_all_snapshots')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_snapshot_get_all')
|
||||
db_driver.share_snapshot_get_all(self.context)
|
||||
self.mox.ReplayAll()
|
||||
|
@ -404,8 +406,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
|
||||
def test_get_all_snapshots_not_admin(self):
|
||||
ctx = context.RequestContext('fakeuid', 'fakepid', id_admin=False)
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(ctx, 'get_all_snapshots')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(ctx, 'get_all_snapshots')
|
||||
self.mox.StubOutWithMock(db_driver,
|
||||
'share_snapshot_get_all_by_project')
|
||||
db_driver.share_snapshot_get_all_by_project(ctx, 'fakepid')
|
||||
|
@ -416,8 +418,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
search_opts = {'size': 'fakesize'}
|
||||
fake_objs = [{'name': 'fakename1'}, search_opts]
|
||||
ctx = context.RequestContext('fakeuid', 'fakepid', id_admin=False)
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(ctx, 'get_all_snapshots')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(ctx, 'get_all_snapshots')
|
||||
self.mox.StubOutWithMock(db_driver,
|
||||
'share_snapshot_get_all_by_project')
|
||||
db_driver.share_snapshot_get_all_by_project(ctx, 'fakepid').\
|
||||
|
@ -431,8 +433,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
values = {'share_id': share['id'],
|
||||
'access_type': 'fakeacctype',
|
||||
'access_to': 'fakeaccto'}
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'allow_access')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'allow_access')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_access_create')
|
||||
db_driver.share_access_create(self.context, values).\
|
||||
AndReturn('fakeacc')
|
||||
|
@ -457,8 +459,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
def test_deny_access_error(self):
|
||||
share = fake_share('fakeid', status='available')
|
||||
access = fake_access('fakaccid', state='fakeerror')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'deny_access')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'deny_access')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_access_delete')
|
||||
db_driver.share_access_delete(self.context, access['id'])
|
||||
self.mox.ReplayAll()
|
||||
|
@ -467,8 +469,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
def test_deny_access_active(self):
|
||||
share = fake_share('fakeid', status='available')
|
||||
access = fake_access('fakaccid', state='fakeactive')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'deny_access')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'deny_access')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_access_update')
|
||||
db_driver.share_access_update(self.context, access['id'],
|
||||
{'state': 'fakedeleting'})
|
||||
|
@ -479,31 +481,31 @@ class ShareAPITestCase(test.TestCase):
|
|||
def test_deny_access_not_active_not_error(self):
|
||||
share = fake_share('fakeid', status='available')
|
||||
access = fake_access('fakaccid', state='fakenew')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'deny_access')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'deny_access')
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exception.InvalidShareAccess, self.api.deny_access,
|
||||
self.context, share, access)
|
||||
|
||||
def test_deny_access_status_not_available(self):
|
||||
share = fake_share('fakeid', status='error')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'deny_access')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'deny_access')
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exception.InvalidShare, self.api.deny_access,
|
||||
self.context, share, 'fakeacc')
|
||||
|
||||
def test_deny_access_no_host(self):
|
||||
share = fake_share('fakeid', host=None)
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'deny_access')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'deny_access')
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exception.InvalidShare, self.api.deny_access,
|
||||
self.context, share, 'fakeacc')
|
||||
|
||||
def test_access_get(self):
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'access_get')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'access_get')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_access_get')
|
||||
db_driver.share_access_get(self.context, 'fakeid').AndReturn('fake')
|
||||
self.mox.ReplayAll()
|
||||
|
@ -512,8 +514,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
|
||||
def test_access_get_all(self):
|
||||
share = fake_share('fakeid')
|
||||
self.mox.StubOutWithMock(share_api, 'check_policy')
|
||||
share_api.check_policy(self.context, 'access_get_all')
|
||||
self.mox.StubOutWithMock(share_api.policy, 'check_policy')
|
||||
share_api.policy.check_policy(self.context, 'access_get_all')
|
||||
self.mox.StubOutWithMock(db_driver, 'share_access_get_all_for_share')
|
||||
db_driver.share_access_get_all_for_share(self.context, 'fakeid').\
|
||||
AndReturn([fake_access('fakeacc0id', state='fakenew'),
|
||||
|
|
|
@ -25,7 +25,6 @@ import errno
|
|||
import functools
|
||||
import hashlib
|
||||
import inspect
|
||||
import itertools
|
||||
import os
|
||||
import paramiko
|
||||
import pyclbr
|
||||
|
@ -34,27 +33,22 @@ import re
|
|||
import shlex
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import types
|
||||
import warnings
|
||||
from xml.dom import minidom
|
||||
from xml.parsers import expat
|
||||
from xml import sax
|
||||
from xml.sax import expatreader
|
||||
from xml.sax import saxutils
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from eventlet import event
|
||||
from eventlet.green import subprocess
|
||||
from eventlet import greenthread
|
||||
from eventlet import pools
|
||||
from oslo.config import cfg
|
||||
|
||||
from manila import exception
|
||||
|
||||
from manila.openstack.common import excutils
|
||||
from manila.openstack.common import importutils
|
||||
from manila.openstack.common import lockutils
|
||||
|
|
Loading…
Reference in New Issue