initial merge
Change-Id: Id7cef7826092e191654da872ee1e11c4c6f50ddf Signed-off-by: Zhijiang Hu <hu.zhijiang@zte.com.cn>
This commit is contained in:
37
code/daisy/daisy/registry/api/__init__.py
Executable file
37
code/daisy/daisy/registry/api/__init__.py
Executable 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)
|
||||
449
code/daisy/daisy/registry/api/v1/__init__.py
Executable file
449
code/daisy/daisy/registry/api/v1/__init__.py
Executable 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)
|
||||
377
code/daisy/daisy/registry/api/v1/config_files.py
Executable file
377
code/daisy/daisy/registry/api/v1/config_files.py
Executable 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)
|
||||
384
code/daisy/daisy/registry/api/v1/config_sets.py
Executable file
384
code/daisy/daisy/registry/api/v1/config_sets.py
Executable 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)
|
||||
381
code/daisy/daisy/registry/api/v1/configs.py
Executable file
381
code/daisy/daisy/registry/api/v1/configs.py
Executable 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)
|
||||
575
code/daisy/daisy/registry/api/v1/disk_array.py
Executable file
575
code/daisy/daisy/registry/api/v1/disk_array.py
Executable 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)
|
||||
1687
code/daisy/daisy/registry/api/v1/hosts.py
Executable file
1687
code/daisy/daisy/registry/api/v1/hosts.py
Executable file
File diff suppressed because it is too large
Load Diff
555
code/daisy/daisy/registry/api/v1/images.py
Executable file
555
code/daisy/daisy/registry/api/v1/images.py
Executable 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)
|
||||
447
code/daisy/daisy/registry/api/v1/members.py
Executable file
447
code/daisy/daisy/registry/api/v1/members.py
Executable 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)
|
||||
481
code/daisy/daisy/registry/api/v1/networks.py
Executable file
481
code/daisy/daisy/registry/api/v1/networks.py
Executable 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)
|
||||
547
code/daisy/daisy/registry/api/v1/template.py
Executable file
547
code/daisy/daisy/registry/api/v1/template.py
Executable 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)
|
||||
35
code/daisy/daisy/registry/api/v2/__init__.py
Executable file
35
code/daisy/daisy/registry/api/v2/__init__.py
Executable 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)
|
||||
57
code/daisy/daisy/registry/api/v2/rpc.py
Executable file
57
code/daisy/daisy/registry/api/v2/rpc.py
Executable 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)
|
||||
Reference in New Issue
Block a user