initial merge

Change-Id: Id7cef7826092e191654da872ee1e11c4c6f50ddf
Signed-off-by: Zhijiang Hu <hu.zhijiang@zte.com.cn>
This commit is contained in:
Zhijiang Hu
2016-03-30 14:07:23 +08:00
parent 8f69c4bcc6
commit e2e358b4f8
1770 changed files with 453295 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
# 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 oslo_config import cfg
from daisy.common import wsgi
from daisy.registry.api import v1
from daisy.registry.api import v2
CONF = cfg.CONF
CONF.import_opt('enable_v1_registry', 'daisy.common.config')
CONF.import_opt('enable_v2_registry', 'daisy.common.config')
class API(wsgi.Router):
"""WSGI entry point for all Registry requests."""
def __init__(self, mapper):
mapper = mapper or wsgi.APIMapper()
if CONF.enable_v1_registry:
v1.init(mapper)
if CONF.enable_v2_registry:
v2.init(mapper)
super(API, self).__init__(mapper)

View File

@@ -0,0 +1,449 @@
# Copyright 2010-2011 OpenStack Foundation
# 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 daisy.common import wsgi
from daisy.registry.api.v1 import members
from daisy.registry.api.v1 import hosts
from daisy.registry.api.v1 import config_files
from daisy.registry.api.v1 import config_sets
from daisy.registry.api.v1 import configs
from daisy.registry.api.v1 import networks
from daisy.registry.api.v1 import disk_array
from daisy.registry.api.v1 import template
def init(mapper):
members_resource = members.create_resource()
mapper.connect("/clusters/{cluster_id}/nodes/{host_id}",
controller=members_resource,
action="add_cluster_host",
conditions={'method': ['PUT']})
mapper.connect("/clusters/{cluster_id}/nodes/{host_id}",
controller=members_resource,
action="delete_cluster_host",
conditions={'method': ['DELETE']})
mapper.connect("/clusters/{cluster_id}/nodes/{host_id}",
controller=members_resource,
action="get_cluster_hosts",
conditions={'method': ['GET']})
mapper.connect("/clusters/{cluster_id}/nodes",
controller=members_resource,
action="get_cluster_hosts",
conditions={'method': ['GET']})
mapper.connect("/multi_clusters/nodes/{host_id}",
controller=members_resource,
action="get_host_clusters",
conditions={'method': ['GET']})
hosts_resource = hosts.create_resource()
mapper.connect("/nodes",
controller=hosts_resource,
action="add_host",
conditions={'method': ['POST']})
mapper.connect("/nodes/{id}",
controller=hosts_resource,
action="delete_host",
conditions={'method': ['DELETE']})
mapper.connect("/nodes/{id}",
controller=hosts_resource,
action="update_host",
conditions={'method': ['PUT']})
mapper.connect("/nodes",
controller=hosts_resource,
action="detail_host",
conditions={'method': ['GET']})
mapper.connect("/nodes/{id}",
controller=hosts_resource,
action="get_host",
conditions=dict(method=["GET"]))
mapper.connect("/discover/nodes",
controller=hosts_resource,
action="add_discover_host",
conditions={'method': ['POST']})
mapper.connect("/discover/nodes",
controller=hosts_resource,
action="detail_discover_host",
conditions={'method': ['GET']})
mapper.connect("/discover/nodes/{id}",
controller=hosts_resource,
action="update_discover_host",
conditions={'method': ['PUT']})
mapper.connect("/discover/nodes/{discover_host_id}",
controller=hosts_resource,
action="get_discover_host",
conditions=dict(method=["GET"]))
mapper.connect("/discover/nodes/{id}",
controller=hosts_resource,
action="delete_discover_host",
conditions={'method': ['DELETE']})
mapper.connect("/host-interface",
controller=hosts_resource,
action="get_host_interface",
conditions=dict(method=["GET"]))
mapper.connect("/interfaces/{interface_id}/network/{network_id}",
controller=hosts_resource,
action="get_assigned_network",
conditions=dict(method=["GET"]))
mapper.connect("/host-interfaces",
controller=hosts_resource,
action="get_all_host_interfaces",
conditions=dict(method=["PUT"]))
mapper.connect("/clusters",
controller=hosts_resource,
action="add_cluster",
conditions={'method': ['POST']})
mapper.connect("/clusters/{id}",
controller=hosts_resource,
action="update_cluster",
conditions={'method': ['PUT']})
mapper.connect("/clusters/{id}",
controller=hosts_resource,
action="delete_cluster",
conditions={'method': ['DELETE']})
mapper.connect("/clusters",
controller=hosts_resource,
action='detail_cluster',
conditions={'method': ['GET']})
mapper.connect("/clusters/{id}",
controller=hosts_resource,
action="get_cluster",
conditions=dict(method=["GET"]))
mapper.connect("/components",
controller=hosts_resource,
action="add_component",
conditions={'method': ['POST']})
mapper.connect("/components/{id}",
controller=hosts_resource,
action="delete_component",
conditions={'method': ['DELETE']})
mapper.connect("/components/detail",
controller=hosts_resource,
action='detail_component',
conditions={'method': ['GET']})
mapper.connect("/components/{id}",
controller=hosts_resource,
action="get_component",
conditions=dict(method=["GET"]))
mapper.connect("/components/{id}",
controller=hosts_resource,
action="update_component",
conditions={'method': ['PUT']})
mapper.connect("/services",
controller=hosts_resource,
action="add_service",
conditions={'method': ['POST']})
mapper.connect("/services/{id}",
controller=hosts_resource,
action="delete_service",
conditions={'method': ['DELETE']})
mapper.connect("/services/detail",
controller=hosts_resource,
action='detail_service',
conditions={'method': ['GET']})
mapper.connect("/services/{id}",
controller=hosts_resource,
action="get_service",
conditions=dict(method=["GET"]))
mapper.connect("/services/{id}",
controller=hosts_resource,
action="update_service",
conditions={'method': ['PUT']})
mapper.connect("/roles",
controller=hosts_resource,
action="add_role",
conditions={'method': ['POST']})
mapper.connect("/roles/{id}",
controller=hosts_resource,
action="delete_role",
conditions={'method': ['DELETE']})
mapper.connect("/roles/detail",
controller=hosts_resource,
action='detail_role',
conditions={'method': ['GET']})
mapper.connect("/roles/{id}",
controller=hosts_resource,
action="get_role",
conditions=dict(method=["GET"]))
mapper.connect("/roles/{id}",
controller=hosts_resource,
action="update_role",
conditions={'method': ['PUT']})
mapper.connect("/roles/{id}/services",
controller=hosts_resource,
action="role_services",
conditions={'method': ['GET']})
mapper.connect("/roles/{id}/hosts",
controller=hosts_resource,
action="host_roles",
conditions={'method': ['GET']})
mapper.connect("/roles/{id}/hosts",
controller=hosts_resource,
action="delete_role_hosts",
conditions={'method': ['DELETE']})
mapper.connect("/roles/{id}/hosts",
controller=hosts_resource,
action="update_role_hosts",
conditions={'method': ['PUT']})
config_files_resource = config_files.create_resource()
mapper.connect("/config_files",
controller=config_files_resource,
action="add_config_file",
conditions={'method': ['POST']})
mapper.connect("/config_files/{id}",
controller=config_files_resource,
action="delete_config_file",
conditions={'method': ['DELETE']})
mapper.connect("/config_files/{id}",
controller=config_files_resource,
action="update_config_file",
conditions={'method': ['PUT']})
mapper.connect("/config_files/detail",
controller=config_files_resource,
action="detail_config_file",
conditions={'method': ['GET']})
mapper.connect("/config_files/{id}",
controller=config_files_resource,
action="get_config_file",
conditions=dict(method=["GET"]))
config_sets_resource = config_sets.create_resource()
mapper.connect("/config_sets",
controller=config_sets_resource,
action="add_config_set",
conditions={'method': ['POST']})
mapper.connect("/config_sets/{id}",
controller=config_sets_resource,
action="delete_config_set",
conditions={'method': ['DELETE']})
mapper.connect("/config_sets/{id}",
controller=config_sets_resource,
action="update_config_set",
conditions={'method': ['PUT']})
mapper.connect("/config_sets/detail",
controller=config_sets_resource,
action="detail_config_set",
conditions={'method': ['GET']})
mapper.connect("/config_sets/{id}",
controller=config_sets_resource,
action="get_config_set",
conditions=dict(method=["GET"]))
configs_resource = configs.create_resource()
mapper.connect("/configs",
controller=configs_resource,
action="add_config",
conditions={'method': ['POST']})
mapper.connect("/configs/{id}",
controller=configs_resource,
action="delete_config",
conditions={'method': ['DELETE']})
mapper.connect("/configs/{id}",
controller=configs_resource,
action="update_config",
conditions={'method': ['PUT']})
mapper.connect("/configs/update_config_by_role_hosts",
controller=configs_resource,
action="update_config_by_role_hosts",
conditions={'method': ['POST']})
mapper.connect("/configs/detail",
controller=configs_resource,
action="detail_config",
conditions={'method': ['GET']})
mapper.connect("/configs/{id}",
controller=configs_resource,
action="get_config",
conditions=dict(method=["GET"]))
networks_resource = networks.create_resource()
mapper.connect("/clusters/{id}/networks",
controller=networks_resource,
action="detail_network",
conditions={'method': ['GET']})
mapper.connect("/networks",
controller=networks_resource,
action="get_all_networks",
conditions={'method': ['GET']})
# mapper.resource('network', 'networks',controller=networks_resource,
# collection={'update_phyname_of_network':'POST', 'add_network':"POST"},
# member={'get_network':'GET', 'update_network':'PUT', 'delete_network':'DELETE'})
mapper.connect("/networks",
controller=networks_resource,
action="add_network",
conditions={'method': ['POST']})
mapper.connect("/networks/{network_id}",
controller=networks_resource,
action="delete_network",
conditions={'method': ['DELETE']})
mapper.connect("/networks/{network_id}",
controller=networks_resource,
action="update_network",
conditions={'method': ['PUT']})
mapper.connect("/networks/{id}",
controller=networks_resource,
action="get_network",
conditions=dict(method=["GET"]))
mapper.connect("/networks/update_phyname_of_network",
controller=networks_resource,
action="update_phyname_of_network",
conditions=dict(method=["POST"]))
config_interface_resource = hosts.create_resource()
mapper.connect("/config_interface",
controller=config_interface_resource,
action="config_interface",
conditions={'method': ['POST']})
array_resource = disk_array.create_resource()
mapper.connect("/service_disk",
controller=array_resource,
action='service_disk_add',
conditions={'method': ['POST']})
mapper.connect("/service_disk/{id}",
controller=array_resource,
action='service_disk_delete',
conditions={'method': ['DELETE']})
mapper.connect("/service_disk/{id}",
controller=array_resource,
action='service_disk_update',
conditions={'method': ['PUT']})
mapper.connect("/service_disk/list",
controller=array_resource,
action='service_disk_list',
conditions={'method': ['GET']})
mapper.connect("/service_disk/{id}",
controller=array_resource,
action='service_disk_detail',
conditions={'method': ['GET']})
mapper.connect("/cinder_volume",
controller=array_resource,
action='cinder_volume_add',
conditions={'method': ['POST']})
mapper.connect("/cinder_volume/{id}",
controller=array_resource,
action='cinder_volume_delete',
conditions={'method': ['DELETE']})
mapper.connect("/cinder_volume/{id}",
controller=array_resource,
action='cinder_volume_update',
conditions={'method': ['PUT']})
mapper.connect("/cinder_volume/list",
controller=array_resource,
action='cinder_volume_list',
conditions={'method': ['GET']})
mapper.connect("/cinder_volume/{id}",
controller=array_resource,
action='cinder_volume_detail',
conditions={'method': ['GET']})
template_resource = template.create_resource()
mapper.connect("/template",
controller=template_resource,
action='template_add',
conditions={'method': ['POST']})
mapper.connect("/template/{template_id}",
controller=template_resource,
action='template_update',
conditions={'method': ['PUT']})
mapper.connect("/template/{template_id}",
controller=template_resource,
action='template_delete',
conditions={'method': ['DELETE']})
mapper.connect("/template/list",
controller=template_resource,
action='template_list',
conditions={'method': ['GET']})
mapper.connect("/template/{template_id}",
controller=template_resource,
action='template_detail',
conditions={'method': ['GET']})
mapper.connect("/host_template",
controller=template_resource,
action='host_template_add',
conditions={'method': ['POST']})
mapper.connect("/host_template/{template_id}",
controller=template_resource,
action='host_template_update',
conditions={'method': ['PUT']})
mapper.connect("/host_template/{template_id}",
controller=template_resource,
action='host_template_delete',
conditions={'method': ['DELETE']})
mapper.connect("/host_template/list",
controller=template_resource,
action='host_template_list',
conditions={'method': ['GET']})
mapper.connect("/host_template/{template_id}",
controller=template_resource,
action='host_template_detail',
conditions={'method': ['GET']})
class API(wsgi.Router):
"""WSGI entry point for all Registry requests."""
def __init__(self, mapper):
mapper = mapper or wsgi.APIMapper()
init(mapper)
super(API, self).__init__(mapper)

