
Currently the RBD store/backend doesn't report the FSID field. To include the FSID field, we need a connection to the RBD cluster. Luckily, while initializing the RBD store, we fetch the FSID and put it in the _url_prefix field (given fsid and pool info are available). We just need to fetch it from the _url_prefix field and return it in the response. If FSID is not set in the RBD store, we return NULL value. This is required for the effort of optimizing the upload volume to image operation from cinder RBD to glance RBD backend. Partial-Implements: blueprint optimize-upload-volume-to-rbd-store Change-Id: I3e7dc11bc632ca0f3e134e0bca7eb2442bf797ca
243 lines
8.6 KiB
Python
243 lines
8.6 KiB
Python
# Copyright (c) 2017 RedHat, Inc.
|
|
#
|
|
# 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 copy
|
|
|
|
import glance_store as g_store
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
import oslo_serialization.jsonutils as json
|
|
import webob.exc
|
|
|
|
from glance.api import policy
|
|
from glance.api.v2 import policy as api_policy
|
|
from glance.common import exception
|
|
from glance.common import wsgi
|
|
import glance.db
|
|
from glance.i18n import _, _LW
|
|
from glance.quota import keystone as ks_quota
|
|
|
|
CONF = cfg.CONF
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class InfoController(object):
|
|
def __init__(self, policy_enforcer=None):
|
|
self.policy = policy_enforcer or policy.Enforcer()
|
|
|
|
def get_image_import(self, req):
|
|
# TODO(jokke): All the rest of the boundaries should be implemented.
|
|
import_methods = {
|
|
'description': 'Import methods available.',
|
|
'type': 'array',
|
|
'value': CONF.get('enabled_import_methods')
|
|
}
|
|
|
|
return {
|
|
'import-methods': import_methods
|
|
}
|
|
|
|
def get_stores(self, req):
|
|
# TODO(abhishekk): This will be removed after config options
|
|
# 'stores' and 'default_store' are removed.
|
|
enabled_backends = CONF.enabled_backends
|
|
if not enabled_backends:
|
|
msg = _("Multi backend is not supported at this site.")
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
|
|
backends = []
|
|
for backend in enabled_backends:
|
|
if backend.startswith("os_glance_"):
|
|
continue
|
|
|
|
stores = {}
|
|
if enabled_backends[backend] == 'swift':
|
|
conf_file = getattr(CONF, backend).swift_store_config_file
|
|
multitenant = getattr(CONF, backend).swift_store_multi_tenant
|
|
if multitenant and conf_file:
|
|
msg = ("The config options 'swift_store_multi_tenant' "
|
|
"and 'swift_store_config_file' are mutually "
|
|
"exclusive. If you intend to use multi-tenant "
|
|
"swift store, please make sure that you have "
|
|
"not set a swift configuration file with the "
|
|
"'swift_store_config_file' option. "
|
|
"Excluding `%s:%s` store details from the "
|
|
"response as it's not configured correctly."
|
|
% (backend, enabled_backends[backend]))
|
|
LOG.warning(_LW(msg))
|
|
continue
|
|
stores['id'] = backend
|
|
description = getattr(CONF, backend).store_description
|
|
if description:
|
|
stores['description'] = description
|
|
if backend == CONF.glance_store.default_backend:
|
|
stores['default'] = "true"
|
|
# Check if http store is configured then mark it as read-only
|
|
if enabled_backends[backend] == 'http':
|
|
stores['read-only'] = "true"
|
|
backends.append(stores)
|
|
|
|
return {'stores': backends}
|
|
|
|
@staticmethod
|
|
def _get_fsid_from_url(store_detail):
|
|
fsid = None
|
|
prefix = 'rbd://'
|
|
url_prefix = store_detail.url_prefix
|
|
# When fsid and pool info are not available,
|
|
# url_prefix is same as prefix
|
|
if url_prefix and url_prefix.startswith(
|
|
prefix) and len(url_prefix) > len(prefix):
|
|
# Remove last trailing forward slash
|
|
url_prefix = (
|
|
url_prefix[:-1] if url_prefix.endswith('/') else url_prefix)
|
|
pieces = url_prefix[len(prefix):].split('/')
|
|
# We expect the rbd store's url format to look like 'rbd://%s/%s/'
|
|
# where the fsid is in the first position; if there are more than
|
|
# 2 pieces, then something has changed in the driver code, so we
|
|
# won't set the fsid
|
|
if len(pieces) == 2:
|
|
fsid = pieces[0]
|
|
return fsid
|
|
|
|
@staticmethod
|
|
def _get_rbd_properties(store_detail):
|
|
return {
|
|
'chunk_size': store_detail.chunk_size,
|
|
'pool': store_detail.pool,
|
|
'thin_provisioning': store_detail.thin_provisioning,
|
|
'fsid': InfoController._get_fsid_from_url(store_detail),
|
|
}
|
|
|
|
@staticmethod
|
|
def _get_file_properties(store_detail):
|
|
return {
|
|
'data_dir': store_detail.datadir,
|
|
'chunk_size': store_detail.chunk_size,
|
|
'thin_provisioning': store_detail.thin_provisioning
|
|
}
|
|
|
|
@staticmethod
|
|
def _get_cinder_properties(store_detail):
|
|
return {
|
|
'volume_type': store_detail.store_conf.cinder_volume_type,
|
|
'use_multipath': store_detail.store_conf.cinder_use_multipath
|
|
}
|
|
|
|
@staticmethod
|
|
def _get_swift_properties(store_detail):
|
|
return {
|
|
'container': getattr(store_detail, 'container', None),
|
|
'large_object_size': store_detail.large_object_size,
|
|
'large_object_chunk_size': store_detail.large_object_chunk_size
|
|
}
|
|
|
|
@staticmethod
|
|
def _get_s3_properties(store_detail):
|
|
return {
|
|
's3_store_large_object_size':
|
|
store_detail.s3_store_large_object_size,
|
|
's3_store_large_object_chunk_size':
|
|
store_detail.s3_store_large_object_chunk_size,
|
|
's3_store_thread_pools':
|
|
store_detail.s3_store_thread_pools
|
|
}
|
|
|
|
@staticmethod
|
|
def _get_http_properties(store_detail):
|
|
# NOTE(mrjoshi): Thre are no useful properties
|
|
# to be exposed.
|
|
return {}
|
|
|
|
def get_stores_detail(self, req):
|
|
enabled_backends = CONF.enabled_backends
|
|
stores = self.get_stores(req).get('stores')
|
|
try:
|
|
api_policy.DiscoveryAPIPolicy(
|
|
req.context,
|
|
enforcer=self.policy).stores_info_detail()
|
|
|
|
store_mapper = {
|
|
'rbd': self._get_rbd_properties,
|
|
'file': self._get_file_properties,
|
|
'cinder': self._get_cinder_properties,
|
|
'swift': self._get_swift_properties,
|
|
's3': self._get_s3_properties,
|
|
'http': self._get_http_properties
|
|
}
|
|
|
|
for store in stores:
|
|
store_type = enabled_backends[store['id']]
|
|
store['type'] = store_type
|
|
store_detail = g_store.get_store_from_store_identifier(
|
|
store['id'])
|
|
store['properties'] = store_mapper.get(store_type)(
|
|
store_detail)
|
|
store['weight'] = getattr(CONF, store['id']).weight
|
|
|
|
except exception.Forbidden as e:
|
|
LOG.debug("User not permitted to view details")
|
|
raise webob.exc.HTTPForbidden(explanation=e.msg)
|
|
|
|
return {'stores': stores}
|
|
|
|
def get_usage(self, req):
|
|
project_usage = ks_quota.get_usage(req.context)
|
|
return {'usage':
|
|
{name: {'usage': usage.usage,
|
|
'limit': usage.limit}
|
|
for name, usage in project_usage.items()}}
|
|
|
|
|
|
class ResponseSerializer(wsgi.JSONResponseSerializer):
|
|
def __init__(self, usage_schema=None):
|
|
super(ResponseSerializer, self).__init__()
|
|
self.schema = usage_schema or get_usage_schema()
|
|
|
|
def get_usage(self, response, usage):
|
|
body = json.dumps(self.schema.filter(usage), ensure_ascii=False)
|
|
response.unicode_body = str(body)
|
|
response.content_type = 'application/json'
|
|
|
|
|
|
_USAGE_SCHEMA = {
|
|
'usage': {
|
|
'type': 'array',
|
|
'items': {
|
|
'type': 'object',
|
|
'additionalProperties': True,
|
|
'validation_data': {
|
|
'type': 'object',
|
|
'additionalProperties': False,
|
|
'properties': {
|
|
'usage': {'type': 'integer'},
|
|
'limit': {'type': 'integer'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def get_usage_schema():
|
|
return glance.schema.Schema('usage', copy.deepcopy(_USAGE_SCHEMA))
|
|
|
|
|
|
def create_resource():
|
|
usage_schema = get_usage_schema()
|
|
serializer = ResponseSerializer(usage_schema)
|
|
return wsgi.Resource(InfoController(), None, serializer)
|