1780 lines
71 KiB
Python
Executable File
1780 lines
71 KiB
Python
Executable File
# 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
|
|
"""
|
|
|
|
import sys
|
|
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
|
|
|
|
from daisyclient import client as daisy_client
|
|
from daisy.registry.api.v1 import hwms as registry_hwm
|
|
import ConfigParser
|
|
|
|
reload(sys)
|
|
sys.setdefaultencoding('utf-8')
|
|
|
|
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', 'id', 'cluster_id',
|
|
'auto_scale', 'container_format', 'disk_format',
|
|
|
|
'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')
|
|
|
|
|
|
class Controller(object):
|
|
|
|
def __init__(self):
|
|
self.db_api = daisy.db.get_api()
|
|
self.daisyclient = self.get_daisyclient()
|
|
|
|
@staticmethod
|
|
def get_daisyclient():
|
|
"""Get Daisy client instance."""
|
|
config_daisy = ConfigParser.ConfigParser()
|
|
config_daisy.read("/etc/daisy/daisy-api.conf")
|
|
daisy_port = config_daisy.get("DEFAULT", "bind_port")
|
|
args = {
|
|
'version': 1.0,
|
|
'endpoint': 'http://127.0.0.1:' + daisy_port
|
|
}
|
|
return daisy_client.Client(**args)
|
|
|
|
def _get_hosts(self, context, filters, **params):
|
|
"""Get hosts, wrapping in exception if necessary."""
|
|
try:
|
|
return self.db_api.host_get_all(context, filters=filters,
|
|
**params)
|
|
except exception.NotFound:
|
|
LOG.warn(_LW("Invalid marker. Host %(id)s could not be "
|
|
"found.") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Host could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except exception.Forbidden:
|
|
LOG.warn(_LW("Access denied to host %(id)s but returning "
|
|
"'not found'") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Host could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to get hosts"))
|
|
raise
|
|
|
|
def _get_clusters(self, context, filters, **params):
|
|
"""Get clusters, wrapping in exception if necessary."""
|
|
try:
|
|
return self.db_api.cluster_get_all(context, filters=filters,
|
|
**params)
|
|
except exception.NotFound:
|
|
LOG.warn(_LW("Invalid marker. Cluster %(id)s could not be "
|
|
"found.") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Cluster could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except exception.Forbidden:
|
|
LOG.warn(_LW("Access denied to cluster %(id)s but returning "
|
|
"'not found'") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Cluster could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to get clusters"))
|
|
raise
|
|
|
|
def detail_host(self, req):
|
|
"""Return a filtered list of public, non-deleted hosts in detail
|
|
|
|
:param req: the Request object coming from the wsgi layer
|
|
:retval a mapping of the following form::
|
|
|
|
dict(nodes=[host_list])
|
|
|
|
Where host_list is a sequence of mappings containing
|
|
all host model fields.
|
|
"""
|
|
params = self._get_query_params(req)
|
|
|
|
nodes = self._get_hosts(req.context, **params)
|
|
|
|
return dict(nodes=nodes)
|
|
|
|
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 hosts
|
|
"""
|
|
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_host(self, req, body):
|
|
"""Registers a new host with the registry.
|
|
|
|
:param req: wsgi Request object
|
|
:param body: Dictionary of information about the host
|
|
|
|
:retval Returns the newly-created host information as a mapping,
|
|
which will include the newly-created host's internal id
|
|
in the 'id' field
|
|
"""
|
|
|
|
host_data = body["host"]
|
|
|
|
host_id = host_data.get('id')
|
|
|
|
if host_id and not utils.is_uuid_like(host_id):
|
|
msg = _LI("Rejecting host creation request for invalid host "
|
|
"id '%(bad_id)s'") % {'bad_id': host_id}
|
|
LOG.info(msg)
|
|
msg = _("Invalid host id format")
|
|
return exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
if host_id is None:
|
|
host_data = self.db_api.host_add(req.context, host_data)
|
|
else:
|
|
orig_config_set_id = None
|
|
if 'config_set_id' in host_data:
|
|
orig_host_data = self.db_api.host_get(req.context, host_id)
|
|
orig_config_set_id = orig_host_data.get('config_set_id')
|
|
|
|
host_data = self.db_api.host_update(
|
|
req.context, host_id, host_data)
|
|
|
|
if orig_config_set_id:
|
|
try:
|
|
self.db_api.config_set_destroy(req.context,
|
|
orig_config_set_id)
|
|
except exception.Forbidden as e:
|
|
LOG.info(e)
|
|
# host_data = dict(host=make_image_dict(host_data))
|
|
msg = (_LI("Successfully created node %s") %
|
|
host_data["id"])
|
|
LOG.info(msg)
|
|
if 'host' not in host_data:
|
|
host_data = dict(host=host_data)
|
|
return host_data
|
|
except exception.Duplicate:
|
|
msg = _("node with identifier %s already exists!") % host_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.Forbidden as e:
|
|
msg = (_("%s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
raise exc.HTTPForbidden(msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to create node %s"), host_id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def delete_host(self, req, id):
|
|
"""Deletes an existing host 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:
|
|
host_interface = self.db_api.get_host_interface(req.context, id)
|
|
deleted_host = self.db_api.host_destroy(req.context, id)
|
|
msg = _LI("Successfully deleted host %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
members = self.db_api.cluster_host_member_find(req.context,
|
|
host_id=id)
|
|
if members:
|
|
for member in members:
|
|
self.db_api.cluster_host_member_delete(
|
|
req.context, member['id'])
|
|
|
|
self.db_api.role_host_member_delete(req.context, host_id=id)
|
|
orig_config_set_id = deleted_host.config_set_id
|
|
if orig_config_set_id:
|
|
try:
|
|
self.db_api.config_set_destroy(req.context,
|
|
orig_config_set_id)
|
|
except exception.Forbidden as e:
|
|
LOG.info(e)
|
|
# TODO delete ironic host by mac
|
|
return dict(host=deleted_host)
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Delete denied for public host %(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 host %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except exception.NotFound:
|
|
msg = _LI("Host %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to delete host %s") % id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def get_host(self, req, id):
|
|
"""Return data about the given node id."""
|
|
os_version_dict = {}
|
|
try:
|
|
host_data = self.db_api.host_get(req.context, id)
|
|
if utils.is_uuid_like(host_data.os_version_id):
|
|
version = self.db_api.get_os_version(
|
|
req.context, host_data.os_version_id)
|
|
if version:
|
|
os_version_dict['name'] = version.name
|
|
os_version_dict['id'] = version.id
|
|
os_version_dict['desc'] = version.description
|
|
msg = "Successfully retrieved host %(id)s" % {'id': id}
|
|
LOG.debug(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("Host %(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 host %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to show host %s") % id)
|
|
raise
|
|
param = dict()
|
|
param['hwm_ip'] = host_data.hwm_ip
|
|
param['hwm_id'] = host_data.hwm_id
|
|
controller = registry_hwm.Controller()
|
|
hwms = controller.hwm_list(req)
|
|
hwms_ip = [hwm['hwm_ip'] for hwm in hwms]
|
|
if param['hwm_ip'] in hwms_ip:
|
|
result = self.daisyclient.node.location(**param)
|
|
location = str(result.rack) + '/' + str(result.position)
|
|
else:
|
|
location = ""
|
|
host_interface = self.db_api.get_host_interface(req.context, id)
|
|
|
|
role_name = []
|
|
backends = set()
|
|
if host_data.status == "with-role":
|
|
host_roles = self.db_api.role_host_member_get(
|
|
req.context, None, id)
|
|
for host_role in host_roles:
|
|
role_info = self.db_api.role_get(
|
|
req.context, host_role.role_id)
|
|
role_name.append(role_info['name'])
|
|
backends.add(role_info.get('deployment_backend'))
|
|
host_cluster = self.db_api.cluster_host_member_find(
|
|
req.context, None, id)
|
|
if host_cluster:
|
|
cluster_info = self.db_api.cluster_get(
|
|
req.context, host_cluster[0]['cluster_id'])
|
|
cluster_name = cluster_info['name']
|
|
else:
|
|
cluster_name = None
|
|
|
|
if 'host' not in host_data:
|
|
host_data = dict(host=host_data)
|
|
if host_interface:
|
|
host_data['host']['interfaces'] = host_interface
|
|
if os_version_dict:
|
|
host_data['host']['os_version'] = os_version_dict
|
|
if role_name:
|
|
host_data['host']['role'] = role_name
|
|
host_data['host']['deployment_backends'] = backends
|
|
if cluster_name:
|
|
host_data['host']['cluster'] = cluster_name
|
|
|
|
host_data['host']['position'] = location
|
|
|
|
return host_data
|
|
|
|
@utils.mutating
|
|
def get_host_interface(self, req, body):
|
|
orig_interfaces = list(eval(body['interfaces']))
|
|
for orig_interface in orig_interfaces:
|
|
host_interface = self.db_api.get_host_interface_mac(
|
|
req.context, orig_interface['mac'])
|
|
return host_interface
|
|
|
|
@utils.mutating
|
|
def get_all_host_interfaces(self, req, body, **params):
|
|
"""Return all_host_interfaces about the given filter."""
|
|
filters = body['filters']
|
|
try:
|
|
host_interfaces = self.db_api.host_interfaces_get_all(
|
|
req.context, filters)
|
|
return host_interfaces
|
|
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
|
|
return
|
|
|
|
@utils.mutating
|
|
def get_assigned_network(self, req, interface_id, network_id):
|
|
try:
|
|
host_assigned_network = self.db_api.get_assigned_network(
|
|
req.context,
|
|
interface_id, network_id)
|
|
except exception.NotFound:
|
|
LOG.warn(_LW("Invalid marker. Assigned_network with interface %("
|
|
"interface_id)s and network %(network_id)s"
|
|
" could not be found.") % {
|
|
'interface_id': interface_id, 'network_id': network_id})
|
|
msg = _("Invalid marker. Assigned_network could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except exception.Forbidden:
|
|
LOG.warn(_LW("Access denied for assigned_network with interface %("
|
|
"interface_id)s "
|
|
"and network %(network_id)s") % {
|
|
'interface_id': interface_id, 'network_id': network_id})
|
|
msg = _("Invalid marker. Assigned_network denied to get.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to get assigned_network"))
|
|
raise
|
|
return host_assigned_network
|
|
|
|
@utils.mutating
|
|
def add_discover_host(self, req, body):
|
|
"""Registers a new host with the registry.
|
|
|
|
:param req: wsgi Request object
|
|
:param body: Dictionary of information about the host
|
|
|
|
:retval Returns the newly-created host information as a mapping,
|
|
which will include the newly-created host's internal id
|
|
in the 'id' field
|
|
"""
|
|
|
|
discover_host_data = body["discover_host"]
|
|
discover_host_id = discover_host_data.get('id')
|
|
|
|
if discover_host_id and not utils.is_uuid_like(discover_host_id):
|
|
msg = _LI("Rejecting host creation request for invalid host "
|
|
"id '%(bad_id)s'") % {'bad_id': discover_host_id}
|
|
LOG.info(msg)
|
|
msg = _("Invalid host id format")
|
|
return exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
if discover_host_id is None:
|
|
discover_host_data = self.db_api.discover_host_add(
|
|
req.context, discover_host_data)
|
|
else:
|
|
discover_host_data = self.db_api.discover_host_update(
|
|
req.context, discover_host_id, discover_host_data)
|
|
# host_data = dict(host=make_image_dict(host_data))
|
|
msg = (_LI("Successfully created node %s") %
|
|
discover_host_data["id"])
|
|
LOG.info(msg)
|
|
if 'discover_host' not in discover_host_data:
|
|
discover_host_data = dict(discover_host=discover_host_data)
|
|
return discover_host_data
|
|
except exception.Duplicate:
|
|
msg = _("node with identifier %s already exists!") % \
|
|
discover_host_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"), discover_host_id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def delete_discover_host(self, req, id):
|
|
"""Deletes an existing discover host 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_host = self.db_api.discover_host_destroy(req.context, id)
|
|
msg = _LI("Successfully deleted host %(id)s") % {'id': id}
|
|
return dict(discover_host=deleted_host)
|
|
except exception.Forbidden:
|
|
msg = _LI("Access denied to host %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except exception.NotFound:
|
|
msg = _LI("Host %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to delete host %s") % id)
|
|
raise
|
|
|
|
def detail_discover_host(self, req):
|
|
"""Return a filtered list of public, non-deleted hosts in detail
|
|
|
|
:param req: the Request object coming from the wsgi layer
|
|
:retval a mapping of the following form::
|
|
|
|
dict(nodes=[host_list])
|
|
|
|
Where host_list is a sequence of mappings containing
|
|
all host model fields.
|
|
"""
|
|
params = self._get_query_params(req)
|
|
try:
|
|
nodes = self.db_api.discover_host_get_all(req.context,
|
|
**params)
|
|
except exception.NotFound:
|
|
LOG.warn(_LW("Invalid marker. Host %(id)s could not be "
|
|
"found.") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Host could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except exception.Forbidden:
|
|
LOG.warn(_LW("Access denied to host %(id)s but returning "
|
|
"'not found'") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Host could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to get hosts"))
|
|
raise
|
|
|
|
return dict(nodes=nodes)
|
|
|
|
@utils.mutating
|
|
def update_discover_host(self, req, id, body):
|
|
'''
|
|
'''
|
|
discover_host_data = body["discover_host"]
|
|
if id and not utils.is_uuid_like(id):
|
|
msg = _LI("Rejecting host creation request for invalid host "
|
|
"id '%(bad_id)s'") % {'bad_id': id}
|
|
LOG.info(msg)
|
|
msg = _("Invalid host id format")
|
|
return exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
updated_host = self.db_api.discover_host_update(
|
|
req.context, id, discover_host_data)
|
|
msg = _LI("Updating metadata for host %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
if 'discover_host' not in updated_host:
|
|
host_data = dict(discover_host=updated_host)
|
|
return host_data
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to update host metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("Host %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Host not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Update denied for public host %(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='Host operation conflicts',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to update host %s") % id)
|
|
raise
|
|
|
|
def get_discover_host(self, req, discover_host_id):
|
|
'''
|
|
'''
|
|
if discover_host_id and not utils.is_uuid_like(discover_host_id):
|
|
msg = _LI("Rejecting host creation request for invalid host "
|
|
"id '%(bad_id)s'") % {'bad_id': discover_host_id}
|
|
LOG.info(msg)
|
|
msg = _("Invalid host id format")
|
|
return exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
host_detail_info = self.db_api.get_discover_host_detail(
|
|
req.context, discover_host_id)
|
|
msg = _LI("Updating metadata for host %(id)s") % {
|
|
'id': discover_host_id}
|
|
LOG.info(msg)
|
|
if 'discover_host' not in host_detail_info:
|
|
host_data = dict(discover_host=host_detail_info)
|
|
LOG.info("host_data: %s" % host_data)
|
|
return host_data
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to update host metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("Host %(id)s not found") % {'id': discover_host_id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Host not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Update denied for public host %(id)s") % {
|
|
'id': discover_host_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='Host operation conflicts',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to update host %s") % discover_host_id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def add_cluster(self, req, body):
|
|
"""Registers a new host with the registry.
|
|
|
|
:param req: wsgi Request object
|
|
:param body: Dictionary of information about the host
|
|
|
|
:retval Returns the newly-created host information as a mapping,
|
|
which will include the newly-created host's internal id
|
|
in the 'id' field
|
|
"""
|
|
|
|
cluster_data = body["cluster"]
|
|
|
|
cluster_id = cluster_data.get('id')
|
|
|
|
if cluster_id and not utils.is_uuid_like(cluster_id):
|
|
msg = _LI("Rejecting host creation request for invalid cluster "
|
|
"id '%(bad_id)s'") % {'bad_id': cluster_id}
|
|
LOG.info(msg)
|
|
msg = _("Invalid cluster id format")
|
|
return exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
cluster_data = self.db_api.cluster_add(req.context, cluster_data)
|
|
msg = (_LI("Successfully created cluster %s") %
|
|
cluster_data["id"])
|
|
LOG.info(msg)
|
|
if 'cluster' not in cluster_data:
|
|
cluster_data = dict(cluster=cluster_data)
|
|
return cluster_data
|
|
except exception.Duplicate:
|
|
msg = _("cluster with identifier %s already exists!") % cluster_id
|
|
LOG.warn(msg)
|
|
return exc.HTTPConflict(msg)
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to add cluster metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to create cluster %s"), cluster_id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def delete_cluster(self, req, id):
|
|
"""Deletes an existing cluster 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_cluster = self.db_api.cluster_destroy(req.context, id)
|
|
msg = _LI("Successfully deleted cluster %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
# Look up an existing membership
|
|
members = self.db_api.cluster_host_member_find(req.context,
|
|
cluster_id=id)
|
|
if members:
|
|
for member in members:
|
|
self.db_api.cluster_host_member_delete(
|
|
req.context, member['id'])
|
|
|
|
return dict(cluster=deleted_cluster)
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Delete 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)
|
|
return exc.HTTPNotFound()
|
|
except exception.NotFound:
|
|
msg = _LI("cluster %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to delete cluster %s") % id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def get_cluster(self, req, id):
|
|
"""Return data about the given cluster id."""
|
|
try:
|
|
cluster_data = self.db_api.cluster_get(req.context, id)
|
|
msg = "Successfully retrieved cluster %(id)s" % {'id': id}
|
|
LOG.debug(msg)
|
|
networking_parameters = {}
|
|
networking_parameters['gre_id_range'] = [
|
|
cluster_data['gre_id_start'], cluster_data['gre_id_end']]
|
|
networking_parameters['vlan_range'] = [
|
|
cluster_data['vlan_start'], cluster_data['vlan_end']]
|
|
networking_parameters['vni_range'] = [
|
|
cluster_data['vni_start'], cluster_data['vni_end']]
|
|
networking_parameters['net_l23_provider'] = cluster_data[
|
|
'net_l23_provider']
|
|
networking_parameters['base_mac'] = cluster_data['base_mac']
|
|
networking_parameters['segmentation_type'] = cluster_data[
|
|
'segmentation_type']
|
|
networking_parameters['public_vip'] = cluster_data['public_vip']
|
|
cluster_data['networking_parameters'] = networking_parameters
|
|
except exception.NotFound:
|
|
msg = _LI("cluster %(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 cluster %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to show cluster %s") % id)
|
|
raise
|
|
cluster_host_member_list = []
|
|
cluster_network_member_list = []
|
|
cluster_id = id
|
|
cluster_host_member = self.db_api.cluster_host_member_find(
|
|
req.context, cluster_id)
|
|
if len(cluster_host_member) > 0:
|
|
for cluster_host in list(cluster_host_member):
|
|
cluster_host_member_list.append(cluster_host['host_id'])
|
|
cluster_data['nodes'] = cluster_host_member_list
|
|
|
|
cluster_network_member = self.db_api.network_get_all(
|
|
req.context, cluster_id)
|
|
if len(cluster_network_member) > 0:
|
|
for cluster_network in list(cluster_network_member):
|
|
cluster_network_member_list.append(cluster_network['id'])
|
|
cluster_data['networks'] = cluster_network_member_list
|
|
|
|
logic_networks = self.db_api.get_logic_network(req.context, id)
|
|
cluster_data['logic_networks'] = logic_networks
|
|
|
|
routers = self.db_api.router_get(req.context, cluster_id)
|
|
cluster_data['routers'] = routers
|
|
return cluster_data
|
|
|
|
def detail_cluster(self, req):
|
|
"""Return a filtered list of public, non-deleted hosts in detail
|
|
|
|
:param req: the Request object coming from the wsgi layer
|
|
:retval a mapping of the following form::
|
|
|
|
dict(hosts=[host_list])
|
|
|
|
Where host_list is a sequence of mappings containing
|
|
all host model fields.
|
|
"""
|
|
params = self._get_query_params(req)
|
|
cluster_host_member_list = []
|
|
cluster_network_member_list = []
|
|
|
|
clusters = self._get_clusters(req.context, **params)
|
|
for cluster in clusters:
|
|
cluster_id = cluster['id']
|
|
filters = {'deleted': False, 'cluster_id': cluster_id}
|
|
roles = self._get_roles(req.context, filters)
|
|
roles_status = [role['status'] for role in roles]
|
|
if len(set(roles_status)) == 1:
|
|
cluster['status'] = roles_status[0]
|
|
else:
|
|
cluster['status'] = "init"
|
|
cluster_host_member = self.db_api.cluster_host_member_find(
|
|
req.context, cluster_id)
|
|
if len(cluster_host_member) > 0:
|
|
for cluster_host in list(cluster_host_member):
|
|
cluster_host_member_list.append(cluster_host['host_id'])
|
|
cluster['nodes'] = cluster_host_member_list
|
|
|
|
cluster_network_member = self.db_api.network_get_all(
|
|
req.context, cluster_id)
|
|
if len(cluster_network_member) > 0:
|
|
for cluster_network in list(cluster_network_member):
|
|
cluster_network_member_list.append(cluster_network['id'])
|
|
cluster['networks'] = cluster_network_member_list
|
|
|
|
return dict(clusters=clusters)
|
|
|
|
@utils.mutating
|
|
def add_component(self, req, body):
|
|
"""Registers a new host with the registry.
|
|
|
|
:param req: wsgi Request object
|
|
:param body: Dictionary of information about the host
|
|
|
|
:retval Returns the newly-created host information as a mapping,
|
|
which will include the newly-created host's internal id
|
|
in the 'id' field
|
|
"""
|
|
|
|
component_data = body["component"]
|
|
|
|
component_id = component_data.get('id')
|
|
|
|
if component_id and not utils.is_uuid_like(component_id):
|
|
msg = _LI("Rejecting host creation request for invalid component "
|
|
"id '%(bad_id)s'") % {'bad_id': component_id}
|
|
LOG.info(msg)
|
|
msg = _("Invalid component id format")
|
|
return exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
component_data = self.db_api.component_add(
|
|
req.context, component_data)
|
|
# host_data = dict(host=make_image_dict(host_data))
|
|
msg = (_LI("Successfully created component %s") %
|
|
component_data["id"])
|
|
LOG.info(msg)
|
|
if 'component' not in component_data:
|
|
component_data = dict(component=component_data)
|
|
return component_data
|
|
except exception.Duplicate:
|
|
msg = (_("component with identifier %s already exists!")
|
|
% component_id)
|
|
LOG.warn(msg)
|
|
return exc.HTTPConflict(msg)
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to add component metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to create component %s"), component_id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def delete_component(self, req, id):
|
|
"""Deletes an existing component 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_component = self.db_api.component_destroy(req.context, id)
|
|
msg = _LI("Successfully deleted component %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
return dict(component=deleted_component)
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Delete denied for public component %(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 component %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except exception.NotFound:
|
|
msg = _LI("Component %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to delete component %s") % id)
|
|
raise
|
|
|
|
def _get_components(self, context, filters, **params):
|
|
"""Get components, wrapping in exception if necessary."""
|
|
try:
|
|
return self.db_api.component_get_all(context, filters=filters,
|
|
**params)
|
|
except exception.NotFound:
|
|
LOG.warn(_LW("Invalid marker. Project %(id)s could not be "
|
|
"found.") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Project could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except exception.Forbidden:
|
|
LOG.warn(_LW("Access denied to component %(id)s but returning "
|
|
"'not found'") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Project could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to get components"))
|
|
raise
|
|
|
|
@utils.mutating
|
|
def get_component(self, req, id):
|
|
"""Return data about the given component id."""
|
|
try:
|
|
component_data = self.db_api.component_get(req.context, id)
|
|
msg = "Successfully retrieved component %(id)s" % {'id': id}
|
|
LOG.debug(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("component %(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 component %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to show component %s") % id)
|
|
raise
|
|
if 'component' not in component_data:
|
|
component_data = dict(component=component_data)
|
|
return component_data
|
|
|
|
def detail_component(self, req):
|
|
"""Return a filtered list of public, non-deleted hosts in detail
|
|
|
|
:param req: the Request object coming from the wsgi layer
|
|
:retval a mapping of the following form::
|
|
|
|
dict(hosts=[host_list])
|
|
|
|
Where host_list is a sequence of mappings containing
|
|
all host model fields.
|
|
"""
|
|
params = self._get_query_params(req)
|
|
|
|
components = self._get_components(req.context, **params)
|
|
|
|
return dict(components=components)
|
|
|
|
@utils.mutating
|
|
def update_component(self, req, id, body):
|
|
"""Updates an existing component 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,
|
|
"""
|
|
component_data = body['component']
|
|
try:
|
|
updated_component = self.db_api.component_update(
|
|
req.context, id, component_data)
|
|
|
|
msg = _LI("Updating metadata for component %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
if 'component' not in updated_component:
|
|
component_data = dict(component=updated_component)
|
|
return component_data
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to update component metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("Component %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Component not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Update denied for public component %(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 component %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Component not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.Conflict as e:
|
|
LOG.info(utils.exception_to_str(e))
|
|
raise exc.HTTPConflict(body='Component operation conflicts',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to update component %s") % id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def add_service(self, req, body):
|
|
"""Registers a new host with the registry.
|
|
|
|
:param req: wsgi Request object
|
|
:param body: Dictionary of information about the host
|
|
|
|
:retval Returns the newly-created host information as a mapping,
|
|
which will include the newly-created host's internal id
|
|
in the 'id' field
|
|
"""
|
|
|
|
service_data = body["service"]
|
|
|
|
service_id = service_data.get('id')
|
|
|
|
if service_id and not utils.is_uuid_like(service_id):
|
|
msg = _LI("Rejecting host creation request for invalid service "
|
|
"id '%(bad_id)s'") % {'bad_id': service_id}
|
|
LOG.info(msg)
|
|
msg = _("Invalid service id format")
|
|
return exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
print service_data
|
|
service_data = self.db_api.service_add(req.context, service_data)
|
|
# host_data = dict(host=make_image_dict(host_data))
|
|
msg = (_LI("Successfully created service %s") %
|
|
service_data["id"])
|
|
LOG.info(msg)
|
|
if 'service' not in service_data:
|
|
service_data = dict(service=service_data)
|
|
return service_data
|
|
except exception.Duplicate:
|
|
msg = _("service with identifier %s already exists!") % service_id
|
|
LOG.warn(msg)
|
|
return exc.HTTPConflict(msg)
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to add service metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to create service %s"), service_id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def delete_service(self, req, id):
|
|
"""Deletes an existing service 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 = self.db_api.service_destroy(req.context, id)
|
|
msg = _LI("Successfully deleted service %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
return dict(service=deleted_service)
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Delete denied for public service %(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 %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except exception.NotFound:
|
|
msg = _LI("Service %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to delete service %s") % id)
|
|
raise
|
|
|
|
def _get_services(self, context, filters, **params):
|
|
"""Get services, wrapping in exception if necessary."""
|
|
try:
|
|
return self.db_api.service_get_all(context, filters=filters,
|
|
**params)
|
|
except exception.NotFound:
|
|
LOG.warn(_LW("Invalid marker. Project %(id)s could not be "
|
|
"found.") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Project could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except exception.Forbidden:
|
|
LOG.warn(_LW("Access denied to service %(id)s but returning "
|
|
"'not found'") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Project could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to get services"))
|
|
raise
|
|
|
|
@utils.mutating
|
|
def get_service(self, req, id):
|
|
"""Return data about the given service id."""
|
|
try:
|
|
service_data = self.db_api.service_get(req.context, id)
|
|
msg = "Successfully retrieved service %(id)s" % {'id': id}
|
|
LOG.debug(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("service %(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 %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to show service %s") % id)
|
|
raise
|
|
if 'service' not in service_data:
|
|
service_data = dict(service=service_data)
|
|
return service_data
|
|
|
|
def detail_service(self, req):
|
|
"""Return a filtered list of public, non-deleted hosts in detail
|
|
|
|
:param req: the Request object coming from the wsgi layer
|
|
:retval a mapping of the following form::
|
|
|
|
dict(hosts=[host_list])
|
|
|
|
Where host_list is a sequence of mappings containing
|
|
all host model fields.
|
|
"""
|
|
params = self._get_query_params(req)
|
|
|
|
services = self._get_services(req.context, **params)
|
|
|
|
return dict(services=services)
|
|
|
|
@utils.mutating
|
|
def update_service(self, req, id, body):
|
|
"""Updates an existing service 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_data = body['service']
|
|
try:
|
|
updated_service = self.db_api.service_update(
|
|
req.context, id, service_data)
|
|
|
|
msg = _LI("Updating metadata for service %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
if 'service' not in updated_service:
|
|
service_data = dict(service=updated_service)
|
|
return service_data
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to update service metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("Service %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Service not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Update denied for public service %(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 %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Service not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.Conflict as e:
|
|
LOG.info(utils.exception_to_str(e))
|
|
raise exc.HTTPConflict(body='Service operation conflicts',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to update service %s") % id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def add_role(self, req, body):
|
|
"""Registers a new host with the registry.
|
|
|
|
:param req: wsgi Request object
|
|
:param body: Dictionary of information about the host
|
|
|
|
:retval Returns the newly-created host information as a mapping,
|
|
which will include the newly-created host's internal id
|
|
in the 'id' field
|
|
"""
|
|
|
|
role_data = body["role"]
|
|
|
|
role_id = role_data.get('id')
|
|
|
|
if role_id and not utils.is_uuid_like(role_id):
|
|
msg = _LI("Rejecting host creation request for invalid role "
|
|
"id '%(bad_id)s'") % {'bad_id': role_id}
|
|
LOG.info(msg)
|
|
msg = _("Invalid role id format")
|
|
return exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
print role_data
|
|
role_data = self.db_api.role_add(req.context, role_data)
|
|
# host_data = dict(host=make_image_dict(host_data))
|
|
msg = (_LI("Successfully created role %s") %
|
|
role_data["id"])
|
|
LOG.info(msg)
|
|
if 'role' not in role_data:
|
|
role_data = dict(role=role_data)
|
|
return role_data
|
|
except exception.Duplicate:
|
|
msg = _("role with identifier %s already exists!") % role_id
|
|
LOG.warn(msg)
|
|
return exc.HTTPConflict(msg)
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to add role metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to create role %s"), role_id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def delete_role(self, req, id):
|
|
"""Deletes an existing role 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_role = self.db_api.role_destroy(req.context, id)
|
|
msg = _LI("Successfully deleted role %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
return dict(role=deleted_role)
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Delete denied for public role %(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 role %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except exception.NotFound:
|
|
msg = _LI("Role %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
return exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to delete role %s") % id)
|
|
raise
|
|
|
|
def _get_roles(self, context, filters, **params):
|
|
"""Get roles, wrapping in exception if necessary."""
|
|
try:
|
|
return self.db_api.role_get_all(context, filters=filters,
|
|
**params)
|
|
except exception.NotFound:
|
|
LOG.warn(_LW("Invalid marker. Project %(id)s could not be "
|
|
"found.") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Project could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except exception.Forbidden:
|
|
LOG.warn(_LW("Access denied to role %(id)s but returning "
|
|
"'not found'") % {'id': params.get('marker')})
|
|
msg = _("Invalid marker. Project could not be found.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to get roles"))
|
|
raise
|
|
|
|
@utils.mutating
|
|
def get_role(self, req, id):
|
|
"""Return data about the given role id."""
|
|
try:
|
|
role_data = self.db_api.role_get(req.context, id)
|
|
msg = "Successfully retrieved role %(id)s" % {'id': id}
|
|
LOG.debug(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("role %(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 role %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to show role %s") % id)
|
|
raise
|
|
role_services = self.db_api.role_services_get(req.context, id)
|
|
service_name = []
|
|
for role_service in role_services:
|
|
service_info = self.db_api.service_get(
|
|
req.context, role_service['service_id'])
|
|
service_name.append(service_info['name'])
|
|
if 'role' not in role_data:
|
|
role_data = dict(role=role_data)
|
|
if service_name:
|
|
role_data['role']['service_name'] = service_name
|
|
return role_data
|
|
|
|
def detail_role(self, req):
|
|
"""Return a filtered list of public, non-deleted hosts in detail
|
|
|
|
:param req: the Request object coming from the wsgi layer
|
|
:retval a mapping of the following form::
|
|
|
|
dict(hosts=[host_list])
|
|
|
|
Where host_list is a sequence of mappings containing
|
|
all host model fields.
|
|
"""
|
|
params = self._get_query_params(req)
|
|
|
|
roles = self._get_roles(req.context, **params)
|
|
|
|
return dict(roles=roles)
|
|
|
|
@utils.mutating
|
|
def update_role(self, req, id, body):
|
|
"""Updates an existing role 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,
|
|
"""
|
|
role_data = body['role']
|
|
try:
|
|
updated_role = self.db_api.role_update(req.context, id, role_data)
|
|
|
|
msg = _LI("Updating metadata for role %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
if 'role' not in updated_role:
|
|
role_data = dict(role=updated_role)
|
|
return role_data
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to update role metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("Role %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Role not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Update denied for public role %(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 role %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Role not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.Conflict as e:
|
|
LOG.info(utils.exception_to_str(e))
|
|
raise exc.HTTPConflict(body='Role operation conflicts',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to update role %s") % id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def role_services(self, req, id):
|
|
"""Return service list of the role."""
|
|
try:
|
|
role_data = self.db_api.role_services_get(req.context, id)
|
|
msg = "Successfully retrieved services of role %(id)s" % {'id': id}
|
|
LOG.debug(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("role %(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 services of role %(id)s denied but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to show services of role %s") % id)
|
|
raise
|
|
if 'role' not in role_data:
|
|
role_data = dict(role=role_data)
|
|
return role_data
|
|
|
|
@utils.mutating
|
|
def update_host(self, req, id, body):
|
|
"""Updates an existing host 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,
|
|
"""
|
|
host_data = body['host']
|
|
try:
|
|
orig_config_set_id = None
|
|
if 'config_set_id' in host_data:
|
|
orig_host_data = self.db_api.host_get(req.context, id)
|
|
orig_config_set_id = orig_host_data.get('config_set_id', None)
|
|
|
|
updated_host = self.db_api.host_update(req.context, id, host_data)
|
|
|
|
if orig_config_set_id:
|
|
try:
|
|
self.db_api.config_set_destroy(req.context,
|
|
orig_config_set_id)
|
|
except exception.Forbidden as e:
|
|
LOG.info(e)
|
|
|
|
msg = _LI("Updating metadata for host %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
if 'host' not in updated_host:
|
|
host_data = dict(host=updated_host)
|
|
return host_data
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to update host metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("Host %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Host not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Update denied for public host %(id)s") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPForbidden()
|
|
except exception.Forbidden as e:
|
|
msg = (_("%s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
raise exc.HTTPForbidden(msg)
|
|
except exception.Conflict as e:
|
|
LOG.info(utils.exception_to_str(e))
|
|
raise exc.HTTPConflict(body='Host operation conflicts',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to update host %s") % 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
|
|
|
|
@utils.mutating
|
|
def host_roles(self, req, id):
|
|
"""Return host list in the host_roles."""
|
|
try:
|
|
role_data = self.db_api.get_host_roles(req.context, id)
|
|
msg = "Successfully retrieved host of role %(id)s" % {'id': id}
|
|
LOG.debug(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("role %(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 host of role %(id)s denied but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to show host of role %s") % id)
|
|
raise
|
|
if 'role' not in role_data:
|
|
role_data = dict(role=role_data)
|
|
return role_data
|
|
|
|
@utils.mutating
|
|
def delete_role_hosts(self, req, id):
|
|
"""Return host list in the host_roles."""
|
|
try:
|
|
role_data = self.db_api.role_host_destroy(req.context, id)
|
|
msg = "Successfully retrieved host of role %(id)s" % {'id': id}
|
|
LOG.debug(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("role %(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 host of role %(id)s denied but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound()
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to show host of role %s") % id)
|
|
raise
|
|
if 'role' not in role_data:
|
|
role_data = dict(role=role_data)
|
|
return role_data
|
|
|
|
@utils.mutating
|
|
def update_role_hosts(self, req, id, body):
|
|
"""Return role hosts list in the host_roles."""
|
|
role_data = body['role']
|
|
try:
|
|
updated_role = self.db_api.role_host_update(
|
|
req.context, id, role_data)
|
|
|
|
msg = _LI("Updating metadata for role_host id %(id)s") % {'id': id}
|
|
return updated_role
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to update role host metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
except exception.NotFound:
|
|
msg = _LI("HostRole %(id)s not found") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='HostRole not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.ForbiddenPublicImage:
|
|
msg = _LI("Update denied for public host_role %(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 host_role %(id)s but returning"
|
|
" 'not found'") % {'id': id}
|
|
LOG.info(msg)
|
|
raise exc.HTTPNotFound(body='Role not found',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except exception.Conflict as e:
|
|
LOG.info(utils.exception_to_str(e))
|
|
raise exc.HTTPConflict(body='HostRole operation conflicts',
|
|
request=req,
|
|
content_type='text/plain')
|
|
except Exception:
|
|
LOG.exception(_LE("Unable to update host_role %s") % id)
|
|
raise
|
|
|
|
@utils.mutating
|
|
def config_interface(self, req, body):
|
|
"""Registers a new config_interface with the registry.
|
|
|
|
:param req: wsgi Request object
|
|
:param body: Dictionary of information about the host
|
|
|
|
:retval Returns the newly-created host information as a mapping,
|
|
which will include the newly-created host's internal id
|
|
in the 'id' field
|
|
"""
|
|
config_interface_meta = body
|
|
try:
|
|
config_interface_meta = self.db_api.config_interface(
|
|
req.context, config_interface_meta)
|
|
except exception.Invalid as e:
|
|
msg = (_("Failed to add role metadata. "
|
|
"Got error: %s") % utils.exception_to_str(e))
|
|
LOG.error(msg)
|
|
return exc.HTTPBadRequest(msg)
|
|
if 'config_interface_meta' not in config_interface_meta:
|
|
config_interface_meta = dict(
|
|
config_interface_meta=config_interface_meta)
|
|
return config_interface_meta
|
|
|
|
|
|
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)
|