View File

@@ -0,0 +1,377 @@
# Copyright 2010-2011 OpenStack Foundation
# 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.
"""
Reference implementation registry server WSGI controller
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import timeutils
from webob import exc
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
CONF = cfg.CONF
DISPLAY_FIELDS_IN_INDEX = ['id', 'name','container_format',
'checksum']
SUPPORTED_FILTERS = ['name', 'container_format']
SUPPORTED_SORT_KEYS = ('name', 'container_format',
'id', 'created_at', 'updated_at')
SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
class Controller(object):
def __init__(self):
self.db_api = daisy.db.get_api()
def _get_config_files(self, context, filters, **params):
"""Get config_files, wrapping in exception if necessary."""
try:
return self.db_api.config_file_get_all(context, filters=filters,
**params)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. Config_file %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. Config_file could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to config_file %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. config_file could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to get config_files"))
raise
def detail_config_file(self, req):
"""Return a filtered list of public, non-deleted config_files in detail
:param req: the Request object coming from the wsgi layer
:retval a mapping of the following form::
dict(config_files=[config_file_list])
Where config_file_list is a sequence of mappings containing
all config_file model fields.
"""
params = self._get_query_params(req)
config_files = self._get_config_files(req.context, **params)
return dict(config_files=config_files)
def _get_query_params(self, req):
"""Extract necessary query parameters from http request.
:param req: the Request object coming from the wsgi layer
:retval dictionary of filters to apply to list of config_files
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
'sort_key': [self._get_sort_key(req)],
'sort_dir': [self._get_sort_dir(req)],
'marker': self._get_marker(req),
}
for key, value in params.items():
if value is None:
del params[key]
return params
def _get_filters(self, req):
"""Return a dictionary of query param filters from the request
:param req: the Request object coming from the wsgi layer
:retval a dict of key/value filters
"""
filters = {}
properties = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
filters[param] = req.params.get(param)
if param.startswith('property-'):
_param = param[9:]
properties[_param] = req.params.get(param)
if 'changes-since' in filters:
isotime = filters['changes-since']
try:
filters['changes-since'] = timeutils.parse_isotime(isotime)
except ValueError:
raise exc.HTTPBadRequest(_("Unrecognized changes-since value"))
if 'protected' in filters:
value = self._get_bool(filters['protected'])
if value is None:
raise exc.HTTPBadRequest(_("protected must be True, or "
"False"))
filters['protected'] = value
# only allow admins to filter on 'deleted'
if req.context.is_admin:
deleted_filter = self._parse_deleted_filter(req)
if deleted_filter is not None:
filters['deleted'] = deleted_filter
elif 'changes-since' not in filters:
filters['deleted'] = False
elif 'changes-since' not in filters:
filters['deleted'] = False
if properties:
filters['properties'] = properties
return filters
def _get_limit(self, req):
"""Parse a limit query param into something usable."""
try:
limit = int(req.params.get('limit', CONF.limit_param_default))
except ValueError:
raise exc.HTTPBadRequest(_("limit param must be an integer"))
if limit < 0:
raise exc.HTTPBadRequest(_("limit param must be positive"))
return min(CONF.api_limit_max, limit)
def _get_marker(self, req):
"""Parse a marker query param into something usable."""
marker = req.params.get('marker', None)
if marker and not utils.is_uuid_like(marker):
msg = _('Invalid marker format')
raise exc.HTTPBadRequest(explanation=msg)
return marker
def _get_sort_key(self, req):
"""Parse a sort key query param from the request object."""
sort_key = req.params.get('sort_key', 'created_at')
if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS:
_keys = ', '.join(SUPPORTED_SORT_KEYS)
msg = _("Unsupported sort_key. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_key
def _get_sort_dir(self, req):
"""Parse a sort direction query param from the request object."""
sort_dir = req.params.get('sort_dir', 'desc')
if sort_dir is not None and sort_dir not in SUPPORTED_SORT_DIRS:
_keys = ', '.join(SUPPORTED_SORT_DIRS)
msg = _("Unsupported sort_dir. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_dir
def _get_bool(self, value):
value = value.lower()
if value == 'true' or value == '1':
return True
elif value == 'false' or value == '0':
return False
return None
def _parse_deleted_filter(self, req):
"""Parse deleted into something usable."""
deleted = req.params.get('deleted')
if deleted is None:
return None
return strutils.bool_from_string(deleted)
@utils.mutating
def add_config_file(self, req, body):
"""Registers a new config_file with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the config_file
:retval Returns the newly-created config_file information as a mapping,
which will include the newly-created config_file's internal id
in the 'id' field
"""
config_file_data = body["config_file"]
config_file_id = config_file_data.get('id')
if config_file_id and not utils.is_uuid_like(config_file_id):
msg = _LI("Rejecting config_file creation request for invalid config_file "
"id '%(bad_id)s'") % {'bad_id': config_file_id}
LOG.info(msg)
msg = _("Invalid config_file id format")
return exc.HTTPBadRequest(explanation=msg)
try:
config_file_data = self.db_api.config_file_add(req.context, config_file_data)
msg = (_LI("Successfully created config_file %s") %
config_file_data["id"])
LOG.info(msg)
if 'config_file' not in config_file_data:
config_file_data = dict(config_file=config_file_data)
return config_file_data
except exception.Duplicate:
msg = _("config_file with identifier %s already exists!") % config_file_id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add config_file metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create config_file %s"), config_file_id)
raise
@utils.mutating
def delete_config_file(self, req, id):
"""Deletes an existing config_file with the registry.
:param req: wsgi Request object
:param id: The opaque internal identifier for the image
:retval Returns 200 if delete was successful, a fault if not. On
success, the body contains the deleted image information as a mapping.
"""
try:
deleted_config_file = self.db_api.config_file_destroy(req.context, id)
msg = _LI("Successfully deleted config_file %(id)s") % {'id': id}
LOG.info(msg)
return dict(config_file=deleted_config_file)
except exception.ForbiddenPublicImage:
msg = _LI("Delete denied for public config_file %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config_file %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except exception.NotFound:
msg = _LI("config_file %(id)s not found") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to delete config_file %s") % id)
raise
@utils.mutating
def get_config_file(self, req, id):
"""Return data about the given config_file id."""
try:
config_file_data = self.db_api.config_file_get(req.context, id)
msg = "Successfully retrieved config_file %(id)s" % {'id': id}
LOG.debug(msg)
except exception.NotFound:
msg = _LI("config_file %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config_file %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to show config_file %s") % id)
raise
if 'config_file' not in config_file_data:
config_file_data = dict(config_file=config_file_data)
return config_file_data
@utils.mutating
def update_config_file(self, req, id, body):
"""Updates an existing config_file with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
config_file_data = body['config_file']
try:
updated_config_file = self.db_api.config_file_update(req.context, id, config_file_data)
msg = _LI("Updating metadata for config_file %(id)s") % {'id': id}
LOG.info(msg)
if 'config_file' not in updated_config_file:
config_file_data = dict(config_file=updated_config_file)
return config_file_data
except exception.Invalid as e:
msg = (_("Failed to update config_file metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except exception.NotFound:
msg = _LI("config_file %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='config_file not found',
request=req,
content_type='text/plain')
except exception.ForbiddenPublicImage:
msg = _LI("Update denied for config_file %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config_file %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='config_file not found',
request=req,
content_type='text/plain')
except exception.Conflict as e:
LOG.info(utils.exception_to_str(e))
raise exc.HTTPConflict(body='config_file operation conflicts',
request=req,
content_type='text/plain')
except Exception:
LOG.exception(_LE("Unable to update config_file %s") % id)
raise
def create_resource():
"""Images resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,384 @@
# Copyright 2010-2011 OpenStack Foundation
# 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.
"""
Reference implementation registry server WSGI controller
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import timeutils
from webob import exc
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
CONF = cfg.CONF
DISPLAY_FIELDS_IN_INDEX = ['id', 'name','container_format',
'checksum']
SUPPORTED_FILTERS = ['name', 'container_format']
SUPPORTED_SORT_KEYS = ('name', 'container_format',
'id', 'created_at', 'updated_at')
SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
class Controller(object):
def __init__(self):
self.db_api = daisy.db.get_api()
def _get_config_sets(self, context, filters, **params):
"""Get config_sets, wrapping in exception if necessary."""
try:
return self.db_api.config_set_get_all(context, filters=filters,
**params)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. Config_set %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. Config_set could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to config_set %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. config_set could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to get config_sets"))
raise
def detail_config_set(self, req):
"""Return a filtered list of public, non-deleted config_sets in detail
:param req: the Request object coming from the wsgi layer
:retval a mapping of the following form::
dict(config_sets=[config_set_list])
Where config_set_list is a sequence of mappings containing
all config_set model fields.
"""
params = self._get_query_params(req)
config_sets = self._get_config_sets(req.context, **params)
return dict(config_sets=config_sets)
def _get_query_params(self, req):
"""Extract necessary query parameters from http request.
:param req: the Request object coming from the wsgi layer
:retval dictionary of filters to apply to list of config_sets
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
'sort_key': [self._get_sort_key(req)],
'sort_dir': [self._get_sort_dir(req)],
'marker': self._get_marker(req),
}
for key, value in params.items():
if value is None:
del params[key]
return params
def _get_filters(self, req):
"""Return a dictionary of query param filters from the request
:param req: the Request object coming from the wsgi layer
:retval a dict of key/value filters
"""
filters = {}
properties = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
filters[param] = req.params.get(param)
if param.startswith('property-'):
_param = param[9:]
properties[_param] = req.params.get(param)
if 'changes-since' in filters:
isotime = filters['changes-since']
try:
filters['changes-since'] = timeutils.parse_isotime(isotime)
except ValueError:
raise exc.HTTPBadRequest(_("Unrecognized changes-since value"))
if 'protected' in filters:
value = self._get_bool(filters['protected'])
if value is None:
raise exc.HTTPBadRequest(_("protected must be True, or "
"False"))
filters['protected'] = value
# only allow admins to filter on 'deleted'
if req.context.is_admin:
deleted_filter = self._parse_deleted_filter(req)
if deleted_filter is not None:
filters['deleted'] = deleted_filter
elif 'changes-since' not in filters:
filters['deleted'] = False
elif 'changes-since' not in filters:
filters['deleted'] = False
if properties:
filters['properties'] = properties
return filters
def _get_limit(self, req):
"""Parse a limit query param into something usable."""
try:
limit = int(req.params.get('limit', CONF.limit_param_default))
except ValueError:
raise exc.HTTPBadRequest(_("limit param must be an integer"))
if limit < 0:
raise exc.HTTPBadRequest(_("limit param must be positive"))
return min(CONF.api_limit_max, limit)
def _get_marker(self, req):
"""Parse a marker query param into something usable."""
marker = req.params.get('marker', None)
if marker and not utils.is_uuid_like(marker):
msg = _('Invalid marker format')
raise exc.HTTPBadRequest(explanation=msg)
return marker
def _get_sort_key(self, req):
"""Parse a sort key query param from the request object."""
sort_key = req.params.get('sort_key', 'created_at')
if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS:
_keys = ', '.join(SUPPORTED_SORT_KEYS)
msg = _("Unsupported sort_key. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_key
def _get_sort_dir(self, req):
"""Parse a sort direction query param from the request object."""
sort_dir = req.params.get('sort_dir', 'desc')
if sort_dir is not None and sort_dir not in SUPPORTED_SORT_DIRS:
_keys = ', '.join(SUPPORTED_SORT_DIRS)
msg = _("Unsupported sort_dir. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_dir
def _get_bool(self, value):
value = value.lower()
if value == 'true' or value == '1':
return True
elif value == 'false' or value == '0':
return False
return None
def _parse_deleted_filter(self, req):
"""Parse deleted into something usable."""
deleted = req.params.get('deleted')
if deleted is None:
return None
return strutils.bool_from_string(deleted)
@utils.mutating
def add_config_set(self, req, body):
"""Registers a new config_set with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the config_set
:retval Returns the newly-created config_set information as a mapping,
which will include the newly-created config_set's internal id
in the 'id' field
"""
config_set_data = body["config_set"]
config_set_id = config_set_data.get('id')
if config_set_id and not utils.is_uuid_like(config_set_id):
msg = _LI("Rejecting config_set creation request for invalid config_set "
"id '%(bad_id)s'") % {'bad_id': config_set_id}
LOG.info(msg)
msg = _("Invalid config_set id format")
return exc.HTTPBadRequest(explanation=msg)
try:
config_set_data = self.db_api.config_set_add(req.context, config_set_data)
msg = (_LI("Successfully created config_set %s") %
config_set_data["id"])
LOG.info(msg)
if 'config_set' not in config_set_data:
config_set_data = dict(config_set=config_set_data)
return config_set_data
except exception.Duplicate:
msg = _("config_set with identifier %s already exists!") % config_set_id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add config_set metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create config_set %s"), config_set_id)
raise
@utils.mutating
def delete_config_set(self, req, id):
"""Deletes an existing config_set with the registry.
:param req: wsgi Request object
:param id: The opaque internal identifier for the image
:retval Returns 200 if delete was successful, a fault if not. On
success, the body contains the deleted image information as a mapping.
"""
try:
deleted_config_set = self.db_api.config_set_destroy(req.context, id)
msg = _LI("Successfully deleted config_set %(id)s") % {'id': id}
LOG.info(msg)
return dict(config_set=deleted_config_set)
except exception.ForbiddenPublicImage:
msg = _LI("Delete denied for public config_set %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config_set %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except exception.NotFound:
msg = _LI("config_set %(id)s not found") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to delete config_set %s") % id)
raise
@utils.mutating
def get_config_set(self, req, id):
"""Return data about the given config_set id."""
try:
config_set_data = self.db_api.config_set_get(req.context, id)
msg = "Successfully retrieved config_set %(id)s" % {'id': id}
LOG.debug(msg)
except exception.NotFound:
msg = _LI("config_set %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config_set %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to show config_set %s") % id)
raise
if 'config_set' not in config_set_data:
config_set_data = dict(config_set=config_set_data)
config_items = self.db_api._config_item_get_by_config_set_id(req.context, id)
config = []
for config_item in config_items:
config_inf = self.db_api.config_get(req.context, config_item['config_id'])
config.append(config_inf)
if config:
config_set_data['config_set']['config'] = config
return config_set_data
@utils.mutating
def update_config_set(self, req, id, body):
"""Updates an existing config_set with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
config_set_data = body['config_set']
try:
updated_config_set = self.db_api.config_set_update(req.context, id, config_set_data)
msg = _LI("Updating metadata for config_set %(id)s") % {'id': id}
LOG.info(msg)
if 'config_set' not in updated_config_set:
config_set_data = dict(config_set=updated_config_set)
return config_set_data
except exception.Invalid as e:
msg = (_("Failed to update config_set metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except exception.NotFound:
msg = _LI("config_set %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='config_set not found',
request=req,
content_type='text/plain')
except exception.ForbiddenPublicImage:
msg = _LI("Update denied for config_set %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config_set %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='config_set not found',
request=req,
content_type='text/plain')
except exception.Conflict as e:
LOG.info(utils.exception_to_str(e))
raise exc.HTTPConflict(body='config_set operation conflicts',
request=req,
content_type='text/plain')
except Exception:
LOG.exception(_LE("Unable to update config_set %s") % id)
raise
def create_resource():
"""Images resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,381 @@
# Copyright 2010-2011 OpenStack Foundation
# 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.
"""
Reference implementation registry server WSGI controller
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import timeutils
from webob import exc
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
CONF = cfg.CONF
DISPLAY_FIELDS_IN_INDEX = ['id', 'name','container_format',
'checksum']
SUPPORTED_FILTERS = ['name', 'container_format']
SUPPORTED_SORT_KEYS = ('name', 'container_format',
'id', 'created_at', 'updated_at')
SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
class Controller(object):
def __init__(self):
self.db_api = daisy.db.get_api()
def _get_configs(self, context, filters, **params):
"""Get configs, wrapping in exception if necessary."""
try:
return self.db_api.config_get_all(context, filters=filters,
**params)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. Config %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. Config could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to config %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. config could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to get configs"))
raise
def detail_config(self, req):
"""Return a filtered list of public, non-deleted configs in detail
:param req: the Request object coming from the wsgi layer
:retval a mapping of the following form::
dict(configs=[config_list])
Where config_list is a sequence of mappings containing
all config model fields.
"""
params = self._get_query_params(req)
configs = self._get_configs(req.context, **params)
return dict(configs=configs)
def _get_query_params(self, req):
"""Extract necessary query parameters from http request.
:param req: the Request object coming from the wsgi layer
:retval dictionary of filters to apply to list of configs
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
'sort_key': [self._get_sort_key(req)],
'sort_dir': [self._get_sort_dir(req)],
'marker': self._get_marker(req),
}
for key, value in params.items():
if value is None:
del params[key]
return params
def _get_filters(self, req):
"""Return a dictionary of query param filters from the request
:param req: the Request object coming from the wsgi layer
:retval a dict of key/value filters
"""
filters = {}
properties = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
filters[param] = req.params.get(param)
if param.startswith('property-'):
_param = param[9:]
properties[_param] = req.params.get(param)
if 'changes-since' in filters:
isotime = filters['changes-since']
try:
filters['changes-since'] = timeutils.parse_isotime(isotime)
except ValueError:
raise exc.HTTPBadRequest(_("Unrecognized changes-since value"))
if 'protected' in filters:
value = self._get_bool(filters['protected'])
if value is None:
raise exc.HTTPBadRequest(_("protected must be True, or "
"False"))
filters['protected'] = value
# only allow admins to filter on 'deleted'
if req.context.is_admin:
deleted_filter = self._parse_deleted_filter(req)
if deleted_filter is not None:
filters['deleted'] = deleted_filter
elif 'changes-since' not in filters:
filters['deleted'] = False
elif 'changes-since' not in filters:
filters['deleted'] = False
if properties:
filters['properties'] = properties
return filters
def _get_limit(self, req):
"""Parse a limit query param into something usable."""
try:
limit = int(req.params.get('limit', CONF.limit_param_default))
except ValueError:
raise exc.HTTPBadRequest(_("limit param must be an integer"))
if limit < 0:
raise exc.HTTPBadRequest(_("limit param must be positive"))
return min(CONF.api_limit_max, limit)
def _get_marker(self, req):
"""Parse a marker query param into something usable."""
marker = req.params.get('marker', None)
if marker and not utils.is_uuid_like(marker):
msg = _('Invalid marker format')
raise exc.HTTPBadRequest(explanation=msg)
return marker
def _get_sort_key(self, req):
"""Parse a sort key query param from the request object."""
sort_key = req.params.get('sort_key', 'created_at')
if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS:
_keys = ', '.join(SUPPORTED_SORT_KEYS)
msg = _("Unsupported sort_key. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_key
def _get_sort_dir(self, req):
"""Parse a sort direction query param from the request object."""
sort_dir = req.params.get('sort_dir', 'desc')
if sort_dir is not None and sort_dir not in SUPPORTED_SORT_DIRS:
_keys = ', '.join(SUPPORTED_SORT_DIRS)
msg = _("Unsupported sort_dir. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_dir
def _get_bool(self, value):
value = value.lower()
if value == 'true' or value == '1':
return True
elif value == 'false' or value == '0':
return False
return None
def _parse_deleted_filter(self, req):
"""Parse deleted into something usable."""
deleted = req.params.get('deleted')
if deleted is None:
return None
return strutils.bool_from_string(deleted)
@utils.mutating
def add_config(self, req, body):
"""Registers a new config with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the config
:retval Returns the newly-created config information as a mapping,
which will include the newly-created config's internal id
in the 'id' field
"""
config_data = body["config"]
config_id = config_data.get('id')
if config_id and not utils.is_uuid_like(config_id):
msg = _LI("Rejecting config creation request for invalid config "
"id '%(bad_id)s'") % {'bad_id': config_id}
LOG.info(msg)
msg = _("Invalid config id format")
return exc.HTTPBadRequest(explanation=msg)
try:
config_data = self.db_api.config_add(req.context, config_data)
msg = (_LI("Successfully created config %s") %
config_data["id"])
LOG.info(msg)
if 'config' not in config_data:
config_data = dict(config=config_data)
return config_data
except exception.Duplicate:
msg = _("config with identifier %s already exists!") % config_id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add config metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create config %s"), config_id)
raise
@utils.mutating
def delete_config(self, req, id):
"""Deletes an existing config with the registry.
:param req: wsgi Request object
:param id: The opaque internal identifier for the image
:retval Returns 200 if delete was successful, a fault if not. On
success, the body contains the deleted image information as a mapping.
"""
try:
deleted_config = self.db_api.config_destroy(req.context, id)
msg = _LI("Successfully deleted config %(id)s") % {'id': id}
LOG.info(msg)
return dict(config=deleted_config)
except exception.ForbiddenPublicImage:
msg = _LI("Delete denied for public config %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except exception.NotFound:
msg = _LI("config %(id)s not found") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to delete config %s") % id)
raise
@utils.mutating
def get_config(self, req, id):
"""Return data about the given config id."""
try:
config_data = self.db_api.config_get(req.context, id)
msg = "Successfully retrieved config %(id)s" % {'id': id}
LOG.debug(msg)
except exception.NotFound:
msg = _LI("config %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to show config %s") % id)
raise
if 'config' not in config_data:
config_data = dict(config=config_data)
return config_data
@utils.mutating
def update_config_by_role_hosts(self, req, body):
return self.db_api.update_config_by_role_hosts(req.context, body['configs'])
@utils.mutating
def update_config(self, req, id, body):
"""Updates an existing config with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
config_data = body['config']
try:
updated_config = self.db_api.config_update(req.context, id, config_data)
msg = _LI("Updating metadata for config %(id)s") % {'id': id}
LOG.info(msg)
if 'config' not in updated_config:
config_data = dict(config=updated_config)
return config_data
except exception.Invalid as e:
msg = (_("Failed to update config metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except exception.NotFound:
msg = _LI("config %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='config not found',
request=req,
content_type='text/plain')
except exception.ForbiddenPublicImage:
msg = _LI("Update denied for config %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to config %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='config not found',
request=req,
content_type='text/plain')
except exception.Conflict as e:
LOG.info(utils.exception_to_str(e))
raise exc.HTTPConflict(body='config operation conflicts',
request=req,
content_type='text/plain')
except Exception:
LOG.exception(_LE("Unable to update config %s") % id)
raise
def create_resource():
"""Images resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,575 @@
# Copyright 2010-2011 OpenStack Foundation
# 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.
"""
Reference implementation registry server WSGI controller
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import timeutils
from webob import exc
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
CONF = cfg.CONF
DISPLAY_FIELDS_IN_INDEX = ['id', 'name', 'size',
'disk_format', 'container_format',
'checksum']
SUPPORTED_FILTERS = ['name', 'status', 'role_id', 'container_format', 'disk_format',
'min_ram', 'min_disk', 'size_min', 'size_max',
'changes-since', 'protected']
SUPPORTED_SORT_KEYS = ('name', 'status', 'cluster_id', 'container_format', 'disk_format',
'size', 'id', 'created_at', 'updated_at')
SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir', 'cluster_id')
SUPPORTED_SORT_KEYS = ('name','role_id', 'status', 'container_format', 'disk_format',
'size', 'id', 'created_at', 'updated_at')
SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('role_id','limit', 'marker', 'sort_key', 'sort_dir')
class Controller(object):
def __init__(self):
self.db_api = daisy.db.get_api()
def _get_query_params(self, req):
"""Extract necessary query parameters from http request.
:param req: the Request object coming from the wsgi layer
:retval dictionary of filters to apply to list of service_disks
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
'sort_key': [self._get_sort_key(req)],
'sort_dir': [self._get_sort_dir(req)],
'marker': self._get_marker(req),
}
for key, value in params.items():
if value is None:
del params[key]
return params
def _get_filters(self, req):
"""Return a dictionary of query param filters from the request
:param req: the Request object coming from the wsgi layer
:retval a dict of key/value filters
"""
filters = {}
properties = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
filters[param] = req.params.get(param)
if param.startswith('property-'):
_param = param[9:]
properties[_param] = req.params.get(param)
if 'changes-since' in filters:
isotime = filters['changes-since']
try:
filters['changes-since'] = timeutils.parse_isotime(isotime)
except ValueError:
raise exc.HTTPBadRequest(_("Unrecognized changes-since value"))
if 'protected' in filters:
value = self._get_bool(filters['protected'])
if value is None:
raise exc.HTTPBadRequest(_("protected must be True, or "
"False"))
filters['protected'] = value
# only allow admins to filter on 'deleted'
if req.context.is_admin:
deleted_filter = self._parse_deleted_filter(req)
if deleted_filter is not None:
filters['deleted'] = deleted_filter
elif 'changes-since' not in filters:
filters['deleted'] = False
elif 'changes-since' not in filters:
filters['deleted'] = False
if properties:
filters['properties'] = properties
return filters
def _get_limit(self, req):
"""Parse a limit query param into something usable."""
try:
limit = int(req.params.get('limit', CONF.limit_param_default))
except ValueError:
raise exc.HTTPBadRequest(_("limit param must be an integer"))
if limit < 0:
raise exc.HTTPBadRequest(_("limit param must be positive"))
return min(CONF.api_limit_max, limit)
def _get_marker(self, req):
"""Parse a marker query param into something usable."""
marker = req.params.get('marker', None)
if marker and not utils.is_uuid_like(marker):
msg = _('Invalid marker format')
raise exc.HTTPBadRequest(explanation=msg)
return marker
def _get_sort_key(self, req):
"""Parse a sort key query param from the request object."""
sort_key = req.params.get('sort_key', 'created_at')
if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS:
_keys = ', '.join(SUPPORTED_SORT_KEYS)
msg = _("Unsupported sort_key. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_key
def _get_sort_dir(self, req):
"""Parse a sort direction query param from the request object."""
sort_dir = req.params.get('sort_dir', 'desc')
if sort_dir is not None and sort_dir not in SUPPORTED_SORT_DIRS:
_keys = ', '.join(SUPPORTED_SORT_DIRS)
msg = _("Unsupported sort_dir. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_dir
def _get_bool(self, value):
value = value.lower()
if value == 'true' or value == '1':
return True
elif value == 'false' or value == '0':
return False
return None
def _parse_deleted_filter(self, req):
"""Parse deleted into something usable."""
deleted = req.params.get('deleted')
if deleted is None:
return None
return strutils.bool_from_string(deleted)
@utils.mutating
def service_disk_add(self, req, body):
"""Registers a new service_disk with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the service_disk
:retval Returns the newly-created service_disk information as a mapping,
which will include the newly-created service_disk's internal id
in the 'id' field
"""
service_disk_data = body["service_disk"]
id = service_disk_data.get('id')
# role = service_disk_data.get('role')
# add id and role
# if role
# self.db_api.get_role(req.context,role)
if id and not utils.is_uuid_like(id):
msg = _LI("Rejecting service_disk creation request for invalid service_disk "
"id '%(bad_id)s'") % {'bad_id': id}
LOG.info(msg)
msg = _("Invalid service_disk id format")
return exc.HTTPBadRequest(explanation=msg)
try:
service_disk_data = self.db_api.service_disk_add(req.context, service_disk_data)
#service_disk_data = dict(service_disk=make_image_dict(service_disk_data))
msg = (_LI("Successfully created node %s") %
service_disk_data["id"])
LOG.info(msg)
if 'service_disk' not in service_disk_data:
service_disk_data = dict(service_disk=service_disk_data)
return service_disk_data
except exception.Duplicate:
msg = _("node with identifier %s already exists!") % image_id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add node metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create node %s"), id)
raise
@utils.mutating
def service_disk_delete(self, req, id):
"""Deletes an existing service_disk with the registry.
:param req: wsgi Request object
:param id: The opaque internal identifier for the image
:retval Returns 200 if delete was successful, a fault if not. On
success, the body contains the deleted image information as a mapping.
"""
try:
deleted_service_disk = self.db_api.service_disk_destroy(req.context, id)
msg = _LI("Successfully deleted service_disk %(id)s") % {'id': id}
LOG.info(msg)
return dict(service_disk=deleted_service_disk)
except exception.ForbiddenPublicImage:
msg = _LI("Delete denied for public service_disk %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to service_disk %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except exception.NotFound:
msg = _LI("service_disk %(id)s not found") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to delete service_disk %s") % id)
raise
@utils.mutating
def service_disk_update(self, req, id, body):
"""Updates an existing service_disk with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
service_disk_data = body['service_disk']
try:
updated_service_disk = self.db_api.service_disk_update(req.context, id, service_disk_data)
msg = _LI("Updating metadata for service_disk %(id)s") % {'id': id}
LOG.info(msg)
if 'service_disk' not in updated_service_disk:
service_disk_data = dict(service_disk=updated_service_disk)
return service_disk_data
except exception.Invalid as e:
msg = (_("Failed to update service_disk metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except exception.NotFound:
msg = _LI("service_disk %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='service_disk not found',
request=req,
content_type='text/plain')
except exception.ForbiddenPublicImage:
msg = _LI("Update denied for public service_disk %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
raise
except exception.Conflict as e:
LOG.info(utils.exception_to_str(e))
raise exc.HTTPConflict(body='service_disk operation conflicts',
request=req,
content_type='text/plain')
except Exception:
LOG.exception(_LE("Unable to update service_disk %s") % id)
raise
@utils.mutating
def service_disk_detail(self, req, id):
"""Return data about the given service_disk id."""
try:
service_disk_data = self.db_api.service_disk_detail(req.context, id)
msg = "Successfully retrieved service_disk %(id)s" % {'id': id}
LOG.debug(msg)
except exception.NotFound:
msg = _LI("service_disk %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to service_disk %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to show service_disk %s") % id)
raise
if 'service_disk' not in service_disk_data:
service_disk_data = dict(service_disk=service_disk_data)
return service_disk_data
def _list_service_disks(self, context, filters, params):
"""Get service_disks, wrapping in exception if necessary."""
try:
return self.db_api.service_disk_list(context, filters=filters,
**params)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. service_disk %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. service_disk could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to service_disk %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. service_disk could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to get service_disks"))
raise
def service_disk_list(self, req):
"""Return a filtered list of public, non-deleted service_disks in detail
:param req: the Request object coming from the wsgi layer
:retval a mapping of the following form::
dict(service_disks=[service_disk_list])
Where service_disk_list is a sequence of mappings containing
all service_disk model fields.
"""
params = self._get_query_params(req)
filters = params.pop('filters')
service_disks = self._list_service_disks(req.context, filters, params)
return dict(service_disks=service_disks)
@utils.mutating
def cinder_volume_add(self, req, body):
"""Registers a new cinder_volume with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the cinder_volume
:retval Returns the newly-created cinder_volume information as a mapping,
which will include the newly-created cinder_volume's internal id
in the 'id' field
"""
cinder_volume_data = body["cinder_volume"]
id = cinder_volume_data.get('id')
# role = service_disk_data.get('role')
# add id and role
# if role
# self.db_api.get_role(req.context,role)
if id and not utils.is_uuid_like(id):
msg = _LI("Rejecting cinder_volume creation request for invalid cinder_volume "
"id '%(bad_id)s'") % {'bad_id': id}
LOG.info(msg)
msg = _("Invalid cinder_volume id format")
return exc.HTTPBadRequest(explanation=msg)
try:
cinder_volume_data = self.db_api.cinder_volume_add(req.context, cinder_volume_data)
msg = (_LI("Successfully created cinder_volume %s") %
cinder_volume_data["id"])
LOG.info(msg)
if 'cinder_volume' not in cinder_volume_data:
cinder_volume_data = dict(cinder_volume=cinder_volume_data)
return cinder_volume_data
except exception.Duplicate:
msg = _("cinder_volume with identifier %s already exists!") % id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add cinder_volume metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create cinder_volume %s"), id)
raise
@utils.mutating
def cinder_volume_delete(self, req, id):
"""Deletes an existing cinder_volume with the registry.
:param req: wsgi Request object
:param id: The opaque internal identifier for the image
:retval Returns 200 if delete was successful, a fault if not. On
success, the body contains the deleted image information as a mapping.
"""
try:
deleted_cinder_volume = self.db_api.cinder_volume_destroy(req.context, id)
msg = _LI("Successfully deleted cinder_volume %(cinder_volume_id)s") % {'cinder_volume_id': id}
LOG.info(msg)
return dict(cinder_volume=deleted_cinder_volume)
except exception.ForbiddenPublicImage:
msg = _LI("Delete denied for public cinder_volume %(cinder_volume_id)s") % {'cinder_volume_id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to cinder_volume %(id)s but returning"
" 'not found'") % {'cinder_volume_id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except exception.NotFound:
msg = _LI("cinder_volume %(cinder_volume_id)s not found") % {'cinder_volume_id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to delete cinder_volume %s") % id)
raise
@utils.mutating
def cinder_volume_update(self, req, id, body):
"""Updates an existing cinder_volume with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
cinder_volume_data = body['cinder_volume']
try:
updated_cinder_volume = self.db_api.cinder_volume_update(req.context, id, cinder_volume_data)
msg = _LI("Updating metadata for cinder_volume %(cinder_volume_id)s") % {'cinder_volume_id': id}
LOG.info(msg)
if 'cinder_volume' not in updated_cinder_volume:
cinder_volume_data = dict(cinder_volume=updated_cinder_volume)
return cinder_volume_data
except exception.Invalid as e:
msg = (_("Failed to update cinder_volume metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except exception.NotFound:
msg = _LI("cinder_volume %(cinder_volume_id)s not found") % {'cinder_volume_id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='cinder_volume not found',
request=req,
content_type='text/plain')
except exception.ForbiddenPublicImage:
msg = _LI("Update denied for public cinder_volume %(cinder_volume_id)s") % {'cinder_volume_id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
raise
except exception.Conflict as e:
LOG.info(utils.exception_to_str(e))
raise exc.HTTPConflict(body='cinder_volume operation conflicts',
request=req,
content_type='text/plain')
except Exception:
LOG.exception(_LE("Unable to update cinder_volume %s") % id)
raise
@utils.mutating
def cinder_volume_detail(self, req, id):
"""Return data about the given cinder_volume id."""
try:
cinder_volume_data = self.db_api.cinder_volume_detail(req.context, id)
msg = "Successfully retrieved cinder_volume %(id)s" % {'id': id}
LOG.debug(msg)
except exception.NotFound:
msg = _LI("cinder_volume %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to cinder_volume %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to show cinder_volume %s") % id)
raise
if 'cinder_volume' not in cinder_volume_data:
cinder_volume_data = dict(cinder_volume=cinder_volume_data)
return cinder_volume_data
def _list_cinder_volumes(self, context, filters, params):
"""Get cinder_volumes, wrapping in exception if necessary."""
try:
return self.db_api.cinder_volume_list(context, filters=filters,
**params)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. cinder_volume %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. cinder_volume could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to cinder_volume %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. cinder_volume could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to get cinder_volumes"))
raise
def cinder_volume_list(self, req):
"""Return a filtered list of public, non-deleted cinder_volumes in detail
:param req: the Request object coming from the wsgi layer
:retval a mapping of the following form::
dict(cinder_volumes=[cinder_volume_list])
Where cinder_volume_list is a sequence of mappings containing
all service_disk model fields.
"""
params = self._get_query_params(req)
filters = params.pop('filters')
cinder_volumes = self._list_cinder_volumes(req.context, filters, params)
return dict(cinder_volumes=cinder_volumes)
def create_resource():
"""Images resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,555 @@
# Copyright 2010-2011 OpenStack Foundation
# 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.
"""
Reference implementation registry server WSGI controller
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import timeutils
from webob import exc
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
CONF = cfg.CONF
DISPLAY_FIELDS_IN_INDEX = ['id', 'name', 'size',
'disk_format', 'container_format',
'checksum']
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
'min_ram', 'min_disk', 'size_min', 'size_max',
'changes-since', 'protected']
SUPPORTED_SORT_KEYS = ('name', 'status', 'container_format', 'disk_format',
'size', 'id', 'created_at', 'updated_at')
SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
def _normalize_image_location_for_db(image_data):
"""
This function takes the legacy locations field and the newly added
location_data field from the image_data values dictionary which flows
over the wire between the registry and API servers and converts it
into the location_data format only which is then consumable by the
Image object.
:param image_data: a dict of values representing information in the image
:return: a new image data dict
"""
if 'locations' not in image_data and 'location_data' not in image_data:
image_data['locations'] = None
return image_data
locations = image_data.pop('locations', [])
location_data = image_data.pop('location_data', [])
location_data_dict = {}
for l in locations:
location_data_dict[l] = {}
for l in location_data:
location_data_dict[l['url']] = {'metadata': l['metadata'],
'status': l['status'],
# Note(zhiyan): New location has no ID.
'id': l['id'] if 'id' in l else None}
# NOTE(jbresnah) preserve original order. tests assume original order,
# should that be defined functionality
ordered_keys = locations[:]
for ld in location_data:
if ld['url'] not in ordered_keys:
ordered_keys.append(ld['url'])
location_data = []
for loc in ordered_keys:
data = location_data_dict[loc]
if data:
location_data.append({'url': loc,
'metadata': data['metadata'],
'status': data['status'],
'id': data['id']})
else:
location_data.append({'url': loc,
'metadata': {},
'status': 'active',
'id': None})
image_data['locations'] = location_data
return image_data
class Controller(object):
def __init__(self):
self.db_api = daisy.db.get_api()
def _get_images(self, context, filters, **params):
"""Get images, wrapping in exception if necessary."""
# NOTE(markwash): for backwards compatibility, is_public=True for
# admins actually means "treat me as if I'm not an admin and show me
# all my images"
if context.is_admin and params.get('is_public') is True:
params['admin_as_user'] = True
del params['is_public']
try:
return self.db_api.image_get_all(context, filters=filters,
**params)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. Image %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. Image could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to image %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. Image could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to get images"))
raise
def index(self, req):
"""Return a basic filtered list of public, non-deleted images
:param req: the Request object coming from the wsgi layer
:retval a mapping of the following form::
dict(images=[image_list])
Where image_list is a sequence of mappings::
{
'id': <ID>,
'name': <NAME>,
'size': <SIZE>,
'disk_format': <DISK_FORMAT>,
'container_format': <CONTAINER_FORMAT>,
'checksum': <CHECKSUM>
}
"""
params = self._get_query_params(req)
images = self._get_images(req.context, **params)
results = []
for image in images:
result = {}
for field in DISPLAY_FIELDS_IN_INDEX:
result[field] = image[field]
results.append(result)
LOG.debug("Returning image list")
return dict(images=results)
def detail(self, req):
"""Return a filtered list of public, non-deleted images in detail
:param req: the Request object coming from the wsgi layer
:retval a mapping of the following form::
dict(images=[image_list])
Where image_list is a sequence of mappings containing
all image model fields.
"""
params = self._get_query_params(req)
images = self._get_images(req.context, **params)
image_dicts = [make_image_dict(i) for i in images]
LOG.debug("Returning detailed image list")
return dict(images=image_dicts)
def _get_query_params(self, req):
"""Extract necessary query parameters from http request.
:param req: the Request object coming from the wsgi layer
:retval dictionary of filters to apply to list of images
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
'sort_key': [self._get_sort_key(req)],
'sort_dir': [self._get_sort_dir(req)],
'marker': self._get_marker(req),
}
if req.context.is_admin:
# Only admin gets to look for non-public images
params['is_public'] = self._get_is_public(req)
for key, value in params.items():
if value is None:
del params[key]
# Fix for LP Bug #1132294
# Ensure all shared images are returned in v1
params['member_status'] = 'all'
return params
def _get_filters(self, req):
"""Return a dictionary of query param filters from the request
:param req: the Request object coming from the wsgi layer
:retval a dict of key/value filters
"""
filters = {}
properties = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
filters[param] = req.params.get(param)
if param.startswith('property-'):
_param = param[9:]
properties[_param] = req.params.get(param)
if 'changes-since' in filters:
isotime = filters['changes-since']
try:
filters['changes-since'] = timeutils.parse_isotime(isotime)
except ValueError:
raise exc.HTTPBadRequest(_("Unrecognized changes-since value"))
if 'protected' in filters:
value = self._get_bool(filters['protected'])
if value is None:
raise exc.HTTPBadRequest(_("protected must be True, or "
"False"))
filters['protected'] = value
# only allow admins to filter on 'deleted'
if req.context.is_admin:
deleted_filter = self._parse_deleted_filter(req)
if deleted_filter is not None:
filters['deleted'] = deleted_filter
elif 'changes-since' not in filters:
filters['deleted'] = False
elif 'changes-since' not in filters:
filters['deleted'] = False
if properties:
filters['properties'] = properties
return filters
def _get_limit(self, req):
"""Parse a limit query param into something usable."""
try:
limit = int(req.params.get('limit', CONF.limit_param_default))
except ValueError:
raise exc.HTTPBadRequest(_("limit param must be an integer"))
if limit < 0:
raise exc.HTTPBadRequest(_("limit param must be positive"))
return min(CONF.api_limit_max, limit)
def _get_marker(self, req):
"""Parse a marker query param into something usable."""
marker = req.params.get('marker', None)
if marker and not utils.is_uuid_like(marker):
msg = _('Invalid marker format')
raise exc.HTTPBadRequest(explanation=msg)
return marker
def _get_sort_key(self, req):
"""Parse a sort key query param from the request object."""
sort_key = req.params.get('sort_key', 'created_at')
if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS:
_keys = ', '.join(SUPPORTED_SORT_KEYS)
msg = _("Unsupported sort_key. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_key
def _get_sort_dir(self, req):
"""Parse a sort direction query param from the request object."""
sort_dir = req.params.get('sort_dir', 'desc')
if sort_dir is not None and sort_dir not in SUPPORTED_SORT_DIRS:
_keys = ', '.join(SUPPORTED_SORT_DIRS)
msg = _("Unsupported sort_dir. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_dir
def _get_bool(self, value):
value = value.lower()
if value == 'true' or value == '1':
return True
elif value == 'false' or value == '0':
return False
return None
def _get_is_public(self, req):
"""Parse is_public into something usable."""
is_public = req.params.get('is_public', None)
if is_public is None:
# NOTE(vish): This preserves the default value of showing only
# public images.
return True
elif is_public.lower() == 'none':
return None
value = self._get_bool(is_public)
if value is None:
raise exc.HTTPBadRequest(_("is_public must be None, True, or "
"False"))
return value
def _parse_deleted_filter(self, req):
"""Parse deleted into something usable."""
deleted = req.params.get('deleted')
if deleted is None:
return None
return strutils.bool_from_string(deleted)
def show(self, req, id):
"""Return data about the given image id."""
try:
image = self.db_api.image_get(req.context, id)
msg = "Successfully retrieved image %(id)s" % {'id': id}
LOG.debug(msg)
except exception.NotFound:
msg = _LI("Image %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to image %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to show image %s") % id)
raise
return dict(image=make_image_dict(image))
@utils.mutating
def delete(self, req, id):
"""Deletes an existing image with the registry.
:param req: wsgi Request object
:param id: The opaque internal identifier for the image
:retval Returns 200 if delete was successful, a fault if not. On
success, the body contains the deleted image information as a mapping.
"""
try:
deleted_image = self.db_api.image_destroy(req.context, id)
msg = _LI("Successfully deleted image %(id)s") % {'id': id}
LOG.info(msg)
return dict(image=make_image_dict(deleted_image))
except exception.ForbiddenPublicImage:
msg = _LI("Delete denied for public image %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to image %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except exception.NotFound:
msg = _LI("Image %(id)s not found") % {'id': id}
LOG.info(msg)
return exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to delete image %s") % id)
raise
@utils.mutating
def create(self, req, body):
"""Registers a new image with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:retval Returns the newly-created image information as a mapping,
which will include the newly-created image's internal id
in the 'id' field
"""
image_data = body['image']
# Ensure the image has a status set
image_data.setdefault('status', 'active')
# Set up the image owner
if not req.context.is_admin or 'owner' not in image_data:
image_data['owner'] = req.context.owner
image_id = image_data.get('id')
if image_id and not utils.is_uuid_like(image_id):
msg = _LI("Rejecting image creation request for invalid image "
"id '%(bad_id)s'") % {'bad_id': image_id}
LOG.info(msg)
msg = _("Invalid image id format")
return exc.HTTPBadRequest(explanation=msg)
if 'location' in image_data:
image_data['locations'] = [image_data.pop('location')]
try:
image_data = _normalize_image_location_for_db(image_data)
image_data = self.db_api.image_create(req.context, image_data)
image_data = dict(image=make_image_dict(image_data))
msg = (_LI("Successfully created image %(id)s") %
image_data['image'])
LOG.info(msg)
return image_data
except exception.Duplicate:
msg = _("Image with identifier %s already exists!") % image_id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add image metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create image %s"), image_id)
raise
@utils.mutating
def update(self, req, id, body):
"""Updates an existing image with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
image_data = body['image']
from_state = body.get('from_state', None)
# Prohibit modification of 'owner'
if not req.context.is_admin and 'owner' in image_data:
del image_data['owner']
if 'location' in image_data:
image_data['locations'] = [image_data.pop('location')]
purge_props = req.headers.get("X-Glance-Registry-Purge-Props", "false")
try:
LOG.debug("Updating image %(id)s with metadata: %(image_data)r",
{'id': id,
'image_data': dict((k, v) for k, v in image_data.items()
if k != 'locations')})
image_data = _normalize_image_location_for_db(image_data)
if purge_props == "true":
purge_props = True
else:
purge_props = False
updated_image = self.db_api.image_update(req.context, id,
image_data,
purge_props=purge_props,
from_state=from_state)
msg = _LI("Updating metadata for image %(id)s") % {'id': id}
LOG.info(msg)
return dict(image=make_image_dict(updated_image))
except exception.Invalid as e:
msg = (_("Failed to update image metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except exception.NotFound:
msg = _LI("Image %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='Image not found',
request=req,
content_type='text/plain')
except exception.ForbiddenPublicImage:
msg = _LI("Update denied for public image %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to image %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='Image not found',
request=req,
content_type='text/plain')
except exception.Conflict as e:
LOG.info(utils.exception_to_str(e))
raise exc.HTTPConflict(body='Image operation conflicts',
request=req,
content_type='text/plain')
except Exception:
LOG.exception(_LE("Unable to update image %s") % id)
raise
def _limit_locations(image):
locations = image.pop('locations', [])
image['location_data'] = locations
image['location'] = None
for loc in locations:
if loc['status'] == 'active':
image['location'] = loc['url']
break
def make_image_dict(image):
"""Create a dict representation of an image which we can use to
serialize the image.
"""
def _fetch_attrs(d, attrs):
return dict([(a, d[a]) for a in attrs
if a in d.keys()])
# TODO(sirp): should this be a dict, or a list of dicts?
# A plain dict is more convenient, but list of dicts would provide
# access to created_at, etc
properties = dict((p['name'], p['value'])
for p in image['properties'] if not p['deleted'])
image_dict = _fetch_attrs(image, daisy.db.IMAGE_ATTRS)
image_dict['properties'] = properties
_limit_locations(image_dict)
return image_dict
def create_resource():
"""Images resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,447 @@
# Copyright 2010-2011 OpenStack Foundation
# 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 oslo_log import log as logging
import webob.exc
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
_LI = i18n._LI
_LW = i18n._LW
class Controller(object):
def __init__(self):
self.db_api = daisy.db.get_api()
def get_cluster_hosts(self, req, cluster_id, host_id=None):
"""
Get the members of an cluster.
"""
try:
self.db_api.cluster_get(req.context, cluster_id)
except exception.NotFound:
msg = _("Project %(id)s not found") % {'id': cluster_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound(msg)
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LW("Access denied to cluster %(id)s but returning"
" 'not found'") % {'id': cluster_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound()
members = self.db_api.cluster_host_member_find(req.context, cluster_id=cluster_id, host_id=host_id)
msg = "Returning member list for cluster %(id)s" % {'id': cluster_id}
LOG.debug(msg)
return dict(members=make_member_list(members,
host_id='host_id'))
@utils.mutating
def update_all(self, req, image_id, body):
"""
Replaces the members of the image with those specified in the
body. The body is a dict with the following format::
{"memberships": [
{"member_id": <MEMBER_ID>,
["can_share": [True|False]]}, ...
]}
"""
self._check_can_access_image_members(req.context)
# Make sure the image exists
try:
image = self.db_api.image_get(req.context, image_id)
except exception.NotFound:
msg = _("Image %(id)s not found") % {'id': image_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound(msg)
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LW("Access denied to image %(id)s but returning"
" 'not found'") % {'id': image_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound()
# Can they manipulate the membership?
if not self.is_image_sharable(req.context, image):
msg = (_LW("User lacks permission to share image %(id)s") %
{'id': image_id})
LOG.warn(msg)
msg = _("No permission to share that image")
raise webob.exc.HTTPForbidden(msg)
# Get the membership list
try:
memb_list = body['memberships']
except Exception as e:
# Malformed entity...
msg = _LW("Invalid membership association specified for "
"image %(id)s") % {'id': image_id}
LOG.warn(msg)
msg = (_("Invalid membership association: %s") %
utils.exception_to_str(e))
raise webob.exc.HTTPBadRequest(explanation=msg)
add = []
existing = {}
# Walk through the incoming memberships
for memb in memb_list:
try:
datum = dict(image_id=image['id'],
member=memb['member_id'],
can_share=None)
except Exception as e:
# Malformed entity...
msg = _LW("Invalid membership association specified for "
"image %(id)s") % {'id': image_id}
LOG.warn(msg)
msg = (_("Invalid membership association: %s") %
utils.exception_to_str(e))
raise webob.exc.HTTPBadRequest(explanation=msg)
# Figure out what can_share should be
if 'can_share' in memb:
datum['can_share'] = bool(memb['can_share'])
# Try to find the corresponding membership
members = self.db_api.image_member_find(req.context,
image_id=datum['image_id'],
member=datum['member'])
try:
member = members[0]
except IndexError:
# Default can_share
datum['can_share'] = bool(datum['can_share'])
add.append(datum)
else:
# Are we overriding can_share?
if datum['can_share'] is None:
datum['can_share'] = members[0]['can_share']
existing[member['id']] = {
'values': datum,
'membership': member,
}
# We now have a filtered list of memberships to add and
# memberships to modify. Let's start by walking through all
# the existing image memberships...
existing_members = self.db_api.image_member_find(req.context,
image_id=image['id'])
for member in existing_members:
if member['id'] in existing:
# Just update the membership in place
update = existing[member['id']]['values']
self.db_api.image_member_update(req.context,
member['id'],
update)
else:
# Outdated one; needs to be deleted
self.db_api.image_member_delete(req.context, member['id'])
# Now add the non-existent ones
for memb in add:
self.db_api.image_member_create(req.context, memb)
# Make an appropriate result
msg = (_LI("Successfully updated memberships for image %(id)s") %
{'id': image_id})
LOG.info(msg)
return webob.exc.HTTPNoContent()
@utils.mutating
def update(self, req, image_id, id, body=None):
"""
Adds a membership to the image, or updates an existing one.
If a body is present, it is a dict with the following format::
{"member": {
"can_share": [True|False]
}}
If "can_share" is provided, the member's ability to share is
set accordingly. If it is not provided, existing memberships
remain unchanged and new memberships default to False.
"""
self._check_can_access_image_members(req.context)
# Make sure the image exists
try:
image = self.db_api.image_get(req.context, image_id)
except exception.NotFound:
msg = _("Image %(id)s not found") % {'id': image_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound(msg)
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LW("Access denied to image %(id)s but returning"
" 'not found'") % {'id': image_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound()
# Can they manipulate the membership?
if not self.is_image_sharable(req.context, image):
msg = (_LW("User lacks permission to share image %(id)s") %
{'id': image_id})
LOG.warn(msg)
msg = _("No permission to share that image")
raise webob.exc.HTTPForbidden(msg)
# Determine the applicable can_share value
can_share = None
if body:
try:
can_share = bool(body['member']['can_share'])
except Exception as e:
# Malformed entity...
msg = _LW("Invalid membership association specified for "
"image %(id)s") % {'id': image_id}
LOG.warn(msg)
msg = (_("Invalid membership association: %s") %
utils.exception_to_str(e))
raise webob.exc.HTTPBadRequest(explanation=msg)
# Look up an existing membership...
members = self.db_api.image_member_find(req.context,
image_id=image_id,
member=id)
if members:
if can_share is not None:
values = dict(can_share=can_share)
self.db_api.image_member_update(req.context,
members[0]['id'],
values)
else:
values = dict(image_id=image['id'], member=id,
can_share=bool(can_share))
self.db_api.image_member_create(req.context, values)
msg = (_LI("Successfully updated a membership for image %(id)s") %
{'id': image_id})
LOG.info(msg)
return webob.exc.HTTPNoContent()
@utils.mutating
def delete(self, req, image_id, id):
"""
Removes a membership from the image.
"""
self._check_can_access_image_members(req.context)
# Make sure the image exists
try:
image = self.db_api.image_get(req.context, image_id)
except exception.NotFound:
msg = _("Image %(id)s not found") % {'id': image_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound(msg)
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LW("Access denied to image %(id)s but returning"
" 'not found'") % {'id': image_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound()
# Can they manipulate the membership?
if not self.is_image_sharable(req.context, image):
msg = (_LW("User lacks permission to share image %(id)s") %
{'id': image_id})
LOG.warn(msg)
msg = _("No permission to share that image")
raise webob.exc.HTTPForbidden(msg)
# Look up an existing membership
members = self.db_api.image_member_find(req.context,
image_id=image_id,
member=id)
if members:
self.db_api.image_member_delete(req.context, members[0]['id'])
else:
msg = ("%(id)s is not a member of image %(image_id)s" %
{'id': id, 'image_id': image_id})
LOG.debug(msg)
msg = _("Membership could not be found.")
raise webob.exc.HTTPNotFound(explanation=msg)
# Make an appropriate result
msg = (_LI("Successfully deleted a membership from image %(id)s") %
{'id': image_id})
LOG.info(msg)
return webob.exc.HTTPNoContent()
@utils.mutating
def add_cluster_host(self, req, cluster_id, host_id, body=None):
"""
Adds a host to cluster.
"""
# Make sure the cluster exists
try:
cluster = self.db_api.cluster_get(req.context, cluster_id)
except exception.NotFound:
msg = _("Project %(id)s not found") % {'id': cluster_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound(msg)
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LW("Access denied to cluster %(id)s but returning"
" 'not found'") % {'id': cluster_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound()
# Make sure the host exists
try:
host = self.db_api.host_get(req.context, host_id)
except exception.NotFound:
msg = _("Host %(id)s not found") % {'id': host_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound(msg)
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LW("Access denied to host %(id)s but returning"
" 'not found'") % {'id': host_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound()
# Look up an existing membership...
members = self.db_api.cluster_host_member_find(req.context,
cluster_id=cluster_id,
host_id=host_id)
if members:
msg = (_LI("Project %(cluster_id)s has host %(id)s membership already!") %
{'cluster_id': image_id,'host_id': host_id})
else:
values = dict(cluster_id=cluster_id, host_id=host_id)
self.db_api.cluster_host_member_create(req.context, values)
msg = (_LI("Successfully added a host %(host_id)s to cluster %(cluster_id)s") %
{'host_id':host_id,'cluster_id': cluster_id})
LOG.info(msg)
return webob.exc.HTTPNoContent()
@utils.mutating
def delete_cluster_host(self, req, cluster_id, host_id):
"""
Removes a host from cluster.
"""
# Make sure the cluster exists
try:
cluster = self.db_api.cluster_get(req.context, cluster_id)
except exception.NotFound:
msg = _("Project %(id)s not found") % {'id': cluster_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound(msg)
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LW("Access denied to cluster %(id)s but returning"
" 'not found'") % {'id': cluster_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound()
# Make sure the host exists
try:
host = self.db_api.host_get(req.context, host_id)
except exception.NotFound:
msg = _("Host %(id)s not found") % {'id': host_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound(msg)
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LW("Access denied to host %(id)s but returning"
" 'not found'") % {'id': host_id}
LOG.warn(msg)
raise webob.exc.HTTPNotFound()
# Look up an existing membership
members = self.db_api.cluster_host_member_find(req.context,
cluster_id=cluster_id,
host_id=host_id)
if members:
self.db_api.cluster_host_member_delete(req.context, members[0]['id'])
else:
msg = ("%(host_id)s is not a member of cluster %(cluster_id)s" %
{'host_id': host_id, 'cluster_id': cluster_id})
LOG.debug(msg)
msg = _("Membership could not be found.")
raise webob.exc.HTTPNotFound(explanation=msg)
# Make an appropriate result
msg = (_LI("Successfully deleted a host %(host_id)s from cluster %(cluster_id)s") %
{'host_id': host_id, 'cluster_id': cluster_id})
LOG.info(msg)
return webob.exc.HTTPNoContent()
def default(self, req, *args, **kwargs):
"""This will cover the missing 'show' and 'create' actions"""
LOG.debug("The method %s is not allowed for this resource" %
req.environ['REQUEST_METHOD'])
raise webob.exc.HTTPMethodNotAllowed(
headers=[('Allow', 'PUT, DELETE')])
def get_host_clusters(self, req, host_id):
"""
Retrieves clusters shared with the given host.
"""
try:
members = self.db_api.cluster_host_member_find(req.context, host_id=host_id)
except exception.NotFound:
msg = _LW("Host %(id)s not found") % {'id': host_id}
LOG.warn(msg)
msg = _("Membership could not be found.")
raise webob.exc.HTTPBadRequest(explanation=msg)
msg = "Returning list of clusters shared with host %(id)s" % {'id': host_id}
LOG.debug(msg)
return dict(multi_clusters=make_member_list(members,
cluster_id='cluster_id'))
def make_member_list(members, **attr_map):
"""
Create a dict representation of a list of members which we can use
to serialize the members list. Keyword arguments map the names of
optional attributes to include to the database attribute.
"""
def _fetch_memb(memb, attr_map):
return dict([(k, memb[v])
for k, v in attr_map.items() if v in memb.keys()])
# Return the list of members with the given attribute mapping
return [_fetch_memb(memb, attr_map) for memb in members]
def create_resource():
"""Image members resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,481 @@
# Copyright 2010-2011 OpenStack Foundation
# 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.
"""
Reference implementation registry server WSGI controller
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import timeutils
from webob import exc
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
CONF = cfg.CONF
DISPLAY_FIELDS_IN_INDEX = ['id', 'name', 'size',
'disk_format', 'container_format',
'checksum']
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
'min_ram', 'min_disk', 'size_min', 'size_max',
'changes-since', 'protected']
SUPPORTED_SORT_KEYS = ('name', 'status', 'container_format', 'disk_format',
'size', 'id', 'created_at', 'updated_at')
SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
class Controller(object):
def __init__(self):
self.db_api = daisy.db.get_api()
def _get_networks(self, context,cluster_id, filters=None, **params):
"""Get networks, wrapping in exception if necessary."""
try:
return self.db_api.network_get_all(context, cluster_id,filters=filters,
**params)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. Network %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. Network could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to network %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. Network could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to get networks"))
raise
def update_phyname_of_network(self, req, body):
try:
self.db_api.update_phyname_of_network(req.context, body)
return {}
except exception.NotFound:
raise exc.HTTPServerError(
explanation="Update database for phyname of network table failed!")
def get_all_networks(self, req):
params = self._get_query_params(req)
try:
networks = self.db_api.network_get_all(req.context,**params)
except Exception:
raise exc.HTTPServerError(explanation="Get all networks failed")
return networks
def detail_network(self, req, id):
"""Return a filtered list of public, non-deleted networks in detail
:param req: the Request object coming from the wsgi layer
:retval a mapping of the following form::
dict(networks=[network_list])
Where network_list is a sequence of mappings containing
all network model fields.
"""
params = self._get_query_params(req)
networks = self._get_networks(req.context, id ,**params)
return dict(networks=networks)
def _get_query_params(self, req):
"""Extract necessary query parameters from http request.
:param req: the Request object coming from the wsgi layer
:retval dictionary of filters to apply to list of networks
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
'sort_key': [self._get_sort_key(req)],
'sort_dir': [self._get_sort_dir(req)],
'marker': self._get_marker(req),
}
for key, value in params.items():
if value is None:
del params[key]
return params
def _get_filters(self, req):
"""Return a dictionary of query param filters from the request
:param req: the Request object coming from the wsgi layer
:retval a dict of key/value filters
"""
filters = {}
properties = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
filters[param] = req.params.get(param)
if param.startswith('property-'):
_param = param[9:]
properties[_param] = req.params.get(param)
if 'changes-since' in filters:
isotime = filters['changes-since']
try:
filters['changes-since'] = timeutils.parse_isotime(isotime)
except ValueError:
raise exc.HTTPBadRequest(_("Unrecognized changes-since value"))
if 'protected' in filters:
value = self._get_bool(filters['protected'])
if value is None:
raise exc.HTTPBadRequest(_("protected must be True, or "
"False"))
filters['protected'] = value
# only allow admins to filter on 'deleted'
if req.context.is_admin:
deleted_filter = self._parse_deleted_filter(req)
if deleted_filter is not None:
filters['deleted'] = deleted_filter
elif 'changes-since' not in filters:
filters['deleted'] = False
elif 'changes-since' not in filters:
filters['deleted'] = False
if properties:
filters['properties'] = properties
return filters
def _get_limit(self, req):
"""Parse a limit query param into something usable."""
try:
limit = int(req.params.get('limit', CONF.limit_param_default))
except ValueError:
raise exc.HTTPBadRequest(_("limit param must be an integer"))
if limit < 0:
raise exc.HTTPBadRequest(_("limit param must be positive"))
return min(CONF.api_limit_max, limit)
def _get_marker(self, req):
"""Parse a marker query param into something usable."""
marker = req.params.get('marker', None)
if marker and not utils.is_uuid_like(marker):
msg = _('Invalid marker format')
raise exc.HTTPBadRequest(explanation=msg)
return marker
def _get_sort_key(self, req):
"""Parse a sort key query param from the request object."""
sort_key = req.params.get('sort_key', 'created_at')
if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS:
_keys = ', '.join(SUPPORTED_SORT_KEYS)
msg = _("Unsupported sort_key. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_key
def _get_sort_dir(self, req):
"""Parse a sort direction query param from the request object."""
sort_dir = req.params.get('sort_dir', 'desc')
if sort_dir is not None and sort_dir not in SUPPORTED_SORT_DIRS:
_keys = ', '.join(SUPPORTED_SORT_DIRS)
msg = _("Unsupported sort_dir. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_dir
def _get_bool(self, value):
value = value.lower()
if value == 'true' or value == '1':
return True
elif value == 'false' or value == '0':
return False
return None
def _parse_deleted_filter(self, req):
"""Parse deleted into something usable."""
deleted = req.params.get('deleted')
if deleted is None:
return None
return strutils.bool_from_string(deleted)
@utils.mutating
def add_network(self, req, body):
"""Registers a new network with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the network
:retval Returns the newly-created network information as a mapping,
which will include the newly-created network's internal id
in the 'id' field
"""
network_data = body["network"]
network_id = network_data.get('id')
# role = network_data.get('role')
# add network_id and role
# if role
# self.db_api.get_role(req.context,role)
if network_id and not utils.is_uuid_like(network_id):
msg = _LI("Rejecting network creation request for invalid network "
"id '%(bad_id)s'") % {'bad_id': network_id}
LOG.info(msg)
msg = _("Invalid network id format")
return exc.HTTPBadRequest(explanation=msg)
try:
network_data = self.db_api.network_add(req.context, network_data)
#network_data = dict(network=make_image_dict(network_data))
msg = (_LI("Successfully created node %s") %
network_data["id"])
LOG.info(msg)
if 'network' not in network_data:
network_data = dict(network=network_data)
return network_data
except exception.Duplicate:
msg = _("node with identifier %s already exists!") % image_id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add node metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create node %s"), network_id)
raise
@utils.mutating
def delete_network(self, req, network_id):
"""Deletes an existing network with the registry.
:param req: wsgi Request object
:param id: The opaque internal identifier for the image
:retval Returns 200 if delete was successful, a fault if not. On
success, the body contains the deleted image information as a mapping.
"""
try:
deleted_network = self.db_api.network_destroy(req.context, network_id)
msg = _LI("Successfully deleted network %(network_id)s") % {'network_id': network_id}
LOG.info(msg)
return dict(network=deleted_network)
except exception.ForbiddenPublicImage:
msg = _LI("Delete denied for public network %(network_id)s") % {'network_id': network_id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to network %(id)s but returning"
" 'not found'") % {'network_id': network_id}
LOG.info(msg)
return exc.HTTPNotFound()
except exception.NotFound:
msg = _LI("Network %(network_id)s not found") % {'network_id': network_id}
LOG.info(msg)
return exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to delete network %s") % id)
raise
@utils.mutating
def get_network(self, req, id):
"""Return data about the given network id."""
try:
network_data = self.db_api.network_get(req.context, id)
msg = "Successfully retrieved network %(id)s" % {'id': id}
LOG.debug(msg)
except exception.NotFound:
msg = _LI("Network %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to network %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound()
except Exception:
LOG.exception(_LE("Unable to show network %s") % id)
raise
if 'network' not in network_data:
network_data = dict(network=network_data)
return network_data
@utils.mutating
def update_network(self, req, network_id, body):
"""Updates an existing network with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
network_data = body['network']
try:
updated_network = self.db_api.network_update(req.context, network_id, network_data)
msg = _LI("Updating metadata for network %(network_id)s") % {'network_id': network_id}
LOG.info(msg)
if 'network' not in updated_network:
network_data = dict(network=updated_network)
return network_data
except exception.Invalid as e:
msg = (_("Failed to update network metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except exception.NotFound:
msg = _LI("Network %(network_id)s not found") % {'network_id': network_id}
LOG.info(msg)
raise exc.HTTPNotFound(body='Network not found',
request=req,
content_type='text/plain')
except exception.ForbiddenPublicImage:
msg = _LI("Update denied for public network %(network_id)s") % {'network_id': network_id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
raise
except exception.Conflict as e:
LOG.info(utils.exception_to_str(e))
raise exc.HTTPConflict(body='Network operation conflicts',
request=req,
content_type='text/plain')
except Exception:
LOG.exception(_LE("Unable to update network %s") % network_id)
raise
@utils.mutating
def update_cluster(self, req, id, body):
"""Updates an existing cluster with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
cluster_data = body['cluster']
try:
updated_cluster = self.db_api.cluster_update(req.context, id, cluster_data)
msg = _LI("Updating metadata for cluster %(id)s") % {'id': id}
LOG.info(msg)
if 'cluster' not in updated_cluster:
cluster_data = dict(cluster=updated_cluster)
return cluster_data
except exception.Invalid as e:
msg = (_("Failed to update cluster metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except exception.NotFound:
msg = _LI("cluster %(id)s not found") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='cluster not found',
request=req,
content_type='text/plain')
except exception.ForbiddenPublicImage:
msg = _LI("Update denied for public cluster %(id)s") % {'id': id}
LOG.info(msg)
raise exc.HTTPForbidden()
except exception.Forbidden:
# If it's private and doesn't belong to them, don't let on
# that it exists
msg = _LI("Access denied to cluster %(id)s but returning"
" 'not found'") % {'id': id}
LOG.info(msg)
raise exc.HTTPNotFound(body='cluster not found',
request=req,
content_type='text/plain')
except exception.Conflict as e:
LOG.info(utils.exception_to_str(e))
raise exc.HTTPConflict(body='cluster operation conflicts',
request=req,
content_type='text/plain')
except Exception:
LOG.exception(_LE("Unable to update cluster %s") % id)
raise
def _limit_locations(image):
locations = image.pop('locations', [])
image['location_data'] = locations
image['location'] = None
for loc in locations:
if loc['status'] == 'active':
image['location'] = loc['url']
break
def make_image_dict(image):
"""Create a dict representation of an image which we can use to
serialize the image.
"""
def _fetch_attrs(d, attrs):
return dict([(a, d[a]) for a in attrs
if a in d.keys()])
# TODO(sirp): should this be a dict, or a list of dicts?
# A plain dict is more convenient, but list of dicts would provide
# access to created_at, etc
properties = dict((p['name'], p['value'])
for p in image['properties'] if not p['deleted'])
image_dict = _fetch_attrs(image, daisy.db.IMAGE_ATTRS)
image_dict['properties'] = properties
_limit_locations(image_dict)
return image_dict
def create_resource():
"""Images resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,547 @@
# Copyright 2010-2011 OpenStack Foundation
# 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.
"""
Reference implementation registry server WSGI controller
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import timeutils
from webob import exc
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
CONF = cfg.CONF
DISPLAY_FIELDS_IN_INDEX = ['id', 'name', 'type', 'hosts', 'content']
SUPPORTED_FILTERS = ['name', 'type', 'cluster_name', 'hosts', 'content']
SUPPORTED_SORT_KEYS = ('name', 'type', 'hosts', 'content', 'id', 'created_at', 'updated_at')
SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir', 'name', 'type', 'cluster_name')
class Controller(object):
def __init__(self):
self.db_api = daisy.db.get_api()
def _get_query_params(self, req):
"""Extract necessary query parameters from http request.
:param req: the Request object coming from the wsgi layer
:retval dictionary of filters to apply to list of templates
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
'sort_key': [self._get_sort_key(req)],
'sort_dir': [self._get_sort_dir(req)],
'marker': self._get_marker(req),
}
for key, value in params.items():
if value is None:
del params[key]
return params
def _get_filters(self, req):
"""Return a dictionary of query param filters from the request
:param req: the Request object coming from the wsgi layer
:retval a dict of key/value filters
"""
filters = {}
properties = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
filters[param] = req.params.get(param)
if param.startswith('property-'):
_param = param[9:]
properties[_param] = req.params.get(param)
if 'changes-since' in filters:
isotime = filters['changes-since']
try:
filters['changes-since'] = timeutils.parse_isotime(isotime)
except ValueError:
raise exc.HTTPBadRequest(_("Unrecognized changes-since value"))
if 'protected' in filters:
value = self._get_bool(filters['protected'])
if value is None:
raise exc.HTTPBadRequest(_("protected must be True, or "
"False"))
filters['protected'] = value
# only allow admins to filter on 'deleted'
if req.context.is_admin:
deleted_filter = self._parse_deleted_filter(req)
if deleted_filter is not None:
filters['deleted'] = deleted_filter
elif 'changes-since' not in filters:
filters['deleted'] = False
elif 'changes-since' not in filters:
filters['deleted'] = False
if properties:
filters['properties'] = properties
return filters
def _get_limit(self, req):
"""Parse a limit query param into something usable."""
try:
limit = int(req.params.get('limit', CONF.limit_param_default))
except ValueError:
raise exc.HTTPBadRequest(_("limit param must be an integer"))
if limit < 0:
raise exc.HTTPBadRequest(_("limit param must be positive"))
return min(CONF.api_limit_max, limit)
def _get_marker(self, req):
"""Parse a marker query param into something usable."""
marker = req.params.get('marker', None)
if marker and not utils.is_uuid_like(marker):
msg = _('Invalid marker format')
raise exc.HTTPBadRequest(explanation=msg)
return marker
def _get_sort_key(self, req):
"""Parse a sort key query param from the request object."""
sort_key = req.params.get('sort_key', 'created_at')
if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS:
_keys = ', '.join(SUPPORTED_SORT_KEYS)
msg = _("Unsupported sort_key. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_key
def _get_sort_dir(self, req):
"""Parse a sort direction query param from the request object."""
sort_dir = req.params.get('sort_dir', 'desc')
if sort_dir is not None and sort_dir not in SUPPORTED_SORT_DIRS:
_keys = ', '.join(SUPPORTED_SORT_DIRS)
msg = _("Unsupported sort_dir. Acceptable values: %s") % (_keys,)
raise exc.HTTPBadRequest(explanation=msg)
return sort_dir
def _get_bool(self, value):
value = value.lower()
if value == 'true' or value == '1':
return True
elif value == 'false' or value == '0':
return False
return None
def _parse_deleted_filter(self, req):
"""Parse deleted into something usable."""
deleted = req.params.get('deleted')
if deleted is None:
return None
return strutils.bool_from_string(deleted)
@utils.mutating
def template_add(self, req, body):
"""Registers a new templatae with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the templatae
:retval Returns the newly-created template information as a mapping,
which will include the newly-created template's internal id
in the 'id' field
"""
template_data = body["template"]
id = template_data.get('id')
# role = service_disk_data.get('role')
# add id and role
# if role
# self.db_api.get_role(req.context,role)
if id and not utils.is_uuid_like(id):
msg = _LI("Rejecting template creation request for invalid template "
"id '%(bad_id)s'") % {'bad_id': id}
LOG.info(msg)
msg = _("Invalid template id format")
return exc.HTTPBadRequest(explanation=msg)
try:
template_data = self.db_api.template_add(req.context, template_data)
msg = (_LI("Successfully created template %s") %
template_data["id"])
LOG.info(msg)
if 'template' not in template_data:
template_data = dict(template=template_data)
return template_data
except exception.Duplicate:
msg = _("template with identifier %s already exists!") % id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add template metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create template %s"), id)
raise
@utils.mutating
def template_update(self, req, template_id, body):
"""Registers a new template with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the template
:retval Returns the newly-created template information as a mapping,
which will include the newly-created template's internal id
in the 'id' field
"""
template_data = body["template"]
if template_id and not utils.is_uuid_like(template_id):
msg = _LI("Rejecting cluster template creation request for invalid template "
"id '%(bad_id)s'") % {'bad_id': template_id}
LOG.info(msg)
msg = _("Invalid template id format")
return exc.HTTPBadRequest(explanation=msg)
try:
template_data = self.db_api.template_update(req.context, template_id, template_data)
msg = (_LI("Successfully updated template %s") %
template_data["id"])
LOG.info(msg)
if 'template' not in template_data:
template_data = dict(template=template_data)
return template_data
except exception.Duplicate:
msg = _("template with identifier %s already exists!") % template_id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to update template metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to update template %s"), template_id)
raise
@utils.mutating
def template_delete(self, req, template_id):
"""Registers a new template with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the template
:retval Returns the newly-created template information as a mapping,
which will include the newly-created template's internal id
in the 'id' field
"""
if template_id and not utils.is_uuid_like(template_id):
msg = _LI("Rejecting template delete request for invalid template "
"id '%(bad_id)s'") % {'bad_id': template_id}
LOG.info(msg)
msg = _("Invalid template id format")
return exc.HTTPBadRequest(explanation=msg)
try:
template_data = self.db_api.template_destroy(req.context, template_id)
#template_data = dict(template=make_image_dict(template_data))
msg = (_LI("Successfully deleted template %s") % template_id)
LOG.info(msg)
if 'template' not in template_data:
template_data = dict(template=template_data)
return template_data
except exception.Invalid as e:
msg = (_("Failed to delete template metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to delete template %s"), template_id)
raise
@utils.mutating
def template_list(self, req):
params = self._get_query_params(req)
try:
filters=params.pop('filters')
marker=params.get('marker')
limit=params.get('limit')
sort_key=params.get('sort_key')
sort_dir=params.get('sort_dir')
return self.db_api.template_get_all(req.context, filters=filters,\
marker=marker,limit=limit,sort_key=sort_key,sort_dir=sort_dir)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. template %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. template could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to template %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. template could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to list template"))
raise
@utils.mutating
def template_detail(self, req, template_id):
"""Registers a new template with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the template
:retval Returns the newly-created template information as a mapping,
which will include the newly-created template's internal id
in the 'id' field
"""
if template_id and not utils.is_uuid_like(template_id):
msg = _LI("Rejecting template delete request for invalid template "
"id '%(bad_id)s'") % {'bad_id': template_id}
LOG.info(msg)
msg = _("Invalid template id format")
return exc.HTTPBadRequest(explanation=msg)
try:
template_data = self.db_api.template_get(req.context, template_id)
#service_disk_data = dict(service_disk=make_image_dict(service_disk_data))
msg = (_LI("Successfully get template information:%s") % template_id)
LOG.info(msg)
if 'template' not in template_data:
template_data = dict(template=template_data)
return template_data
except exception.Invalid as e:
msg = (_("Failed to get template metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to get template %s"), template_id)
raise
@utils.mutating
def host_template_add(self, req, body):
"""Registers a new service_disk with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the service_disk
:retval Returns the newly-created service_disk information as a mapping,
which will include the newly-created service_disk's internal id
in the 'id' field
"""
template_data = body["template"]
id = template_data.get('id')
# role = service_disk_data.get('role')
# add id and role
# if role
# self.db_api.get_role(req.context,role)
if id and not utils.is_uuid_like(id):
msg = _LI("Rejecting service_disk creation request for invalid service_disk "
"id '%(bad_id)s'") % {'bad_id': id}
LOG.info(msg)
msg = _("Invalid service_disk id format")
return exc.HTTPBadRequest(explanation=msg)
try:
template_data = self.db_api.host_template_add(req.context, template_data)
#service_disk_data = dict(service_disk=make_image_dict(service_disk_data))
msg = (_LI("Successfully created node %s") %
template_data["id"])
LOG.info(msg)
if 'template' not in template_data:
template_data = dict(host_template=template_data)
return template_data
except exception.Duplicate:
msg = _("node with identifier %s already exists!") % id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add node metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create node %s"), id)
raise
@utils.mutating
def host_template_update(self, req, template_id, body):
"""Registers a new service_disk with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the service_disk
:retval Returns the newly-created service_disk information as a mapping,
which will include the newly-created service_disk's internal id
in the 'id' field
"""
template_data = body["template"]
#template_id = template_data.get('template_id')
if template_id and not utils.is_uuid_like(template_id):
msg = _LI("Rejecting cluster template creation request for invalid template "
"id '%(bad_id)s'") % {'bad_id': template_id}
LOG.info(msg)
msg = _("Invalid template id format")
return exc.HTTPBadRequest(explanation=msg)
try:
template_data = self.db_api.host_template_update(req.context, template_id, template_data)
#service_disk_data = dict(service_disk=make_image_dict(service_disk_data))
msg = (_LI("Successfully updated template %s") %
template_data["id"])
LOG.info(msg)
if 'template' not in template_data:
template_data = dict(host_template=template_data)
return template_data
except exception.Duplicate:
msg = _("template with identifier %s already exists!") % template_id
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to update template metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to update template %s"), template_id)
raise
@utils.mutating
def host_template_delete(self, req, template_id):
"""Registers a new service_disk with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the service_disk
:retval Returns the newly-created service_disk information as a mapping,
which will include the newly-created service_disk's internal id
in the 'id' field
"""
if template_id and not utils.is_uuid_like(template_id):
msg = _LI("Rejecting template delete request for invalid template "
"id '%(bad_id)s'") % {'bad_id': template_id}
LOG.info(msg)
msg = _("Invalid template id format")
return exc.HTTPBadRequest(explanation=msg)
try:
template_data = self.db_api.host_template_destroy(req.context, template_id)
#service_disk_data = dict(service_disk=make_image_dict(service_disk_data))
msg = (_LI("Successfully deleted template %s") % template_id)
LOG.info(msg)
if 'template' not in template_data:
template_data = dict(host_template=template_data)
return template_data
except exception.Invalid as e:
msg = (_("Failed to delete template metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to delete template %s"), template_id)
raise
@utils.mutating
def host_template_list(self, req):
params = self._get_query_params(req)
try:
filters=params.pop('filters')
marker=params.get('marker')
limit=params.get('limit')
sort_key=params.get('sort_key')
sort_dir=params.get('sort_dir')
return self.db_api.host_template_get_all(req.context, filters=filters,\
marker=marker,limit=limit,sort_key=sort_key,sort_dir=sort_dir)
except exception.NotFound:
LOG.warn(_LW("Invalid marker. template %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. template could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to template %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. template could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to list template"))
raise
@utils.mutating
def host_template_detail(self, req, template_id):
"""Registers a new service_disk with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the service_disk
:retval Returns the newly-created service_disk information as a mapping,
which will include the newly-created service_disk's internal id
in the 'id' field
"""
if template_id and not utils.is_uuid_like(template_id):
msg = _LI("Rejecting template delete request for invalid template "
"id '%(bad_id)s'") % {'bad_id': template_id}
LOG.info(msg)
msg = _("Invalid template id format")
return exc.HTTPBadRequest(explanation=msg)
try:
template_data = self.db_api.host_template_get(req.context, template_id)
#service_disk_data = dict(service_disk=make_image_dict(service_disk_data))
msg = (_LI("Successfully get template information:%s") % template_id)
LOG.info(msg)
if 'template' not in template_data:
template_data = dict(host_template=template_data)
return template_data
except exception.Invalid as e:
msg = (_("Failed to get template metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to get template %s"), template_id)
raise
def create_resource():
"""Images resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,35 @@
# Copyright 2013 Red Hat, 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.
from daisy.common import wsgi
from daisy.registry.api.v2 import rpc
def init(mapper):
rpc_resource = rpc.create_resource()
mapper.connect("/rpc", controller=rpc_resource,
conditions=dict(method=["POST"]),
action="__call__")
class API(wsgi.Router):
"""WSGI entry point for all Registry requests."""
def __init__(self, mapper):
mapper = mapper or wsgi.APIMapper()
init(mapper)
super(API, self).__init__(mapper)

View File

@@ -0,0 +1,57 @@
# Copyright 2013 Red Hat, 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.
"""
RPC Controller
"""
from oslo_config import cfg
from oslo_log import log as logging
from daisy.common import rpc
from daisy.common import wsgi
import daisy.db
from daisy import i18n
LOG = logging.getLogger(__name__)
_ = i18n._
CONF = cfg.CONF
class Controller(rpc.Controller):
def __init__(self, raise_exc=False):
super(Controller, self).__init__(raise_exc)
# NOTE(flaper87): Avoid using registry's db
# driver for the registry service. It would
# end up in an infinite loop.
if CONF.data_api == "daisy.db.registry.api":
msg = _("Registry service can't use %s") % CONF.data_api
raise RuntimeError(msg)
# NOTE(flaper87): Register the
# db_api as a resource to expose.
db_api = daisy.db.get_api()
self.register(daisy.db.unwrap(db_api))
def create_resource():
"""Images resource factory method."""
deserializer = rpc.RPCJSONDeserializer()
serializer = rpc.RPCJSONSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)