initial merge

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

View File

@@ -0,0 +1,23 @@
# Copyright 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.
SUPPORTED_FILTERS = ['name', 'status','cluster_id','id','host_id', 'role_id', 'auto_scale','container_format', 'disk_format',
'min_ram', 'min_disk', 'size_min', 'size_max',
'is_public', 'changes-since', 'protected']
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
# Metadata which only an admin can change once the image is active
ACTIVE_IMMUTABLE = ('size', 'checksum')

View File

@@ -0,0 +1,742 @@
# Copyright 2013 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.
"""
/clusters endpoint for Daisy v1 API
"""
import copy
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob.exc import HTTPServerError
from webob import Response
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
CLUSTER_DEFAULT_NETWORKS = ['PUBLIC', 'DEPLOYMENT', 'PRIVATE', 'EXTERNAL',
'STORAGE', 'VXLAN', 'MANAGEMENT']
class Controller(controller.BaseController):
"""
WSGI controller for clusters resource in Daisy v1 API
The clusters resource API is a RESTful web service for cluster data. The API
is as follows::
GET /clusters -- Returns a set of brief metadata about clusters
GET /clusters -- Returns a set of detailed metadata about
clusters
HEAD /clusters/<ID> -- Return metadata about an cluster with id <ID>
GET /clusters/<ID> -- Return cluster data for cluster with id <ID>
POST /clusters -- Store cluster data and return metadata about the
newly-stored cluster
PUT /clusters/<ID> -- Update cluster metadata and/or upload cluster
data for a previously-reserved cluster
DELETE /clusters/<ID> -- Delete the cluster with id <ID>
"""
def check_params(f):
"""
Cluster add and update operation params valid check.
:param f: Function hanle for 'cluster_add' and 'cluster_update'.
:return: f
"""
def wrapper(*args, **kwargs):
controller, req = args
cluster_meta = kwargs.get('cluster_meta', None)
cluster_id = kwargs.get('id', None)
errmsg = (_("I'm params checker."))
LOG.debug(_("Params check for cluster-add or cluster-update begin!"))
def check_params_range(param, type=None):
'''
param : input a list ,such as [start, end]
check condition: start must less than end, and existed with pair
return True of False
'''
if len(param) != 2:
msg = '%s range must be existed in pairs.' % type
raise HTTPForbidden(explanation=msg)
if param[0] == None or param[0] == '':
msg = 'The start value of %s range can not be None.' % type
raise HTTPForbidden(explanation=msg)
if param[1] == None:
msg = 'The end value of %s range can not be None.' % type
raise HTTPForbidden(explanation=msg)
if int(param[0]) > int(param[1]):
msg = 'The start value of the %s range must be less than the end value.' % type
raise HTTPForbidden(explanation=msg)
if type not in ['vni']:
if int(param[0]) < 0 or int(param[0]) > 4096:
msg = 'Invalid value of the start value(%s) of the %s range .' % (param[0], type)
raise HTTPForbidden(explanation=msg)
if int(param[1]) < 0 or int(param[1]) > 4096:
msg = 'Invalid value of the end value(%s) of the %s range .' % (param[1], type)
raise HTTPForbidden(explanation=msg)
else:
if int(param[0]) < 0 or int(param[0]) > 16777216:
msg = 'Invalid value of the start value(%s) of the %s range .' % (param[0], type)
raise HTTPForbidden(explanation=msg)
if int(param[1]) < 0 or int(param[1]) > 16777216:
msg = 'Invalid value of the end value(%s) of the %s range .' % (param[1], type)
raise HTTPForbidden(explanation=msg)
return True
def _check_auto_scale(req, cluster_meta):
if cluster_meta.has_key('auto_scale') and cluster_meta['auto_scale'] =='1':
meta = { "auto_scale":'1' }
params = { 'filters': meta }
clusters = registry.get_clusters_detail(req.context, **params)
if clusters:
if cluster_id:
temp_cluster = [cluster for cluster in clusters if cluster['id'] !=cluster_id]
if temp_cluster:
errmsg = (_("already exist cluster auto_scale is true"))
raise HTTPBadRequest(explanation=errmsg)
else:
errmsg = (_("already exist cluster auto_scale is true"))
raise HTTPBadRequest(explanation=errmsg)
def _ip_into_int(ip):
"""
Switch ip string to decimalism integer..
:param ip: ip string
:return: decimalism integer
"""
return reduce(lambda x, y: (x<<8)+y, map(int, ip.split('.')))
def _is_in_network_range(ip, network):
"""
Check ip is in range
:param ip: Ip will be checked, like:192.168.1.2.
:param network: Ip range,like:192.168.0.0/24.
:return: If ip in range,return True,else return False.
"""
network = network.split('/')
mask = ~(2**(32 - int(network[1])) - 1)
return (_ip_into_int(ip) & mask) == (_ip_into_int(network[0]) & mask)
def _check_param_nonull_and_valid(values_set, keys_set, valids_set={}):
"""
Check operation params is not null and valid.
:param values_set: Params set.
:param keys_set: Params will be checked.
:param valids_set:
:return:
"""
for k in keys_set:
v = values_set.get(k, None)
if type(v) == type(True) and v == None:
errmsg = (_("Segment %s can't be None." % k))
raise HTTPBadRequest(explanation=errmsg)
elif type(v) != type(True) and not v:
errmsg = (_("Segment %s can't be None." % k))
raise HTTPBadRequest(explanation=errmsg)
for (k, v) in valids_set.items():
# if values_set.get(k, None) and values_set[k] not in v:
if values_set.get(k, None) and -1 == v.find(values_set[k]):
errmsg = (_("Segment %s is out of valid range." % k))
raise HTTPBadRequest(explanation=errmsg)
def _get_network_detail(req, cluster_id, networks_list):
all_network_list = []
if cluster_id:
all_network_list = registry.get_networks_detail(req.context, cluster_id)
if networks_list:
for net_id in networks_list:
network_detail = registry.get_network_metadata(req.context, net_id)
all_network_list.append(network_detail)
all_private_network_list = \
[network for network in all_network_list if network['network_type'] == "PRIVATE"]
return all_private_network_list
def _check_cluster_add_parameters(req, cluster_meta):
"""
By params set segment,check params is available.
:param req: http req
:param cluster_meta: params set
:return:error message
"""
if cluster_meta.has_key('nodes'):
orig_keys = list(eval(cluster_meta['nodes']))
for host_id in orig_keys:
controller._raise_404_if_host_deleted(req, host_id)
if cluster_meta.has_key('networks'):
orig_keys = list(eval(cluster_meta['networks']))
network_with_same_name = []
for network_id in orig_keys:
network_name = controller._raise_404_if_network_deleted(req, network_id)
if network_name in CLUSTER_DEFAULT_NETWORKS:
return (_("Network name %s of %s already exits"
" in the cluster, please check." %
(network_name, network_id)))
if network_name in network_with_same_name:
return (_("Network name can't be same with each other in 'networks[]', "
"please check."))
network_with_same_name.append(network_name)
# checkout network_params--------------------------------------------------
if cluster_meta.get('networking_parameters', None):
networking_parameters = eval(cluster_meta['networking_parameters'])
_check_param_nonull_and_valid(networking_parameters,
['segmentation_type'])
segmentation_type_set = networking_parameters['segmentation_type'].split(",")
for segmentation_type in segmentation_type_set:
if segmentation_type not in ['vlan', 'vxlan', 'flat', 'gre']:
return (_("Segmentation_type of networking_parameters is not valid."))
if segmentation_type =='vxlan':
_check_param_nonull_and_valid(networking_parameters,['vni_range'])
elif segmentation_type =='gre':
_check_param_nonull_and_valid(networking_parameters,['gre_id_range'])
vlan_range = networking_parameters.get("vlan_range", None)
vni_range = networking_parameters.get("vni_range", None)
gre_id_range = networking_parameters.get("gre_id_range", None)
#if (vlan_range and len(vlan_range) != 2) \
# or (vni_range and len(vni_range) != 2) \
# or (gre_id_range and len(gre_id_range) != 2):
# return (_("Range params must be pair."))
if vlan_range:
check_params_range(vlan_range, 'vlan')
if vni_range:
check_params_range(vni_range, 'vni')
if gre_id_range:
check_params_range(gre_id_range, 'gre_id')
# check logic_networks--------------------------------------------------
subnet_name_set = [] # record all subnets's name
logic_network_name_set = [] # record all logic_network's name
subnets_in_logic_network = {}
external_logic_network_name = []
if cluster_meta.get('logic_networks', None):
# get physnet_name list
all_private_cluster_networks_list = _get_network_detail(
req, cluster_id,
cluster_meta.get('networks', None)
if not isinstance(cluster_meta.get('networks', None), unicode)
else eval(cluster_meta.get('networks', None)))
if not all_private_cluster_networks_list:
LOG.info("Private network is empty in db, it lead logical network config invalid.")
physnet_name_set = [net['name'] for net in all_private_cluster_networks_list]
logic_networks = eval(cluster_meta['logic_networks'])
for logic_network in logic_networks:
subnets_in_logic_network[logic_network['name']] = []
# We force setting the physnet_name of flat logical network to 'flat'.
if logic_network.get('segmentation_type', None) == "flat":
if logic_network['physnet_name'] != "physnet1" or logic_network['type'] != "external":
LOG.info("When 'segmentation_type' is flat the 'physnet_name' and 'type' segmentation"
"must be 'physnet1'' and 'external'', but got '%s' and '%s'.We have changed"
"it to the valid value.")
logic_network['physnet_name'] = "physnet1"
logic_network['type'] = "external"
physnet_name_set.append("physnet1")
_check_param_nonull_and_valid(
logic_network,
['name', 'type', 'physnet_name', 'segmentation_type', 'shared', 'segmentation_id'],
{'segmentation_type' : networking_parameters['segmentation_type'],
'physnet_name' : ','.join(physnet_name_set),
'type' : ','.join(["external", "internal"])})
if logic_network['type'] == "external":
external_logic_network_name.append(logic_network['name'])
logic_network_name_set.append(logic_network['name'])
# By segmentation_type check segmentation_id is in range
segmentation_id = logic_network.get('segmentation_id', None)
if segmentation_id:
err = "Segmentation_id is out of private network %s of %s.Vaild range is [%s, %s]."
segmentation_type = logic_network.get('segmentation_type', None)
if 0 == cmp(segmentation_type, "vlan"):
private_vlan_range = \
[(net['vlan_start'], net['vlan_end'])
for net in all_private_cluster_networks_list
if logic_network['physnet_name'] == net['name']]
if private_vlan_range and \
not private_vlan_range[0][0] or \
not private_vlan_range[0][1]:
return (_("Private network plane %s don't config the 'vlan_start' or "
"'vlan_end' parameter."))
if int(segmentation_id) not in range(private_vlan_range[0][0], private_vlan_range[0][1]):
return (_(err % ("vlan_range", logic_network['physnet_name'],
private_vlan_range[0][0], private_vlan_range[0][1])))
elif 0 == cmp(segmentation_type, "vxlan") and vni_range:
if int(segmentation_id) not in range(vni_range[0], vni_range[1]):
return (_("Segmentation_id is out of vni_range."))
elif 0 == cmp(segmentation_type, "gre") and gre_id_range:
if int(segmentation_id) not in range(gre_id_range[0], gre_id_range[1]):
return (_("Segmentation_id is out of gre_id_range."))
# checkout subnets params--------------------------------------------------
if logic_network.get('subnets', None):
subnet_data = logic_network['subnets']
for subnet in subnet_data:
_check_param_nonull_and_valid(
subnet,
['name', 'cidr'])
subnet_name_set.append(subnet['name'])
# By cidr check floating_ranges is in range and not overlap
#---------------start-----
if subnet['gateway'] and not _is_in_network_range(subnet['gateway'], subnet['cidr']):
return (_("Wrong gateway format."))
if subnet['floating_ranges']:
inter_ip = lambda x: '.'.join([str(x/(256**i)%256) for i in range(3,-1,-1)])
floating_ranges_with_int_ip = list()
sorted_floating_ranges = list()
sorted_floating_ranges_with_int_ip = list()
for floating_ip in subnet['floating_ranges']:
if len(floating_ip) != 2:
return (_("Floating ip must be paris."))
ip_start = _ip_into_int(floating_ip[0])
ip_end = _ip_into_int(floating_ip[1])
if ip_start > ip_end:
return (_("Wrong floating ip format."))
floating_ranges_with_int_ip.append([ip_start, ip_end])
sorted_floating_ranges_with_int_ip = sorted(floating_ranges_with_int_ip, key=lambda x : x[0])
for ip_range in sorted_floating_ranges_with_int_ip:
ip_start = inter_ip(ip_range[0])
ip_end = inter_ip(ip_range[1])
sorted_floating_ranges.append([ip_start, ip_end])
last_rang_ip = []
for floating in sorted_floating_ranges:
if not _is_in_network_range(floating[0], subnet['cidr']) \
or not _is_in_network_range(floating[1], subnet['cidr']):
return (_("Floating ip or gateway is out of range cidr."))
err_list = [err for err in last_rang_ip if _ip_into_int(floating[0]) < err]
if last_rang_ip and 0 < len(err_list):
return (_("Between floating ip range can not be overlap."))
last_rang_ip.append(_ip_into_int(floating[1]))
subnets_in_logic_network[logic_network['name']].append(subnet['name'])
# check external logical network uniqueness
if len(external_logic_network_name) > 1:
return (_("External logical network is uniqueness in the cluster.Got %s." %
",".join(external_logic_network_name)))
# check logic_network_name uniqueness
if len(logic_network_name_set) != len(set(logic_network_name_set)):
return (_("Logic network name segment is repetition."))
# check subnet_name uniqueness
if len(subnet_name_set) != len(set(subnet_name_set)):
return (_("Subnet name segment is repetition."))
cluster_meta['logic_networks'] = unicode(logic_networks)
# check routers--------------------------------------------------
subnet_name_set_deepcopy = copy.deepcopy(subnet_name_set)
router_name_set = [] # record all routers name
if cluster_meta.get('routers', None):
router_data = eval(cluster_meta['routers'])
for router in router_data:
_check_param_nonull_and_valid(router, ['name'])
# check relevance logic_network is valid
external_logic_network_data = router.get('external_logic_network', None)
if external_logic_network_data and \
external_logic_network_data not in logic_network_name_set:
return (_("Logic_network %s is not valid range." % external_logic_network_data))
router_name_set.append(router['name'])
# check relevance subnets is valid
for subnet in router.get('subnets', []):
if subnet not in subnet_name_set:
return (_("Subnet %s is not valid range." % subnet))
# subnet cann't relate with two routers
if subnet not in subnet_name_set_deepcopy:
return (_("The subnet can't be related with multiple routers."))
subnet_name_set_deepcopy.remove(subnet)
if external_logic_network_data and \
subnets_in_logic_network[external_logic_network_data] and \
set(subnets_in_logic_network[external_logic_network_data]). \
issubset(set(router['subnets'])):
return (_("Logic network's subnets is all related with a router, it's not allowed."))
# check subnet_name uniqueness
if len(router_name_set) != len(set(router_name_set)):
return (_("Router name segment is repetition."))
return (_("I'm params checker."))
_check_auto_scale(req, cluster_meta)
check_result = _check_cluster_add_parameters(req, cluster_meta)
if 0 != cmp(check_result, errmsg):
LOG.exception(_("Params check for cluster-add or cluster-update is failed!"))
raise HTTPBadRequest(explanation=check_result)
LOG.debug(_("Params check for cluster-add or cluster-update is done!"))
return f(*args, **kwargs)
return wrapper
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
def _raise_404_if_host_deleted(self, req, host_id):
host = self.get_host_meta_or_404(req, host_id)
if host['deleted']:
msg = _("Host with identifier %s has been deleted.") % host_id
raise HTTPNotFound(msg)
def _raise_404_if_network_deleted(self, req, network_id):
network = self.get_network_meta_or_404(req, network_id)
if network['deleted']:
msg = _("Network with identifier %s has been deleted.") % network_id
raise HTTPNotFound(msg)
return network.get('name', None)
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
@utils.mutating
@check_params
def add_cluster(self, req, cluster_meta):
"""
Adds a new cluster to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about cluster
:raises HTTPBadRequest if x-cluster-name is missing
"""
self._enforce(req, 'add_cluster')
cluster_name = cluster_meta["name"]
if not cluster_name:
raise ValueError('cluster name is null!')
cluster_name_split = cluster_name.split('_')
for cluster_name_info in cluster_name_split :
if not cluster_name_info.isalnum():
raise ValueError('cluster name must be numbers or letters or underscores !')
if cluster_meta.get('nodes', None):
orig_keys = list(eval(cluster_meta['nodes']))
for host_id in orig_keys:
self._raise_404_if_host_deleted(req, host_id)
node = registry.get_host_metadata(req.context, host_id)
if node['status'] == 'in-cluster':
msg = _("Forbidden to add host %s with status "
"'in-cluster' in another cluster") % host_id
raise HTTPForbidden(explanation=msg)
if node.get('interfaces', None):
interfaces = node['interfaces']
input_host_pxe_info = [interface for interface in interfaces
if interface.get('is_deployment', None) == 1]
if not input_host_pxe_info and node.get('os_status',None) != 'active':
msg = _("The host %s has more than one dhcp server, "
"please choose one interface for deployment") % host_id
raise HTTPServerError(explanation=msg)
print cluster_name
print cluster_meta
cluster_meta = registry.add_cluster_metadata(req.context, cluster_meta)
return {'cluster_meta': cluster_meta}
@utils.mutating
def delete_cluster(self, req, id):
"""
Deletes a cluster from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about cluster
:raises HTTPBadRequest if x-cluster-name is missing
"""
self._enforce(req, 'delete_cluster')
#cluster = self.get_cluster_meta_or_404(req, id)
print "delete_cluster:%s" % id
try:
registry.delete_cluster_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find cluster to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete cluster: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("cluster %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('cluster.delete', cluster)
return Response(body='', status=200)
@utils.mutating
def get_cluster(self, req, id):
"""
Returns metadata about an cluster in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque cluster identifier
:raises HTTPNotFound if cluster metadata is not available to user
"""
self._enforce(req, 'get_cluster')
cluster_meta = self.get_cluster_meta_or_404(req, id)
return {'cluster_meta': cluster_meta}
def detail(self, req):
"""
Returns detailed information for all available clusters
:param req: The WSGI/Webob Request object
:retval The response body is a mapping of the following form::
{'clusters': [
{'id': <ID>,
'name': <NAME>,
'nodes': <NODES>,
'networks': <NETWORKS>,
'description': <DESCRIPTION>,
'created_at': <TIMESTAMP>,
'updated_at': <TIMESTAMP>,
'deleted_at': <TIMESTAMP>|<NONE>,}, ...
]}
"""
self._enforce(req, 'get_clusters')
params = self._get_query_params(req)
try:
clusters = registry.get_clusters_detail(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(clusters=clusters)
@utils.mutating
@check_params
def update_cluster(self, req, id, cluster_meta):
"""
Updates an existing cluster with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque cluster identifier
:retval Returns the updated cluster information as a mapping
"""
self._enforce(req, 'update_cluster')
if cluster_meta.has_key('nodes'):
orig_keys = list(eval(cluster_meta['nodes']))
for host_id in orig_keys:
self._raise_404_if_host_deleted(req, host_id)
node = registry.get_host_metadata(req.context, host_id)
if node['status'] == 'in-cluster':
host_cluster = registry.get_host_clusters(req.context, host_id)
if host_cluster[0]['cluster_id'] != id:
msg = _("Forbidden to add host %s with status "
"'in-cluster' in another cluster") % host_id
raise HTTPForbidden(explanation=msg)
if node.get('interfaces', None):
interfaces = node['interfaces']
input_host_pxe_info = [interface for interface in interfaces
if interface.get('is_deployment', None) == 1]
if not input_host_pxe_info and node.get('os_status', None) != 'active':
msg = _("The host %s has more than one dhcp server, "
"please choose one interface for deployment") % host_id
raise HTTPServerError(explanation=msg)
if cluster_meta.has_key('networks'):
orig_keys = list(eval(cluster_meta['networks']))
for network_id in orig_keys:
self._raise_404_if_network_deleted(req, network_id)
orig_cluster_meta = self.get_cluster_meta_or_404(req, id)
# Do not allow any updates on a deleted cluster.
# Fix for LP Bug #1060930
if orig_cluster_meta['deleted']:
msg = _("Forbidden to update deleted cluster.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
try:
cluster_meta = registry.update_cluster_metadata(req.context,
id,
cluster_meta)
except exception.Invalid as e:
msg = (_("Failed to update cluster metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find cluster to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update cluster: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('Cluster operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('cluster.update', cluster_meta)
return {'cluster_meta': cluster_meta}
class ProjectDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["cluster_meta"] = utils.get_cluster_meta(request)
return result
def add_cluster(self, request):
return self._deserialize(request)
def update_cluster(self, request):
return self._deserialize(request)
class ProjectSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_cluster(self, response, result):
cluster_meta = result['cluster_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(cluster=cluster_meta))
return response
def update_cluster(self, response, result):
cluster_meta = result['cluster_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(cluster=cluster_meta))
return response
def delete_cluster(self, response, result):
cluster_meta = result['cluster_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(cluster=cluster_meta))
return response
def get_cluster(self, response, result):
cluster_meta = result['cluster_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(cluster=cluster_meta))
return response
def create_resource():
"""Projects resource factory method"""
deserializer = ProjectDeserializer()
serializer = ProjectSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,328 @@
# Copyright 2013 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.
"""
/components endpoint for Daisy v1 API
"""
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
class Controller(controller.BaseController):
"""
WSGI controller for components resource in Daisy v1 API
The components resource API is a RESTful web service for component data. The API
is as follows::
GET /components -- Returns a set of brief metadata about components
GET /components/detail -- Returns a set of detailed metadata about
components
HEAD /components/<ID> -- Return metadata about an component with id <ID>
GET /components/<ID> -- Return component data for component with id <ID>
POST /components -- Store component data and return metadata about the
newly-stored component
PUT /components/<ID> -- Update component metadata and/or upload component
data for a previously-reserved component
DELETE /components/<ID> -- Delete the component with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
@utils.mutating
def add_component(self, req, component_meta):
"""
Adds a new component to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about component
:raises HTTPBadRequest if x-component-name is missing
"""
self._enforce(req, 'add_component')
#component_id=component_meta["id"]
#component_owner=component_meta["owner"]
component_name = component_meta["name"]
component_description = component_meta["description"]
#print component_id
#print component_owner
print component_name
print component_description
component_meta = registry.add_component_metadata(req.context, component_meta)
return {'component_meta': component_meta}
@utils.mutating
def delete_component(self, req, id):
"""
Deletes a component from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about component
:raises HTTPBadRequest if x-component-name is missing
"""
self._enforce(req, 'delete_component')
#component = self.get_component_meta_or_404(req, id)
print "delete_component:%s" % id
try:
registry.delete_component_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find component to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete component: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("component %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('component.delete', component)
return Response(body='', status=200)
@utils.mutating
def get_component(self, req, id):
"""
Returns metadata about an component in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque component identifier
:raises HTTPNotFound if component metadata is not available to user
"""
self._enforce(req, 'get_component')
component_meta = self.get_component_meta_or_404(req, id)
return {'component_meta': component_meta}
def detail(self, req):
"""
Returns detailed information for all available components
:param req: The WSGI/Webob Request object
:retval The response body is a mapping of the following form::
{'components': [
{'id': <ID>,
'name': <NAME>,
'description': <DESCRIPTION>,
'created_at': <TIMESTAMP>,
'updated_at': <TIMESTAMP>,
'deleted_at': <TIMESTAMP>|<NONE>,}, ...
]}
"""
self._enforce(req, 'get_components')
params = self._get_query_params(req)
try:
components = registry.get_components_detail(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(components=components)
@utils.mutating
def update_component(self, req, id, component_meta):
"""
Updates an existing component with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'modify_image')
orig_component_meta = self.get_component_meta_or_404(req, id)
# Do not allow any updates on a deleted image.
# Fix for LP Bug #1060930
if orig_component_meta['deleted']:
msg = _("Forbidden to update deleted component.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
try:
component_meta = registry.update_component_metadata(req.context,
id,
component_meta)
except exception.Invalid as e:
msg = (_("Failed to update component metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find component to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update component: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('Host operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('component.update', component_meta)
return {'component_meta': component_meta}
class ComponentDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["component_meta"] = utils.get_component_meta(request)
return result
def add_component(self, request):
return self._deserialize(request)
def update_component(self, request):
return self._deserialize(request)
class ComponentSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_component(self, response, result):
component_meta = result['component_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(component=component_meta))
return response
def delete_component(self, response, result):
component_meta = result['component_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(component=component_meta))
return response
def get_component(self, response, result):
component_meta = result['component_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(component=component_meta))
return response
def create_resource():
"""Components resource factory method"""
deserializer = ComponentDeserializer()
serializer = ComponentSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,325 @@
# Copyright 2013 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.
"""
/config_files endpoint for Daisy v1 API
"""
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
class Controller(controller.BaseController):
"""
WSGI controller for config_files resource in Daisy v1 API
The config_files resource API is a RESTful web service for config_file data. The API
is as follows::
GET /config_files -- Returns a set of brief metadata about config_files
GET /config_files/detail -- Returns a set of detailed metadata about
config_files
HEAD /config_files/<ID> -- Return metadata about an config_file with id <ID>
GET /config_files/<ID> -- Return config_file data for config_file with id <ID>
POST /config_files -- Store config_file data and return metadata about the
newly-stored config_file
PUT /config_files/<ID> -- Update config_file metadata and/or upload config_file
data for a previously-reserved config_file
DELETE /config_files/<ID> -- Delete the config_file with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
@utils.mutating
def add_config_file(self, req, config_file_meta):
"""
Adds a new config_file to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about config_file
:raises HTTPBadRequest if x-config_file-name is missing
"""
self._enforce(req, 'add_config_file')
#config_file_id=config_file_meta["id"]
config_file_name = config_file_meta["name"]
config_file_description = config_file_meta["description"]
#print config_file_id
print config_file_name
print config_file_description
config_file_meta = registry.add_config_file_metadata(req.context, config_file_meta)
return {'config_file_meta': config_file_meta}
@utils.mutating
def delete_config_file(self, req, id):
"""
Deletes a config_file from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about config_file
:raises HTTPBadRequest if x-config_file-name is missing
"""
self._enforce(req, 'delete_config_file')
try:
registry.delete_config_file_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find config_file to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete config_file: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("config_file %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('config_file.delete', config_file)
return Response(body='', status=200)
@utils.mutating
def get_config_file(self, req, id):
"""
Returns metadata about an config_file in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque config_file identifier
:raises HTTPNotFound if config_file metadata is not available to user
"""
self._enforce(req, 'get_config_file')
config_file_meta = self.get_config_file_meta_or_404(req, id)
return {'config_file_meta': config_file_meta}
def detail(self, req):
"""
Returns detailed information for all available config_files
:param req: The WSGI/Webob Request object
:retval The response body is a mapping of the following form::
{'config_files': [
{'id': <ID>,
'name': <NAME>,
'description': <DESCRIPTION>,
'created_at': <TIMESTAMP>,
'updated_at': <TIMESTAMP>,
'deleted_at': <TIMESTAMP>|<NONE>,}, ...
]}
"""
self._enforce(req, 'get_config_files')
params = self._get_query_params(req)
try:
config_files = registry.get_config_files_detail(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(config_files=config_files)
@utils.mutating
def update_config_file(self, req, id, config_file_meta):
"""
Updates an existing config_file with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'modify_image')
orig_config_file_meta = self.get_config_file_meta_or_404(req, id)
# Do not allow any updates on a deleted image.
# Fix for LP Bug #1060930
if orig_config_file_meta['deleted']:
msg = _("Forbidden to update deleted config_file.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
try:
config_file_meta = registry.update_config_file_metadata(req.context,
id,
config_file_meta)
except exception.Invalid as e:
msg = (_("Failed to update config_file metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find config_file to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update config_file: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('config_file operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('config_file.update', config_file_meta)
return {'config_file_meta': config_file_meta}
class Config_fileDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["config_file_meta"] = utils.get_config_file_meta(request)
return result
def add_config_file(self, request):
return self._deserialize(request)
def update_config_file(self, request):
return self._deserialize(request)
class Config_fileSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_config_file(self, response, result):
config_file_meta = result['config_file_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config_file=config_file_meta))
return response
def delete_config_file(self, response, result):
config_file_meta = result['config_file_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config_file=config_file_meta))
return response
def get_config_file(self, response, result):
config_file_meta = result['config_file_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config_file=config_file_meta))
return response
def create_resource():
"""config_files resource factory method"""
deserializer = Config_fileDeserializer()
serializer = Config_fileSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,434 @@
# Copyright 2013 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.
"""
/config_sets endpoint for Daisy v1 API
"""
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
from daisy.api.configset import manager
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
class Controller(controller.BaseController):
"""
WSGI controller for config_sets resource in Daisy v1 API
The config_sets resource API is a RESTful web service for config_set data. The API
is as follows::
GET /config_sets -- Returns a set of brief metadata about config_sets
GET /config_sets/detail -- Returns a set of detailed metadata about
config_sets
HEAD /config_sets/<ID> -- Return metadata about an config_set with id <ID>
GET /config_sets/<ID> -- Return config_set data for config_set with id <ID>
POST /config_sets -- Store config_set data and return metadata about the
newly-stored config_set
PUT /config_sets/<ID> -- Update config_set metadata and/or upload config_set
data for a previously-reserved config_set
DELETE /config_sets/<ID> -- Delete the config_set with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
def _raise_404_if_cluster_deleted(self, req, cluster_id):
cluster = self.get_cluster_meta_or_404(req, cluster_id)
if cluster['deleted']:
msg = _("cluster with identifier %s has been deleted.") % cluster_id
raise HTTPNotFound(msg)
@utils.mutating
def add_config_set(self, req, config_set_meta):
"""
Adds a new config_set to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about config_set
:raises HTTPBadRequest if x-config_set-name is missing
"""
self._enforce(req, 'add_config_set')
#config_set_id=config_set_meta["id"]
config_set_name = config_set_meta["name"]
config_set_description = config_set_meta["description"]
#print config_set_id
print config_set_name
print config_set_description
config_set_meta = registry.add_config_set_metadata(req.context, config_set_meta)
return {'config_set_meta': config_set_meta}
@utils.mutating
def delete_config_set(self, req, id):
"""
Deletes a config_set from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about config_set
:raises HTTPBadRequest if x-config_set-name is missing
"""
self._enforce(req, 'delete_config_set')
try:
registry.delete_config_set_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find config_set to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete config_set: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("config_set %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('config_set.delete', config_set)
return Response(body='', status=200)
@utils.mutating
def get_config_set(self, req, id):
"""
Returns metadata about an config_set in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque config_set identifier
:raises HTTPNotFound if config_set metadata is not available to user
"""
self._enforce(req, 'get_config_set')
config_set_meta = self.get_config_set_meta_or_404(req, id)
return {'config_set_meta': config_set_meta}
def detail(self, req):
"""
Returns detailed information for all available config_sets
:param req: The WSGI/Webob Request object
:retval The response body is a mapping of the following form::
{'config_sets': [
{'id': <ID>,
'name': <NAME>,
'description': <DESCRIPTION>,
'created_at': <TIMESTAMP>,
'updated_at': <TIMESTAMP>,
'deleted_at': <TIMESTAMP>|<NONE>,}, ...
]}
"""
self._enforce(req, 'get_config_sets')
params = self._get_query_params(req)
try:
config_sets = registry.get_config_sets_detail(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(config_sets=config_sets)
@utils.mutating
def update_config_set(self, req, id, config_set_meta):
"""
Updates an existing config_set with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'modify_image')
orig_config_set_meta = self.get_config_set_meta_or_404(req, id)
# Do not allow any updates on a deleted image.
# Fix for LP Bug #1060930
if orig_config_set_meta['deleted']:
msg = _("Forbidden to update deleted config_set.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
try:
config_set_meta = registry.update_config_set_metadata(req.context,
id,
config_set_meta)
except exception.Invalid as e:
msg = (_("Failed to update config_set metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find config_set to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update config_set: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('config_set operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('config_set.update', config_set_meta)
return {'config_set_meta': config_set_meta}
def _raise_404_if_role_exist(self,req,config_set_meta):
role_id_list=[]
try:
roles = registry.get_roles_detail(req.context)
for role in roles:
for role_name in eval(config_set_meta['role']):
if role['cluster_id'] == config_set_meta['cluster'] and role['name'] == role_name:
role_id_list.append(role['id'])
break
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return role_id_list
@utils.mutating
def cluster_config_set_update(self, req, config_set_meta):
if config_set_meta.has_key('cluster'):
orig_cluster = str(config_set_meta['cluster'])
self._raise_404_if_cluster_deleted(req, orig_cluster)
try:
if config_set_meta.get('role',None):
role_id_list=self._raise_404_if_role_exist(req,config_set_meta)
if len(role_id_list) == len(eval(config_set_meta['role'])):
for role_id in role_id_list:
backend=manager.configBackend('clushshell', req, role_id)
backend.push_config()
else:
msg = "the role is not exist"
LOG.error(msg)
raise HTTPNotFound(msg)
else:
roles = registry.get_roles_detail(req.context)
for role in roles:
if role['cluster_id'] == config_set_meta['cluster']:
backend=manager.configBackend('clushshell', req, role['id'])
backend.push_config()
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
config_status={"status":"config successful"}
return {'config_set':config_status}
else:
msg = "the cluster is not exist"
LOG.error(msg)
raise HTTPNotFound(msg)
@utils.mutating
def cluster_config_set_progress(self, req, config_set_meta):
role_list = []
if config_set_meta.has_key('cluster'):
orig_cluster = str(config_set_meta['cluster'])
self._raise_404_if_cluster_deleted(req, orig_cluster)
try:
if config_set_meta.get('role',None):
role_id_list=self._raise_404_if_role_exist(req,config_set_meta)
if len(role_id_list) == len(eval(config_set_meta['role'])):
for role_id in role_id_list:
role_info = {}
role_meta=registry.get_role_metadata(req.context, role_id)
role_info['role-name']=role_meta['name']
role_info['config_set_update_progress']=role_meta['config_set_update_progress']
role_list.append(role_info)
else:
msg = "the role is not exist"
LOG.error(msg)
raise HTTPNotFound(msg)
else:
roles = registry.get_roles_detail(req.context)
for role in roles:
if role['cluster_id'] == config_set_meta['cluster']:
role_info = {}
role_info['role-name']=role['name']
role_info['config_set_update_progress']=role['config_set_update_progress']
role_list.append(role_info)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return role_list
else:
msg = "the cluster is not exist"
LOG.error(msg)
raise HTTPNotFound(msg)
class Config_setDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["config_set_meta"] = utils.get_config_set_meta(request)
return result
def add_config_set(self, request):
return self._deserialize(request)
def update_config_set(self, request):
return self._deserialize(request)
def cluster_config_set_update(self, request):
return self._deserialize(request)
def cluster_config_set_progress(self, request):
return self._deserialize(request)
class Config_setSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_config_set(self, response, result):
config_set_meta = result['config_set_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config_set=config_set_meta))
return response
def delete_config_set(self, response, result):
config_set_meta = result['config_set_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config_set=config_set_meta))
return response
def get_config_set(self, response, result):
config_set_meta = result['config_set_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config_set=config_set_meta))
return response
def cluster_config_set_update(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def cluster_config_set_progress(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config_set=result))
return response
def create_resource():
"""config_sets resource factory method"""
deserializer = Config_setDeserializer()
serializer = Config_setSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,301 @@
# Copyright 2013 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.
"""
/configs endpoint for Daisy v1 API
"""
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
class Controller(controller.BaseController):
"""
WSGI controller for configs resource in Daisy v1 API
The configs resource API is a RESTful web service for config data. The API
is as follows::
GET /configs -- Returns a set of brief metadata about configs
GET /configs/detail -- Returns a set of detailed metadata about
configs
HEAD /configs/<ID> -- Return metadata about an config with id <ID>
GET /configs/<ID> -- Return config data for config with id <ID>
POST /configs -- Store config data and return metadata about the
newly-stored config
PUT /configs/<ID> -- Update config metadata and/or upload config
data for a previously-reserved config
DELETE /configs/<ID> -- Delete the config with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
def _raise_404_if_config_set_delete(self, req, config_set_id):
config_set = self.get_config_set_meta_or_404(req, config_set_id)
if config_set['deleted']:
msg = _("config_set with identifier %s has been deleted.") % config_set_id
raise HTTPNotFound(msg)
def _raise_404_if_config_file_delete(self, req, config_file_id):
config_file = self.get_config_file_meta_or_404(req, config_file_id)
if config_file['deleted']:
msg = _("config_file with identifier %s has been deleted.") % config_file_id
raise HTTPNotFound(msg)
def _raise_404_if_role_exist(self,req,config_meta):
role_id=""
try:
roles = registry.get_roles_detail(req.context)
for role in roles:
if role['cluster_id'] == config_meta['cluster'] and role['name'] == config_meta['role']:
role_id=role['id']
break
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return role_id
def _raise_404_if_cluster_deleted(self, req, cluster_id):
cluster = self.get_cluster_meta_or_404(req, cluster_id)
if cluster['deleted']:
msg = _("cluster with identifier %s has been deleted.") % cluster_id
raise HTTPNotFound(msg)
@utils.mutating
def add_config(self, req, config_meta):
"""
Adds a new config to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about config
:raises HTTPBadRequest if x-config-name is missing
"""
self._enforce(req, 'add_config')
if config_meta.has_key('cluster'):
orig_cluster = str(config_meta['cluster'])
self._raise_404_if_cluster_deleted(req, orig_cluster)
if config_meta.has_key('role'):
role_id=self._raise_404_if_role_exist(req,config_meta)
if not role_id:
msg = "the role name is not exist"
LOG.error(msg)
raise HTTPNotFound(msg)
config_meta = registry.config_interface_metadata(req.context, config_meta)
return config_meta
@utils.mutating
def delete_config(self, req, config_meta):
"""
Deletes a config from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about config
:raises HTTPBadRequest if x-config-name is missing
"""
self._enforce(req, 'delete_config')
try:
for id in eval(config_meta['config']):
registry.delete_config_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find config to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete config: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("config %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('config.delete', config)
return Response(body='', status=200)
@utils.mutating
def get_config(self, req, id):
"""
Returns metadata about an config in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque config identifier
:raises HTTPNotFound if config metadata is not available to user
"""
self._enforce(req, 'get_config')
config_meta = self.get_config_meta_or_404(req, id)
return {'config_meta': config_meta}
def detail(self, req):
"""
Returns detailed information for all available configs
:param req: The WSGI/Webob Request object
:retval The response body is a mapping of the following form::
{'configs': [
{'id': <ID>,
'name': <NAME>,
'description': <DESCRIPTION>,
'created_at': <TIMESTAMP>,
'updated_at': <TIMESTAMP>,
'deleted_at': <TIMESTAMP>|<NONE>,}, ...
]}
"""
self._enforce(req, 'get_configs')
params = self._get_query_params(req)
try:
configs = registry.get_configs_detail(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(configs=configs)
class ConfigDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["config_meta"] = utils.get_config_meta(request)
return result
def add_config(self, request):
return self._deserialize(request)
def delete_config(self, request):
return self._deserialize(request)
class ConfigSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_config(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def delete_config(self, response, result):
config_meta = result['config_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config=config_meta))
return response
def get_config(self, response, result):
config_meta = result['config_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(config=config_meta))
return response
def create_resource():
"""configs resource factory method"""
deserializer = ConfigDeserializer()
serializer = ConfigSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,369 @@
# Copyright 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.
import glance_store as store
from oslo_log import log as logging
import webob.exc
from daisy.common import exception
from daisy import i18n
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
class BaseController(object):
def get_image_meta_or_404(self, request, image_id):
"""
Grabs the image metadata for an image with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param image_id: The opaque image identifier
:raises HTTPNotFound if image does not exist
"""
context = request.context
try:
return registry.get_image_metadata(context, image_id)
except exception.NotFound:
msg = "Image with identifier %s not found" % image_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden image access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_host_meta_or_404(self, request, host_id):
"""
Grabs the host metadata for an host with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param host_id: The opaque host identifier
:raises HTTPNotFound if host does not exist
"""
context = request.context
try:
return registry.get_host_metadata(context, host_id)
except exception.NotFound:
msg = "Host with identifier %s not found" % host_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden host access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_cluster_meta_or_404(self, request, cluster_id):
"""
Grabs the cluster metadata for an cluster with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param cluster_id: The opaque cluster identifier
:raises HTTPNotFound if cluster does not exist
"""
context = request.context
try:
return registry.get_cluster_metadata(context, cluster_id)
except exception.NotFound:
msg = "Cluster with identifier %s not found" % cluster_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden host access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_component_meta_or_404(self, request, component_id):
"""
Grabs the component metadata for an component with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param component_id: The opaque component identifier
:raises HTTPNotFound if component does not exist
"""
context = request.context
try:
return registry.get_component_metadata(context, component_id)
except exception.NotFound:
msg = "Component with identifier %s not found" % component_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden host access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_service_meta_or_404(self, request, service_id):
"""
Grabs the service metadata for an service with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param service_id: The opaque service identifier
:raises HTTPNotFound if service does not exist
"""
context = request.context
try:
return registry.get_service_metadata(context, service_id)
except exception.NotFound:
msg = "Service with identifier %s not found" % service_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden host access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_role_meta_or_404(self, request, role_id):
"""
Grabs the role metadata for an role with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param role_id: The opaque role identifier
:raises HTTPNotFound if role does not exist
"""
context = request.context
try:
return registry.get_role_metadata(context, role_id)
except exception.NotFound:
msg = "Role with identifier %s not found" % role_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden host access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_network_meta_or_404(self, request, network_id):
"""
Grabs the network metadata for an network with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param network_id: The opaque network identifier
:raises HTTPNotFound if network does not exist
"""
context = request.context
try:
return registry.get_network_metadata(context, network_id)
except exception.NotFound:
msg = "Network with identifier %s not found" % network_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden network access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_active_image_meta_or_error(self, request, image_id):
"""
Same as get_image_meta_or_404 except that it will raise a 403 if the
image is deactivated or 404 if the image is otherwise not 'active'.
"""
image = self.get_image_meta_or_404(request, image_id)
if image['status'] == 'deactivated':
msg = "Image %s is deactivated" % image_id
LOG.debug(msg)
msg = _("Image %s is deactivated") % image_id
raise webob.exc.HTTPForbidden(
msg, request=request, content_type='type/plain')
if image['status'] != 'active':
msg = "Image %s is not active" % image_id
LOG.debug(msg)
msg = _("Image %s is not active") % image_id
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
return image
def update_store_acls(self, req, image_id, location_uri, public=False):
if location_uri:
try:
read_tenants = []
write_tenants = []
members = registry.get_image_members(req.context, image_id)
if members:
for member in members:
if member['can_share']:
write_tenants.append(member['member_id'])
else:
read_tenants.append(member['member_id'])
store.set_acls(location_uri, public=public,
read_tenants=read_tenants,
write_tenants=write_tenants,
context=req.context)
except store.UnknownScheme:
msg = _("Store for image_id not found: %s") % image_id
raise webob.exc.HTTPBadRequest(explanation=msg,
request=req,
content_type='text/plain')
def get_config_file_meta_or_404(self, request, config_file_id):
"""
Grabs the config_file metadata for an config_file with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param host_id: The opaque config_file identifier
:raises HTTPNotFound if config_file does not exist
"""
context = request.context
try:
return registry.get_config_file_metadata(context, config_file_id)
except exception.NotFound:
msg = "config_file with identifier %s not found" % config_file_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden config_filke access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_config_set_meta_or_404(self, request, config_set_id):
"""
Grabs the config_set metadata for an config_set with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param host_id: The opaque config_set identifier
:raises HTTPNotFound if config_set does not exist
"""
context = request.context
try:
return registry.get_config_set_metadata(context, config_set_id)
except exception.NotFound:
msg = "config_set with identifier %s not found" % config_set_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden config_set access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_config_meta_or_404(self, request, config_id):
"""
Grabs the config metadata for an config with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param host_id: The opaque config identifier
:raises HTTPNotFound if config does not exist
"""
context = request.context
try:
return registry.get_config_metadata(context, config_id)
except exception.NotFound:
msg = "config with identifier %s not found" % config_id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden config access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_service_disk_meta_or_404(self, request, id):
"""
Grabs the config metadata for an config with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param host_id: The opaque config identifier
:raises HTTPNotFound if config does not exist
"""
context = request.context
try:
return registry.get_service_disk_detail_metadata(context, id)
except exception.NotFound:
msg = "config with identifier %s not found" % id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden config access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')
def get_cinder_volume_meta_or_404(self, request, id):
"""
Grabs the config metadata for an config with a supplied
identifier or raises an HTTPNotFound (404) response
:param request: The WSGI/Webob Request object
:param host_id: The opaque config identifier
:raises HTTPNotFound if config does not exist
"""
context = request.context
try:
return registry.get_cinder_volume_detail_metadata(context, id)
except exception.NotFound:
msg = "config with identifier %s not found" % id
LOG.debug(msg)
raise webob.exc.HTTPNotFound(
msg, request=request, content_type='text/plain')
except exception.Forbidden:
msg = "Forbidden config access"
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg,
request=request,
content_type='text/plain')

View File

@@ -0,0 +1,668 @@
# Copyright 2013 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.
"""
/hosts endpoint for Daisy v1 API
"""
import time
import traceback
import ast
import webob.exc
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPForbidden
from webob.exc import HTTPServerError
from webob import Response
from threading import Thread
from daisy import i18n
from daisy import notifier
from daisy.api import policy
import daisy.api.v1
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
import daisy.registry.client.v1.api as registry
from daisy.api.v1 import controller
from daisy.api.v1 import filters
try:
import simplejson as json
except ImportError:
import json
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
SERVICE_DISK_SERVICE = ('db', 'glance', 'dbbackup', 'mongodb', 'nova')
DISK_LOCATION = ('local', 'share')
CINDER_VOLUME_BACKEND_PARAMS = ('management_ips', 'data_ips','pools',
'volume_driver', 'volume_type',
'role_id', 'user_name','user_pwd')
CINDER_VOLUME_BACKEND_DRIVER = ['KS3200_IPSAN', 'KS3200_FCSAN',
'FUJISTU_ETERNUS']
class Controller(controller.BaseController):
"""
WSGI controller for hosts resource in Daisy v1 API
The hosts resource API is a RESTful web service for host data. The API
is as follows::
GET /hosts -- Returns a set of brief metadata about hosts
GET /hosts/detail -- Returns a set of detailed metadata about
hosts
HEAD /hosts/<ID> -- Return metadata about an host with id <ID>
GET /hosts/<ID> -- Return host data for host with id <ID>
POST /hosts -- Store host data and return metadata about the
newly-stored host
PUT /hosts/<ID> -- Update host metadata and/or upload host
data for a previously-reserved host
DELETE /hosts/<ID> -- Delete the host with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
def _raise_404_if_role_deleted(self, req, role_id):
role = self.get_role_meta_or_404(req, role_id)
if role is None or role['deleted']:
msg = _("role with identifier %s has been deleted.") % role_id
raise HTTPNotFound(msg)
if role['type'] == 'template':
msg = "role type of %s is 'template'" % role_id
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
def _raise_404_if_service_disk_deleted(self, req, service_disk_id):
service_disk = self.get_service_disk_meta_or_404(req, service_disk_id)
if service_disk is None or service_disk['deleted']:
msg = _("service_disk with identifier %s has been deleted.") % service_disk_id
raise HTTPNotFound(msg)
def _default_value_set(self, disk_meta):
if (not disk_meta.has_key('disk_location') or
not disk_meta['disk_location'] or
disk_meta['disk_location'] == ''):
disk_meta['disk_location'] = 'local'
if not disk_meta.has_key('lun'):
disk_meta['lun'] = 0
if not disk_meta.has_key('size'):
disk_meta['size'] = -1
def _unique_service_in_role(self, req, disk_meta):
params = {'filters': {'role_id': disk_meta['role_id']}}
service_disks = registry.list_service_disk_metadata(req.context, **params)
for service_disk in service_disks:
if service_disk['service'] == disk_meta['service']:
msg = "disk service %s has existed in role %s" %(disk_meta['service'], disk_meta['role_id'])
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
def _service_disk_add_meta_valid(self, req, disk_meta):
if not disk_meta.has_key('role_id'):
msg = "'role_id' must be given"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
else:
self._raise_404_if_role_deleted(req,disk_meta['role_id'])
if not disk_meta.has_key('service'):
msg = "'service' must be given"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
else:
if disk_meta['service'] not in SERVICE_DISK_SERVICE:
msg = "service '%s' is not supported" % disk_meta['service']
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_meta['disk_location'] not in DISK_LOCATION:
msg = "disk_location %s is not supported" % disk_meta['disk_location']
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_meta['disk_location'] == 'share' and not disk_meta.has_key('data_ips'):
msg = "'data_ips' must be given when disk_location is share"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_meta['lun'] < 0:
msg = "'lun' should not be less than 0"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
disk_meta['size'] = ast.literal_eval(str(disk_meta['size']))
if not isinstance(disk_meta['size'], int):
msg = "'size' is not integer"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_meta['size'] < -1:
msg = "'size' is invalid"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
self._unique_service_in_role(req, disk_meta)
def _service_disk_update_meta_valid(self, req, id, disk_meta):
orig_disk_meta = self.get_service_disk_meta_or_404(req, id)
if disk_meta.has_key('role_id'):
self._raise_404_if_role_deleted(req,disk_meta['role_id'])
if disk_meta.has_key('service'):
if disk_meta['service'] not in SERVICE_DISK_SERVICE:
msg = "service '%s' is not supported" % disk_meta['service']
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_meta.has_key('disk_location'):
if disk_meta['disk_location'] not in DISK_LOCATION:
msg = "disk_location '%s' is not supported" % disk_meta['disk_location']
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if (disk_meta['disk_location'] == 'share' and
not disk_meta.has_key('data_ips') and
not orig_disk_meta['data_ips']):
msg = "'data_ips' must be given when disk_location is share"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_meta.has_key('size'):
disk_meta['size'] = ast.literal_eval(str(disk_meta['size']))
if not isinstance(disk_meta['size'], int):
msg = "'size' is not integer"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_meta['size'] < -1:
msg = "'size' is invalid"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
@utils.mutating
def service_disk_add(self, req, disk_meta):
"""
Export daisy db data to tecs.conf and HA.conf.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
self._enforce(req, 'service_disk_add')
self._default_value_set(disk_meta)
self._service_disk_add_meta_valid(req, disk_meta)
service_disk_meta = registry.add_service_disk_metadata(req.context, disk_meta)
return {'disk_meta': service_disk_meta}
@utils.mutating
def service_disk_delete(self, req, id):
"""
Deletes a service_disk from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about service_disk
:raises HTTPBadRequest if x-service-disk-name is missing
"""
self._enforce(req, 'delete_service_disk')
try:
registry.delete_service_disk_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find service_disk to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete service_disk: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("service_disk %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
return Response(body='', status=200)
@utils.mutating
def service_disk_update(self, req, id, disk_meta):
self._enforce(req, 'service_disk_update')
self._service_disk_update_meta_valid(req, id, disk_meta)
try:
service_disk_meta = registry.update_service_disk_metadata(req.context,
id,
disk_meta)
except exception.Invalid as e:
msg = (_("Failed to update role metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find role to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update role: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('Host operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('role.update', service_disk_meta)
return {'disk_meta': service_disk_meta}
@utils.mutating
def service_disk_detail(self, req, id):
"""
Returns metadata about an role in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque role identifier
:raises HTTPNotFound if role metadata is not available to user
"""
self._enforce(req, 'service_disk_detail')
service_disk_meta = self.get_service_disk_meta_or_404(req, id)
return {'disk_meta': service_disk_meta}
def service_disk_list(self, req):
self._enforce(req, 'service_disk_list')
params = self._get_query_params(req)
filters=params.get('filters',None)
if 'role_id' in filters:
role_id=filters['role_id']
self._raise_404_if_role_deleted(req, role_id)
try:
service_disks = registry.list_service_disk_metadata(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(disk_meta=service_disks)
def _cinder_volume_list(self, req, params):
try:
cinder_volumes = registry.list_cinder_volume_metadata(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return cinder_volumes
def _is_cinder_volume_repeat(self, req, array_disk_info, update_id = None):
cinder_volume_id = None
params = {'filters': {}}
if update_id:
cinder_volume_metal = self.get_cinder_volume_meta_or_404(req, update_id)
new_management_ips = array_disk_info.get('management_ips', cinder_volume_metal['management_ips']).split(",")
new_pools = array_disk_info.get('pools', cinder_volume_metal['pools']).split(",")
else:
new_management_ips = array_disk_info['management_ips'].split(",")
new_pools = array_disk_info['pools'].split(",")
org_cinder_volumes = self._cinder_volume_list(req, params)
for cinder_volume in org_cinder_volumes:
if (set(cinder_volume['management_ips'].split(",")) == set(new_management_ips) and
set(cinder_volume['pools'].split(",")) == set(new_pools)):
if cinder_volume['id'] != update_id:
msg = 'cinder_volume array disks conflict with cinder_volume %s' % cinder_volume['id']
raise HTTPBadRequest(explanation=msg, request=req)
def _get_cinder_volume_backend_index(self, req, disk_array):
params = {'filters': {}}
cinder_volumes = self._cinder_volume_list(req, params)
index = 1
while True:
backend_index = "%s-%s" %(disk_array['volume_driver'], index)
flag = True
for cinder_volume in cinder_volumes:
if backend_index == cinder_volume['backend_index']:
index=index+1
flag = False
break
if flag:
break
return backend_index
@utils.mutating
def cinder_volume_add(self, req, disk_meta):
"""
Export daisy db data to tecs.conf and HA.conf.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
self._enforce(req, 'cinder_volume_add')
if not disk_meta.has_key('role_id'):
msg = "'role_id' must be given"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
else:
self._raise_404_if_role_deleted(req,disk_meta['role_id'])
disk_arrays = eval(disk_meta['disk_array'])
for disk_array in disk_arrays:
for key in disk_array.keys():
if (key not in CINDER_VOLUME_BACKEND_PARAMS and
key != 'data_ips'):
msg = "'%s' must be given for cinder volume config" % key
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_array['volume_driver'] not in CINDER_VOLUME_BACKEND_DRIVER:
msg = "volume_driver %s is not supported" % disk_array['volume_driver']
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if (disk_array['volume_driver'] == 'FUJISTU_ETERNUS' and
(not disk_array.has_key('data_ips') or
not disk_array['data_ips'])):
msg = "data_ips must be given when using FUJISTU Disk Array"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
self._is_cinder_volume_repeat(req, disk_array)
disk_array['role_id'] = disk_meta['role_id']
disk_array['backend_index'] = self._get_cinder_volume_backend_index(req, disk_array)
cinder_volumes = registry.add_cinder_volume_metadata(req.context, disk_array)
return {'disk_meta': cinder_volumes}
@utils.mutating
def cinder_volume_delete(self, req, id):
"""
Deletes a service_disk from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about service_disk
:raises HTTPBadRequest if x-service-disk-name is missing
"""
self._enforce(req, 'delete_cinder_volume')
try:
registry.delete_cinder_volume_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find cinder volume to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete cinder volume: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("cindre volume %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
return Response(body='', status=200)
def _is_data_ips_valid(self, req, update_id, update_meta):
orgin_cinder_volume = self.get_cinder_volume_meta_or_404(req, update_id)
new_driver = update_meta.get('volume_driver',
orgin_cinder_volume['volume_driver'])
if new_driver != 'FUJISTU_ETERNUS':
return
new_data_ips = update_meta.get('data_ips',
orgin_cinder_volume['data_ips'])
if not new_data_ips:
msg = "data_ips must be given when using FUJISTU Disk Array"
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
@utils.mutating
def cinder_volume_update(self, req, id, disk_meta):
for key in disk_meta.keys():
if key not in CINDER_VOLUME_BACKEND_PARAMS:
msg = "'%s' must be given for cinder volume config" % key
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
if disk_meta.has_key('role_id'):
self._raise_404_if_role_deleted(req,disk_meta['role_id'])
if (disk_meta.has_key('volume_driver') and
disk_meta['volume_driver'] not in CINDER_VOLUME_BACKEND_DRIVER):
msg = "volume_driver %s is not supported" % disk_meta['volume_driver']
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
self._is_cinder_volume_repeat(req, disk_meta, id)
self._is_data_ips_valid(req, id, disk_meta)
try:
cinder_volume_meta = registry.update_cinder_volume_metadata(req.context,
id,
disk_meta)
except exception.Invalid as e:
msg = (_("Failed to update cinder_volume metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find cinder_volume to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update cinder_volume: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('cinder_volume operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('cinder_volume.update', cinder_volume_meta)
return {'disk_meta': cinder_volume_meta}
@utils.mutating
def cinder_volume_detail(self, req, id):
"""
Returns metadata about an role in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque role identifier
:raises HTTPNotFound if role metadata is not available to user
"""
self._enforce(req, 'cinder_volume_detail')
cinder_volume_meta = self.get_cinder_volume_meta_or_404(req, id)
return {'disk_meta': cinder_volume_meta}
def cinder_volume_list(self, req):
self._enforce(req, 'cinder_volume_list')
params = self._get_query_params(req)
filters=params.get('filters',None)
if 'role_id' in filters:
role_id=filters['role_id']
self._raise_404_if_role_deleted(req, role_id)
cinder_volumes = self._cinder_volume_list(req, params)
return dict(disk_meta=cinder_volumes)
class DiskArrayDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["disk_meta"] = utils.get_dict_meta(request)
return result
def service_disk_add(self, request):
return self._deserialize(request)
def service_disk_update(self, request):
return self._deserialize(request)
def cinder_volume_add(self, request):
return self._deserialize(request)
def cinder_volume_update(self, request):
return self._deserialize(request)
class DiskArraySerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def service_disk_add(self, response, result):
disk_meta = result['disk_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def service_disk_update(self, response, result):
disk_meta = result['disk_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def cinder_volume_add(self, response, result):
disk_meta = result['disk_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def cinder_volume_update(self, response, result):
disk_meta = result['disk_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def create_resource():
"""Image members resource factory method"""
deserializer = DiskArrayDeserializer()
serializer = DiskArraySerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,40 @@
# Copyright 2012, Piston Cloud Computing, 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.
def validate(filter, value):
return FILTER_FUNCTIONS.get(filter, lambda v: True)(value)
def validate_int_in_range(min=0, max=None):
def _validator(v):
try:
if max is None:
return min <= int(v)
return min <= int(v) <= max
except ValueError:
return False
return _validator
def validate_boolean(v):
return v.lower() in ('none', 'true', 'false', '1', '0')
FILTER_FUNCTIONS = {'size_max': validate_int_in_range(), # build validator
'size_min': validate_int_in_range(), # build validator
'min_ram': validate_int_in_range(), # build validator
'protected': validate_boolean,
'is_public': validate_boolean, }

View File

@@ -0,0 +1,569 @@
# Copyright 2013 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.
"""
/host_Templates endpoint for Daisy v1 API
"""
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
import copy
import json
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
from daisy.registry.api.v1 import template
import daisy.api.backends.tecs.common as tecs_cmn
import daisy.api.backends.common as daisy_cmn
try:
import simplejson as json
except ImportError:
import json
daisy_tecs_path = tecs_cmn.daisy_tecs_path
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = template.SUPPORTED_PARAMS
SUPPORTED_FILTERS = template.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
class Controller(controller.BaseController):
"""
WSGI controller for Templates resource in Daisy v1 API
The HostTemplates resource API is a RESTful web Template for Template data. The API
is as follows::
GET /HostTemplates -- Returns a set of brief metadata about Templates
GET /HostTemplates/detail -- Returns a set of detailed metadata about
HostTemplates
HEAD /HostTemplates/<ID> -- Return metadata about an Template with id <ID>
GET /HostTemplates/<ID> -- Return Template data for Template with id <ID>
POST /HostTemplates -- Store Template data and return metadata about the
newly-stored Template
PUT /HostTemplates/<ID> -- Update Template metadata and/or upload Template
data for a previously-reserved Template
DELETE /HostTemplates/<ID> -- Delete the Template with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
def _raise_404_if_cluster_deleted(self, req, cluster_id):
cluster = self.get_cluster_meta_or_404(req, cluster_id)
if cluster['deleted']:
msg = _("Cluster with identifier %s has been deleted.") % cluster_id
raise webob.exc.HTTPNotFound(msg)
@utils.mutating
def add_template(self, req, host_template):
"""
Adds a new cluster template to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about Template
:raises HTTPBadRequest if x-Template-name is missing
"""
self._enforce(req, 'add_host_template')
template_name = host_template["name"]
host_template = registry.add_host_template_metadata(req.context, host_template)
return {'host_template': template}
@utils.mutating
def update_host_template(self, req, template_id, host_template):
"""
Updates an existing Template with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'update_host_template')
#orig_Template_meta = self.get_Template_meta_or_404(req, id)
'''
if orig_Template_meta['deleted']:
msg = _("Forbidden to update deleted Template.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
'''
try:
host_template = registry.update_host_template_metadata(req.context,
template_id,
host_template)
except exception.Invalid as e:
msg = (_("Failed to update template metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find host_template to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update host_template: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('host_template operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('host_template.update', host_template)
return {'host_template': host_template}
def _filter_params(self, host_meta):
for key in host_meta.keys():
if key=="id" or key=="updated_at" or key=="deleted_at" or key=="created_at" or key=="deleted":
del host_meta[key]
if host_meta.has_key("memory"):
del host_meta['memory']
if host_meta.has_key("system"):
del host_meta['system']
if host_meta.has_key("disks"):
del host_meta['disks']
if host_meta.has_key("os_status"):
del host_meta['os_status']
if host_meta.has_key("status"):
del host_meta['status']
if host_meta.has_key("messages"):
del host_meta['messages']
if host_meta.has_key("cpu"):
del host_meta['cpu']
if host_meta.has_key("ipmi_addr"):
del host_meta['ipmi_addr']
if host_meta.has_key("interfaces"):
for interface in host_meta['interfaces']:
for key in interface.keys():
if key=="id" or key=="updated_at" or key=="deleted_at" \
or key=="created_at" or key=="deleted" or key=="current_speed" \
or key=="max_speed" or key=="host_id" or key=="state":
del interface[key]
for assigned_network in interface['assigned_networks']:
if assigned_network.has_key("ip"):
assigned_network['ip'] = ""
return host_meta
@utils.mutating
def get_host_template_detail(self, req, template_id):
"""
delete a existing cluster template with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'get_host_template_detail')
try:
host_template = registry.host_template_detail_metadata(req.context, template_id)
return {'host_template': host_template}
except exception.NotFound as e:
msg = (_("Failed to find host template: %s") %
utils.exception_to_str(e))
LOG.error(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to get host template: %s") %
utils.exception_to_str(e))
LOG.error(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("host template %(id)s could not be get because it is in use: "
"%(exc)s") % {"id": template_id, "exc": utils.exception_to_str(e)})
LOG.error(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('host.delete', host)
return Response(body='', status=200)
@utils.mutating
def get_host_template_lists(self, req):
self._enforce(req, 'get_template_lists')
params = self._get_query_params(req)
template_meta = {}
try:
host_template_lists = registry.host_template_lists_metadata(req.context, **params)
if host_template_lists and host_template_lists[0]:
template_meta = json.loads(host_template_lists[0]['hosts'])
return {'host_template': template_meta}
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(host_template=host_template_lists)
@utils.mutating
def host_to_template(self, req, host_template):
"""
host to Template.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-Template-cluster is missing
"""
self._enforce(req, 'host_to_template')
if host_template.get('host_id', None):
origin_host_meta = self.get_host_meta_or_404(req, host_template['host_id'])
host_meta = self._filter_params(origin_host_meta)
if host_template.get('host_template_name', None) and host_template.get('cluster_name', None):
host_meta['name'] = host_template['host_template_name']
host_meta['description'] = host_template.get('description', None)
params = {'filters':{'cluster_name':host_template['cluster_name']}}
templates = registry.host_template_lists_metadata(req.context, **params)
if templates and templates[0]:
had_host_template = False
if templates[0]['hosts']:
templates[0]['hosts'] = json.loads(templates[0]['hosts'])
else:
templates[0]['hosts'] = []
for index in range(len(templates[0]['hosts'])):
if host_template['host_template_name'] == templates[0]['hosts'][index]['name']:
had_host_template = True
templates[0]['hosts'][index] = host_meta
break
if not had_host_template:
host_meta['name'] = host_template['host_template_name']
templates[0]['hosts'].append(host_meta)
templates[0]['hosts'] = json.dumps(templates[0]['hosts'])
host_template = registry.update_host_template_metadata(req.context,
templates[0]['id'],
templates[0])
else:
param = {"cluster_name": host_template['cluster_name'], "hosts":json.dumps([host_meta])}
host_template = registry.add_host_template_metadata(req.context, param)
return {'host_template': host_template}
@utils.mutating
def template_to_host(self, req, host_template):
if not host_template.get('cluster_name', None):
msg = "cluster name is null"
raise HTTPNotFound(explanation=msg)
params = {'filters':{'cluster_name':host_template['cluster_name']}}
templates = registry.host_template_lists_metadata(req.context, **params)
hosts_param = []
host_template_used = {}
if templates and templates[0]:
hosts_param = json.loads(templates[0]['hosts'])
for host in hosts_param:
if host['name'] == host_template['host_template_name']:
host_template_used = host
break
if not host_template_used:
msg = "not host_template %s" % host_template['host_template_name']
raise HTTPNotFound(explanation=msg, request=req, content_type="text/plain")
if host_template.get('host_id', None):
self.get_host_meta_or_404(req, host_template['host_id'])
else:
msg="host_id is not null"
raise HTTPBadRequest(explanation = msg)
host_id = host_template['host_id']
params = {'filters':{'name': host_template['cluster_name']}}
clusters = registry.get_clusters_detail(req.context, **params)
if clusters and clusters[0]:
host_template_used['cluster'] = clusters[0]['id']
if host_template_used.has_key('role') and host_template_used['role']:
role_id_list = []
host_role_list = []
if host_template_used.has_key('cluster'):
params = self._get_query_params(req)
role_list = registry.get_roles_detail(req.context, **params)
for role_name in role_list:
if role_name['cluster_id'] == host_template_used['cluster']:
host_role_list = list(host_template_used['role'])
if role_name['name'] in host_role_list:
role_id_list.append(role_name['id'])
host_template_used['role'] = role_id_list
if host_template_used.has_key('name'):
host_template_used.pop('name')
if host_template_used.has_key('dmi_uuid'):
host_template_used.pop('dmi_uuid')
if host_template_used.has_key('ipmi_user'):
host_template_used.pop('ipmi_user')
if host_template_used.has_key('ipmi_passwd'):
host_template_used.pop('ipmi_passwd')
if host_template_used.has_key('ipmi_addr'):
host_template_used.pop('ipmi_addr')
host_template_interfaces = host_template_used.get('interfaces', None)
if host_template_interfaces:
template_ether_interface = [interface for interface in host_template_interfaces if interface['type'] == "ether" ]
orig_host_meta = registry.get_host_metadata(req.context, host_id)
orig_host_interfaces = orig_host_meta.get('interfaces', None)
temp_orig_host_interfaces = [ interface for interface in orig_host_interfaces if interface['type'] == "ether" ]
if len(temp_orig_host_interfaces) != len(template_ether_interface):
msg = (_('host_id %s does not match the host_id host_template '
'%s.') % (host_id, host_template['host_template_name']))
raise HTTPBadRequest(explanation = msg)
interface_match_flag = 0
for host_template_interface in host_template_interfaces:
if host_template_interface['type'] == 'ether':
for orig_host_interface in orig_host_interfaces:
if orig_host_interface['pci'] == host_template_interface['pci']:
interface_match_flag += 1
host_template_interface['mac'] = orig_host_interface['mac']
if host_template_interface.has_key('ip'):
host_template_interface.pop('ip')
if interface_match_flag != len(template_ether_interface):
msg = (_('host_id %s does not match the host '
'host_template %s.') % (host_id, host_template['host_template_name']))
raise HTTPBadRequest(explanation=msg)
host_template_used['interfaces'] = str(host_template_interfaces)
host_template = registry.update_host_metadata(req.context, host_id, host_template_used)
return {"host_template": host_template}
@utils.mutating
def delete_host_template(self, req, host_template):
"""
delete a existing host template with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'delete_host_template')
try:
if not host_template.get('cluster_name', None):
msg = "cluster name is null"
raise HTTPNotFound(explanation=msg)
params = {'filters':{'cluster_name':host_template['cluster_name']}}
host_templates = registry.host_template_lists_metadata(req.context, **params)
template_param = []
had_host_template = False
if host_templates and host_templates[0]:
template_param = json.loads(host_templates[0]['hosts'])
for host in template_param:
if host['name'] == host_template['host_template_name']:
template_param.remove(host)
had_host_template = True
break
if not had_host_template:
msg = "not host template name %s" %host_template['host_template_name']
raise HTTPNotFound(explanation=msg)
else:
host_templates[0]['hosts'] = json.dumps(template_param)
host_template = registry.update_host_template_metadata(req.context,
host_templates[0]['id'],
host_templates[0])
return {"host_template": host_template}
else:
msg = "host template cluster name %s is null" %host_template['cluster_name']
raise HTTPNotFound(explanation=msg)
except exception.NotFound as e:
msg = (_("Failed to find host template to delete: %s") %
utils.exception_to_str(e))
LOG.error(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete template: %s") %
utils.exception_to_str(e))
LOG.error(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("template %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": template_id, "exc": utils.exception_to_str(e)})
LOG.error(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
return Response(body='', status=200)
class HostTemplateDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["host_template"] = utils.get_template_meta(request)
return result
def add_host_template(self, request):
return self._deserialize(request)
def update_host_template(self, request):
return self._deserialize(request)
def host_to_template(self, request):
return self._deserialize(request)
def template_to_host(self, request):
return self._deserialize(request)
def delete_host_template(self, request):
return self._deserialize(request)
class HostTemplateSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_host_template(self, response, result):
host_template = result['host_template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(host_template=host_template))
return response
def delete_host_template(self, response, result):
host_template = result['host_template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(host_template=host_template))
return response
def get_host_template_detail(self, response, result):
host_template = result['host_template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(host_template=host_template))
return response
def update_host_template(self, response, result):
host_template = result['host_template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(host_template=host_template))
return response
def host_to_template(self, response, result):
host_template = result['host_template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(host_template=host_template))
return response
def template_to_host(self, response, result):
host_template = result['host_template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(host_template=host_template))
return response
def get_host_template_lists(self, response, result):
host_template = result['host_template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(host_template=host_template))
def create_resource():
"""Templates resource factory method"""
deserializer = HostTemplateDeserializer()
serializer = HostTemplateSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

1728
code/daisy/daisy/api/v1/hosts.py Executable file

File diff suppressed because it is too large Load Diff

1264
code/daisy/daisy/api/v1/images.py Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,405 @@
# Copyright 2013 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.
"""
/hosts endpoint for Daisy v1 API
"""
import time
import traceback
import webob.exc
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPForbidden
from webob.exc import HTTPServerError
from threading import Thread
from daisy import i18n
from daisy import notifier
from daisy.api import policy
import daisy.api.v1
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
import daisy.registry.client.v1.api as registry
from daisy.api.v1 import controller
from daisy.api.v1 import filters
import daisy.api.backends.common as daisy_cmn
from daisy.api.backends import driver
from daisy.api.backends import os as os_handle
try:
import simplejson as json
except ImportError:
import json
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
# if some backends have order constraint, please add here
# if backend not in the next three order list, we will be
# think it does't have order constraint.
BACKENDS_INSTALL_ORDER = ['proton', 'zenic', 'tecs']
BACKENDS_UPGRADE_ORDER = ['proton', 'zenic', 'tecs']
BACKENDS_UNINSTALL_ORDER = []
def get_deployment_backends(req, cluster_id, backends_order):
cluster_roles = daisy_cmn.get_cluster_roles_detail(req,cluster_id)
cluster_backends = set([role['deployment_backend'] for role in cluster_roles if daisy_cmn.get_hosts_of_role(req, role['id'])])
ordered_backends = [backend for backend in backends_order if backend in cluster_backends]
other_backends = [backend for backend in cluster_backends if backend not in backends_order]
deployment_backends =ordered_backends + other_backends
return deployment_backends
class InstallTask(object):
"""
Class for install OS and TECS.
"""
""" Definition for install states."""
def __init__(self, req, cluster_id):
self.req = req
self.cluster_id = cluster_id
def _backends_install(self):
backends = get_deployment_backends(self.req, self.cluster_id, BACKENDS_INSTALL_ORDER)
if not backends:
LOG.info(_("No backends need to install."))
return
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
backend_driver.install(self.req, self.cluster_id)
# this will be raise raise all the exceptions of the thread to log file
def run(self):
try:
self._run()
except Exception as e:
LOG.exception(e.message)
def _run(self):
"""
Exectue os installation with sync mode.
:return:
"""
# get hosts config which need to install OS
all_hosts_need_os = os_handle.get_cluster_hosts_config(self.req, self.cluster_id)
if all_hosts_need_os:
hosts_with_role_need_os = [host_detail for host_detail in all_hosts_need_os if host_detail['status'] == 'with-role']
hosts_without_role_need_os = [host_detail for host_detail in all_hosts_need_os if host_detail['status'] != 'with-role']
else:
LOG.info(_("No host need to install os, begin to install "
"backends for cluster %s." % self.cluster_id))
self._backends_install()
return
run_once_flag = True
# if no hosts with role need os, install backend applications immediately
if not hosts_with_role_need_os:
run_once_flag = False
role_hosts_need_os = []
LOG.info(_("All of hosts with role is 'active', begin to install "
"backend applications for cluster %s first." % self.cluster_id))
self._backends_install()
else:
role_hosts_need_os = [host_detail['id'] for host_detail in hosts_with_role_need_os]
# hosts with role put the head of the list
order_hosts_need_os = hosts_with_role_need_os + hosts_without_role_need_os
while order_hosts_need_os:
os_install = os_handle.OSInstall(self.req, self.cluster_id)
#all os will be installed batch by batch with max_parallel_os_number which was set in daisy-api.conf
(order_hosts_need_os,role_hosts_need_os) = os_install.install_os(order_hosts_need_os,role_hosts_need_os)
# after a batch of os install over, judge if all role hosts install os completely,
# if role_hosts_need_os is empty, install TECS immediately
if run_once_flag and not role_hosts_need_os:
run_once_flag = False
#wait to reboot os after new os installed
time.sleep(10)
LOG.info(_("All hosts with role install successfully, "
"begin to install backend applications for cluster %s." % self.cluster_id))
self._backends_install()
class Controller(controller.BaseController):
"""
WSGI controller for hosts resource in Daisy v1 API
The hosts resource API is a RESTful web service for host data. The API
is as follows::
GET /hosts -- Returns a set of brief metadata about hosts
GET /hosts/detail -- Returns a set of detailed metadata about
hosts
HEAD /hosts/<ID> -- Return metadata about an host with id <ID>
GET /hosts/<ID> -- Return host data for host with id <ID>
POST /hosts -- Store host data and return metadata about the
newly-stored host
PUT /hosts/<ID> -- Update host metadata and/or upload host
data for a previously-reserved host
DELETE /hosts/<ID> -- Delete the host with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
def _raise_404_if_cluster_deleted(self, req, cluster_id):
cluster = self.get_cluster_meta_or_404(req, cluster_id)
if cluster['deleted']:
msg = _("Cluster with identifier %s has been deleted.") % cluster_id
raise webob.exc.HTTPNotFound(msg)
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
@utils.mutating
def install_cluster(self, req, install_meta):
"""
Install TECS to a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
cluster_id = install_meta['cluster_id']
self._enforce(req, 'install_cluster')
self._raise_404_if_cluster_deleted(req, cluster_id)
if install_meta.get("deployment_interface", None):
os_handle.pxe_server_build(req, install_meta)
return {"status": "pxe is installed"}
# if have hosts need to install os, TECS installataion executed in InstallTask
os_install_obj = InstallTask(req, cluster_id)
os_install_thread = Thread(target=os_install_obj.run)
os_install_thread.start()
return {"status":"begin install"}
@utils.mutating
def uninstall_cluster(self, req, cluster_id):
"""
Uninstall TECS to a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
self._enforce(req, 'uninstall_cluster')
self._raise_404_if_cluster_deleted(req, cluster_id)
backends = get_deployment_backends(req, cluster_id, BACKENDS_UNINSTALL_ORDER)
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
uninstall_thread = Thread(target=backend_driver.uninstall, args=(req, cluster_id))
uninstall_thread.start()
return {"status":"begin uninstall"}
@utils.mutating
def uninstall_progress(self, req, cluster_id):
self._enforce(req, 'uninstall_progress')
self._raise_404_if_cluster_deleted(req, cluster_id)
all_nodes = {}
backends = get_deployment_backends(req, cluster_id, BACKENDS_UNINSTALL_ORDER)
if not backends:
LOG.info(_("No backends need to uninstall."))
return all_nodes
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
nodes_process = backend_driver.uninstall_progress(req, cluster_id)
all_nodes.update(nodes_process)
return all_nodes
@utils.mutating
def update_cluster(self, req, cluster_id):
"""
Uninstall TECS to a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
self._enforce(req, 'update_cluster')
self._raise_404_if_cluster_deleted(req, cluster_id)
backends = get_deployment_backends(req, cluster_id, BACKENDS_UPGRADE_ORDER)
if not backends:
LOG.info(_("No backends need to update."))
return {"status":""}
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
update_thread = Thread(target=backend_driver.upgrade, args=(req, cluster_id))
update_thread.start()
return {"status":"begin update"}
@utils.mutating
def update_progress(self, req, cluster_id):
self._enforce(req, 'update_progress')
self._raise_404_if_cluster_deleted(req, cluster_id)
backends = get_deployment_backends(req, cluster_id, BACKENDS_UPGRADE_ORDER)
all_nodes = {}
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
nodes_process = backend_driver.upgrade_progress(req, cluster_id)
all_nodes.update(nodes_process)
return all_nodes
@utils.mutating
def export_db(self, req, install_meta):
"""
Export daisy db data to tecs.conf and HA.conf.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
self._enforce(req, 'export_db')
cluster_id = install_meta['cluster_id']
self._raise_404_if_cluster_deleted(req, cluster_id)
all_config_files = {}
backends = get_deployment_backends(req, cluster_id, BACKENDS_INSTALL_ORDER)
if not backends:
LOG.info(_("No backends need to export."))
return all_config_files
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
backend_config_files = backend_driver.export_db(req, cluster_id)
all_config_files.update(backend_config_files)
return all_config_files
@utils.mutating
def update_disk_array(self, req, cluster_id):
"""
update TECS Disk Array config for a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-cluster is missing
"""
self._enforce(req, 'update_disk_array')
self._raise_404_if_cluster_deleted(req, cluster_id)
tecs_backend_name = 'tecs'
backends = get_deployment_backends(req, cluster_id, BACKENDS_UNINSTALL_ORDER)
if tecs_backend_name not in backends:
message = "No tecs backend"
LOG.info(_(message))
else:
backend_driver = driver.load_deployment_dirver(tecs_backend_name)
message = backend_driver.update_disk_array(req, cluster_id)
return {'status':message}
class InstallDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["install_meta"] = utils.get_dict_meta(request)
return result
def install_cluster(self, request):
return self._deserialize(request)
def export_db(self, request):
return self._deserialize(request)
def update_disk_array(self, request):
return {}
class InstallSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def install_cluster(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def export_db(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def update_disk_array(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def create_resource():
"""Image members resource factory method"""
deserializer = InstallDeserializer()
serializer = InstallSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,278 @@
# Copyright 2012 OpenStack Foundation.
# Copyright 2013 NTT corp.
# 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 oslo_log import log as logging
import webob.exc
from daisy.api import policy
from daisy.api.v1 import controller
from daisy.common import exception
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
CONF = cfg.CONF
CONF.import_opt('image_member_quota', 'daisy.common.config')
class Controller(controller.BaseController):
def __init__(self):
self.policy = policy.Enforcer()
def _enforce(self, req, action):
"""Authorize an action against our policies"""
try:
self.policy.enforce(req.context, action, {})
except exception.Forbidden:
raise webob.exc.HTTPForbidden()
def _raise_404_if_host_deleted(self, req, host_id):
host = self.get_host_meta_or_404(req, host_id)
if host['deleted']:
msg = _("Host with identifier %s has been deleted.") % host_id
raise webob.exc.HTTPNotFound(msg)
def _raise_404_if_project_deleted(self, req, cluster_id):
project = self.get_cluster_meta_or_404(req, cluster_id)
if project['deleted']:
msg = _("Cluster with identifier %s has been deleted.") % cluster_id
raise webob.exc.HTTPNotFound(msg)
# def get_cluster_hosts(self, req, cluster_id, host_id=None):
# """
# Return a list of dictionaries indicating the members of the
# image, i.e., those tenants the image is shared with.
#
# :param req: the Request object coming from the wsgi layer
# :param image_id: The opaque image identifier
# :retval The response body is a mapping of the following form::
# {'members': [
# {'host_id': <HOST>, ...}, ...
# ]}
# """
# self._enforce(req, 'get_cluster_hosts')
# self._raise_404_if_project_deleted(req, cluster_id)
#
# try:
# members = registry.get_cluster_hosts(req.context, cluster_id, host_id)
# except exception.NotFound:
# msg = _("Project with identifier %s not found") % cluster_id
# LOG.warn(msg)
# raise webob.exc.HTTPNotFound(msg)
# except exception.Forbidden:
# msg = _("Unauthorized project access")
# LOG.warn(msg)
# raise webob.exc.HTTPForbidden(msg)
# return dict(members=members)
@utils.mutating
def delete(self, req, image_id, id):
"""
Removes a membership from the image.
"""
self._check_can_access_image_members(req.context)
self._enforce(req, 'delete_member')
self._raise_404_if_image_deleted(req, image_id)
try:
registry.delete_member(req.context, image_id, id)
self._update_store_acls(req, image_id)
except exception.NotFound as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
return webob.exc.HTTPNoContent()
@utils.mutating
def add_cluster_host(self, req, cluster_id, host_id, body=None):
"""
Adds a host with host_id to project with cluster_id.
"""
self._enforce(req, 'add_cluster_host')
self._raise_404_if_project_deleted(req, cluster_id)
self._raise_404_if_host_deleted(req, host_id)
try:
registry.add_cluster_host(req.context, cluster_id, host_id)
except exception.Invalid as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.NotFound as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
return webob.exc.HTTPNoContent()
@utils.mutating
def delete_cluster_host(self, req, cluster_id, host_id):
"""
Delete a host with host_id from project with cluster_id.
"""
self._enforce(req, 'delete_cluster_host')
self._raise_404_if_project_deleted(req, cluster_id)
self._raise_404_if_host_deleted(req, host_id)
try:
registry.delete_cluster_host(req.context, cluster_id, host_id)
except exception.NotFound as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
return webob.exc.HTTPNoContent()
def default(self, req, image_id, id, body=None):
"""This will cover the missing 'show' and 'create' actions"""
raise webob.exc.HTTPMethodNotAllowed()
def _enforce_image_member_quota(self, req, attempted):
if CONF.image_member_quota < 0:
# If value is negative, allow unlimited number of members
return
maximum = CONF.image_member_quota
if attempted > maximum:
msg = _("The limit has been exceeded on the number of allowed "
"image members for this image. Attempted: %(attempted)s, "
"Maximum: %(maximum)s") % {'attempted': attempted,
'maximum': maximum}
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
request=req)
@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)
self._enforce(req, 'modify_member')
self._raise_404_if_image_deleted(req, image_id)
new_number_of_members = len(registry.get_image_members(req.context,
image_id)) + 1
self._enforce_image_member_quota(req, new_number_of_members)
# Figure out can_share
can_share = None
if body and 'member' in body and 'can_share' in body['member']:
can_share = bool(body['member']['can_share'])
try:
registry.add_member(req.context, image_id, id, can_share)
self._update_store_acls(req, image_id)
except exception.Invalid as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.NotFound as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
return webob.exc.HTTPNoContent()
@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)
self._enforce(req, 'modify_member')
self._raise_404_if_image_deleted(req, image_id)
memberships = body.get('memberships')
if memberships:
new_number_of_members = len(body['memberships'])
self._enforce_image_member_quota(req, new_number_of_members)
try:
registry.replace_members(req.context, image_id, body)
self._update_store_acls(req, image_id)
except exception.Invalid as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.NotFound as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
return webob.exc.HTTPNoContent()
def get_host_projects(self, req, host_id):
"""
Retrieves list of image memberships for the given member.
:param req: the Request object coming from the wsgi layer
:param id: the opaque member identifier
:retval The response body is a mapping of the following form::
{'multi_projects': [
{'cluster_id': <PROJECT>, ...}, ...
]}
"""
try:
members = registry.get_host_projects(req.context, host_id)
except exception.NotFound as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
LOG.debug(utils.exception_to_str(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
return dict(multi_projects=members)
def _update_store_acls(self, req, image_id):
image_meta = self.get_image_meta_or_404(req, image_id)
location_uri = image_meta.get('location')
public = image_meta.get('is_public')
self.update_store_acls(req, image_id, location_uri, public)
def create_resource():
"""Image members resource factory method"""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,691 @@
# Copyright 2013 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.
"""
/hosts endpoint for Daisy v1 API
"""
import copy
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
SUPPORT_NETWORK_TYPE = ('PUBLIC', 'PRIVATE', 'STORAGE', 'MANAGEMENT', 'EXTERNAL', 'DEPLOYMENT', 'VXLAN')
SUPPORT_NETWORK_TEMPLATE_TYPE = ('custom', 'template', 'default')
SUPPORT_ML2_TYPE = ('ovs', 'sriov(direct)', 'sriov(macvtap)',
'ovs,sriov(direct)', 'ovs,sriov(macvtap)')
SUPPORT_NETWORK_CAPABILITY = ('high', 'low')
class Controller(controller.BaseController):
"""
WSGI controller for networks resource in Daisy v1 API
The networks resource API is a RESTful web service for host data. The API
is as follows::
GET /networks -- Returns a set of brief metadata about networks
GET /networks/detail -- Returns a set of detailed metadata about
networks
HEAD /networks/<ID> -- Return metadata about an host with id <ID>
GET /networks/<ID> -- Return host data for host with id <ID>
POST /networks -- Store host data and return metadata about the
newly-stored host
PUT /networks/<ID> -- Update host metadata and/or upload host
data for a previously-reserved host
DELETE /networks/<ID> -- Delete the host with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
def _raise_404_if_network_deleted(self, req, network_id):
network = self.get_network_meta_or_404(req, network_id)
if network['deleted']:
msg = _("Network with identifier %s has been deleted.") % network_id
raise HTTPNotFound(msg)
def _raise_404_if_cluster_delete(self, req, cluster_id):
cluster_id = self.get_cluster_meta_or_404(req, cluster_id)
if cluster_id['deleted']:
msg = _("cluster_id with identifier %s has been deleted.") % cluster_id
raise HTTPNotFound(msg)
def _get_network_name_by_cluster_id(self, context, cluster_id):
networks = registry.get_networks_detail(context, cluster_id)
network_name_list = []
for network in networks:
network_name_list.append(network['name'])
return network_name_list
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
def validate_ip_format(self, ip_str):
'''
valid ip_str format = '10.43.178.9'
invalid ip_str format : '123. 233.42.12', spaces existed in field
'3234.23.453.353', out of range
'-2.23.24.234', negative number in field
'1.2.3.4d', letter in field
'10.43.1789', invalid format
'''
valid_fromat = False
if ip_str.count('.') == 3 and \
all(num.isdigit() and 0<=int(num)<256 for num in ip_str.rstrip().split('.')):
valid_fromat = True
if valid_fromat == False:
msg = (_("%s invalid ip format!") % ip_str)
LOG.warn(msg)
raise HTTPForbidden(msg)
def _ip_into_int(self, ip):
"""
Switch ip string to decimalism integer..
:param ip: ip string
:return: decimalism integer
"""
return reduce(lambda x, y: (x<<8)+y, map(int, ip.split('.')))
def _is_in_network_range(self, ip, network):
"""
Check ip is in range
:param ip: Ip will be checked, like:192.168.1.2.
:param network: Ip range,like:192.168.0.0/24.
:return: If ip in range,return True,else return False.
"""
network = network.split('/')
mask = ~(2**(32 - int(network[1])) - 1)
return (self._ip_into_int(ip) & mask) == (self._ip_into_int(network[0]) & mask)
def _verify_uniqueness_of_network_name(self, req, network_list, network_meta, is_update = False):
"""
Network name is match case and uniqueness in cluster.
:param req:
:param network_list: network plane in cluster
:param network_meta: network plane need be verified
:return:
"""
if not network_list or not network_meta or not network_meta.get('name', None):
msg = _("Input params invalid for verifying uniqueness of network name.")
raise HTTPBadRequest(msg, request=req, content_type="text/plain")
network_name = network_meta['name']
for network in network_list['networks']:
if (is_update and
network_name == network['name'] and
network_meta['id'] == network['id']):
return
# network name don't match case
network_name_list = [network['name'].lower() for network in
network_list['networks'] if network.get('name', None)]
if network_name.lower() in network_name_list:
msg = _("Name of network isn't match case and %s already exits in the cluster." % network_name)
raise HTTPConflict(msg, request=req, content_type="text/plain")
if not is_update:
# Input networks type can't be same with db record which is all ready exit,
# except PRIVATE network.
network_type_exist_list = \
[network['network_type'] for network in network_list['networks']
if network.get('network_type', None) and network['network_type'] != "PRIVATE"
and network['network_type'] != "STORAGE"]
if network_meta.get("network_type", None) in network_type_exist_list:
msg = _("The %s network plane %s must be only, except PRIVATE network." %
(network_meta['network_type'], network_name))
raise HTTPConflict(msg, request=req, content_type="text/plain")
def _valid_vlan_range(self, req, network_meta):
if ((network_meta.has_key('vlan_start') and not network_meta.has_key('vlan_end')) or
(not network_meta.has_key('vlan_start') and network_meta.has_key('vlan_end'))):
raise HTTPBadRequest(explanation="vlan-start and vlan-end must be appeared at the same time", request=req)
if network_meta.has_key('vlan_start'):
if not (int(network_meta['vlan_start']) >= 1 and
int(network_meta['vlan_start']) <= 4094):
raise HTTPBadRequest(explanation="vlan-start must be a integer in '1~4096'", request=req)
if network_meta.has_key('vlan_end'):
if not (int(network_meta['vlan_end']) >= 1 and
int(network_meta['vlan_end']) <= 4094):
raise HTTPBadRequest(explanation="vlan-end must be a integer in '1~4096'", request=req)
if int(network_meta['vlan_start']) > int(network_meta['vlan_end']):
raise HTTPBadRequest(explanation="vlan-start must be less than vlan-end", request=req)
@utils.mutating
def add_network(self, req, network_meta):
"""
Adds a new networks to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about network
:raises HTTPBadRequest if x-host-name is missing
"""
self._enforce(req, 'add_network')
cluster_id = network_meta.get('cluster_id',None)
if cluster_id:
self._raise_404_if_cluster_delete(req, cluster_id)
network_list = self.detail(req, cluster_id)
self._verify_uniqueness_of_network_name(req, network_list, network_meta)
# else:
# if network_meta.get('type',None) != "template":
# raise HTTPBadRequest(explanation="cluster id must be given", request=req)
network_name=network_meta.get('name',None)
network_name_split = network_name.split('_')
for network_name_info in network_name_split :
if not network_name_info.isalnum():
raise ValueError('network name must be numbers or letters or underscores !')
if not network_meta.has_key('network_type'):
raise HTTPBadRequest(explanation="network-type must be given", request=req)
if network_meta['network_type'] not in SUPPORT_NETWORK_TYPE:
raise HTTPBadRequest(explanation="unsupported network-type", request=req)
if (network_meta.has_key('type') and
network_meta['type'] not in SUPPORT_NETWORK_TEMPLATE_TYPE):
raise HTTPBadRequest(explanation="unsupported type", request=req)
if (network_meta.has_key('capability') and
network_meta['capability'] not in SUPPORT_NETWORK_CAPABILITY):
raise HTTPBadRequest(explanation="unsupported capability type", request=req)
self._valid_vlan_range(req, network_meta)
if network_meta.get('ip_ranges', None):
cidr = None
if not network_meta.has_key('cidr'):
msg = (_("When ip range was specified, the CIDR parameter can not be empty."))
LOG.warn(msg)
raise HTTPForbidden(msg)
else:
cidr = network_meta['cidr']
cidr_division = cidr.split('/')
if len(cidr_division) != 2 or ( cidr_division[1] \
and int(cidr_division[1]) > 32 or int(cidr_division[1]) < 0):
msg = (_("Wrong CIDR format."))
LOG.warn(msg)
raise HTTPForbidden(msg)
self.validate_ip_format(cidr_division[0])
ip_ranges = eval(network_meta['ip_ranges'])
last_ip_range_end = 0
int_ip_ranges_list = list()
sorted_int_ip_ranges_list = list()
for ip_pair in ip_ranges:
if ['start', 'end'] != ip_pair.keys():
msg = (_("IP range was not start with 'start:' or end with 'end:'."))
LOG.warn(msg)
raise HTTPForbidden(msg)
ip_start = ip_pair['start']
ip_end = ip_pair['end']
self.validate_ip_format(ip_start) #check ip format
self.validate_ip_format(ip_end)
if not self._is_in_network_range(ip_start, cidr):
msg = (_("IP address %s was not in the range of CIDR %s." % (ip_start, cidr)))
LOG.warn(msg)
raise HTTPForbidden(msg)
if not self._is_in_network_range(ip_end, cidr):
msg = (_("IP address %s was not in the range of CIDR %s." % (ip_end, cidr)))
LOG.warn(msg)
raise HTTPForbidden(msg)
#transform ip format to int when the string format is valid
int_ip_start = self._ip_into_int(ip_start)
int_ip_end = self._ip_into_int(ip_end)
if int_ip_start > int_ip_end:
msg = (_("Wrong ip range format."))
LOG.warn(msg)
raise HTTPForbidden(msg)
int_ip_ranges_list.append([int_ip_start, int_ip_end])
sorted_int_ip_ranges_list = sorted(int_ip_ranges_list, key=lambda x : x[0])
for int_ip_range in sorted_int_ip_ranges_list:
if last_ip_range_end and last_ip_range_end >= int_ip_range[0]:
msg = (_("Between ip ranges can not be overlap."))
LOG.warn(msg) # such as "[10, 15], [12, 16]", last_ip_range_end >= int_ip_range[0], this ip ranges were overlap
raise HTTPForbidden(msg)
else:
last_ip_range_end = int_ip_range[1]
if network_meta.get('cidr', None) \
and network_meta.get('vlan_id', None) \
and cluster_id:
networks = registry.get_networks_detail(req.context, cluster_id)
for network in networks:
if network['cidr'] and network['vlan_id']:
if network_meta['cidr'] == network['cidr'] \
and network_meta['vlan_id'] != network['vlan_id']:
msg = (_('Networks with the same cidr must '
'have the same vlan_id'))
raise HTTPBadRequest(explanation=msg)
if network_meta['vlan_id'] == network['vlan_id'] \
and network_meta['cidr'] != network['cidr']:
msg = (_('Networks with the same vlan_id must '
'have the same cidr'))
raise HTTPBadRequest(explanation=msg)
if network_meta.get('gateway', None) and network_meta.get('cidr', None):
gateway = network_meta['gateway']
cidr = network_meta['cidr']
self.validate_ip_format(gateway)
return_flag = self._is_in_network_range(gateway, cidr)
if not return_flag:
msg = (_('The gateway %s was not in the same segment with the cidr %s of management network.' % (gateway, cidr)))
raise HTTPBadRequest(explanation=msg)
network_meta = registry.add_network_metadata(req.context, network_meta)
return {'network_meta': network_meta}
@utils.mutating
def delete_network(self, req, network_id):
"""
Deletes a network from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about host
:raises HTTPBadRequest if x-host-name is missing
"""
self._enforce(req, 'delete_network')
#self._raise_404_if_cluster_deleted(req, cluster_id)
#self._raise_404_if_network_deleted(req, network_id)
network = self.get_network_meta_or_404(req, network_id)
if network['deleted']:
msg = _("Network with identifier %s has been deleted.") % network_id
raise HTTPNotFound(msg)
if network['type'] != 'custom':
msg = _("Type of network was not custom, can not delete this network.")
raise HTTPForbidden(msg)
try:
registry.delete_network_metadata(req.context, network_id)
except exception.NotFound as e:
msg = (_("Failed to find network to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete network: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("Network %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('host.delete', host)
return Response(body='', status=200)
@utils.mutating
def get_network(self, req, id):
"""
Returns metadata about an network in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque host identifier
:raises HTTPNotFound if host metadata is not available to user
"""
self._enforce(req, 'get_network')
network_meta = self.get_network_meta_or_404(req, id)
return {'network_meta': network_meta}
def get_all_network(self, req):
"""
List all network.
:param req:
:return:
"""
self._enforce(req, 'get_all_network')
params = self._get_query_params(req)
try:
networks = registry.get_all_networks(req.context,**params)
except Exception:
raise HTTPBadRequest(explanation="Get all networks failed.", request=req)
return dict(networks=networks)
def detail(self, req, id):
"""
Returns detailed information for all available hosts
:param req: The WSGI/Webob Request object
:retval The response body is a mapping of the following form::
{'networks': [
{'id': <ID>,
'name': <NAME>,
'description': <DESCRIPTION>,
'created_at': <TIMESTAMP>,
'updated_at': <TIMESTAMP>,
'deleted_at': <TIMESTAMP>|<NONE>,}, ...
]}
"""
cluster_id = self._raise_404_if_cluster_delete(req, id)
self._enforce(req, 'get_networks')
params = self._get_query_params(req)
try:
networks = registry.get_networks_detail(req.context, id,**params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(networks=networks)
@utils.mutating
def update_network(self, req, network_id, network_meta):
"""
Updates an existing host with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
if network_meta.has_key('name'):
network_name=network_meta.get('name',None)
network_name_split = network_name.split('_')
for network_name_info in network_name_split :
if not network_name_info.isalnum():
raise ValueError('network name must be numbers or letters or underscores !')
self._enforce(req, 'update_network')
#orig_cluster_meta = self.get_cluster_meta_or_404(req, cluster_id)
orig_network_meta = self.get_network_meta_or_404(req, network_id)
# Do not allow any updates on a deleted network.
if orig_network_meta['deleted']:
msg = _("Forbidden to update deleted host.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
if (network_meta.has_key('network_type') and
network_meta['network_type'] not in SUPPORT_NETWORK_TYPE):
raise HTTPBadRequest(explanation="unsupported network-type", request=req)
if (network_meta.has_key('type') and
network_meta['type'] not in SUPPORT_NETWORK_TEMPLATE_TYPE):
raise HTTPBadRequest(explanation="unsupported type", request=req)
if (network_meta.has_key('type') and
network_meta['type'] == 'template'):
raise HTTPBadRequest(explanation="network template type is not allowed to update", request=req)
if (network_meta.has_key('capability') and
network_meta['capability'] not in SUPPORT_NETWORK_CAPABILITY):
raise HTTPBadRequest(explanation="unsupported capability type", request=req)
self._valid_vlan_range(req, network_meta)
network_name = network_meta.get('name', None)
cluster_id = orig_network_meta['cluster_id']
if network_name and cluster_id:
network_updated = copy.deepcopy(network_meta)
network_updated['id'] = network_id
network_type = network_meta.get('network_type', None)
network_updated['network_type'] = \
orig_network_meta['network_type'] if not network_type else network_type
network_list = self.detail(req, cluster_id)
self._verify_uniqueness_of_network_name(req, network_list, network_updated, True)
cidr = network_meta.get('cidr', orig_network_meta['cidr'])
vlan_id = network_meta.get('vlan_id', orig_network_meta['vlan_id'])
if cidr:
cidr_division = cidr.split('/')
if len(cidr_division) != 2 or ( cidr_division[1] \
and int(cidr_division[1]) > 32 or int(cidr_division[1]) < 0):
msg = (_("Wrong CIDR format."))
LOG.warn(msg)
raise HTTPForbidden(msg)
self.validate_ip_format(cidr_division[0])
if cidr and vlan_id and cluster_id:
networks = registry.get_networks_detail(req.context, cluster_id)
for network in networks:
if network['cidr'] and network['vlan_id']:
if cidr == network['cidr'] \
and vlan_id != network['vlan_id'] \
and network['id'] != network_id:
msg = (_('Networks with the same cidr must have '
'the same vlan_id'))
raise HTTPBadRequest(explanation=msg)
if vlan_id == network['vlan_id'] \
and cidr != network['cidr'] \
and network['id'] != network_id:
msg = (_('Networks with the same vlan_id must '
'have the same cidr'))
raise HTTPBadRequest(explanation=msg)
if network_meta.get('ip_ranges', None):
if not cidr:
msg = (_("When ip range was specified, the CIDR parameter can not be empty."))
LOG.warn(msg)
raise HTTPForbidden(msg)
ip_ranges = eval(network_meta['ip_ranges'])
last_ip_range_end = 0
int_ip_ranges_list = list()
sorted_int_ip_ranges_list = list()
for ip_pair in ip_ranges:
if ['start', 'end'] != ip_pair.keys():
msg = (_("IP range was not start with 'start:' or end with 'end:'."))
LOG.warn(msg)
raise HTTPForbidden(msg)
ip_start = ip_pair['start']
ip_end = ip_pair['end']
self.validate_ip_format(ip_start) #check ip format
self.validate_ip_format(ip_end)
if not self._is_in_network_range(ip_start, cidr):
msg = (_("IP address %s was not in the range of CIDR %s." % (ip_start, cidr)))
LOG.warn(msg)
raise HTTPForbidden(msg)
if not self._is_in_network_range(ip_end, cidr):
msg = (_("IP address %s was not in the range of CIDR %s." % (ip_end, cidr)))
LOG.warn(msg)
raise HTTPForbidden(msg)
#transform ip format to int when the string format is valid
int_ip_start = self._ip_into_int(ip_start)
int_ip_end = self._ip_into_int(ip_end)
if int_ip_start > int_ip_end:
msg = (_("Wrong ip range format."))
LOG.warn(msg)
raise HTTPForbidden(msg)
int_ip_ranges_list.append([int_ip_start, int_ip_end])
sorted_int_ip_ranges_list = sorted(int_ip_ranges_list, key=lambda x : x[0])
LOG.warn("sorted_int_ip_ranges_list: "% sorted_int_ip_ranges_list)
#check ip ranges overlap
for int_ip_range in sorted_int_ip_ranges_list:
if last_ip_range_end and last_ip_range_end >= int_ip_range[0]:
msg = (_("Between ip ranges can not be overlap."))
LOG.warn(msg) # such as "[10, 15], [12, 16]", last_ip_range_end >= int_ip_range[0], this ip ranges were overlap
raise HTTPForbidden(msg)
else:
last_ip_range_end = int_ip_range[1]
if network_meta.get('gateway', orig_network_meta['gateway']) and network_meta.get('cidr', orig_network_meta['cidr']):
gateway = network_meta.get('gateway', orig_network_meta['gateway'])
cidr = network_meta.get('cidr', orig_network_meta['cidr'])
self.validate_ip_format(gateway)
return_flag = self._is_in_network_range(gateway, cidr)
if not return_flag:
msg = (_('The gateway %s was not in the same segment with the cidr %s of management network.' % (gateway, cidr)))
raise HTTPBadRequest(explanation=msg)
try:
network_meta = registry.update_network_metadata(req.context,
network_id,
network_meta)
except exception.Invalid as e:
msg = (_("Failed to update network metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find network to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update network: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('Network operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('network.update', network_meta)
return {'network_meta': network_meta}
class HostDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["network_meta"] = utils.get_network_meta(request)
return result
def add_network(self, request):
return self._deserialize(request)
def update_network(self, request):
return self._deserialize(request)
class HostSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_network(self, response, result):
network_meta = result['network_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(network=network_meta))
return response
def delete_network(self, response, result):
network_meta = result['network_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(network=network_meta))
return response
def get_network(self, response, result):
network_meta = result['network_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(network=network_meta))
return response
def create_resource():
"""Hosts resource factory method"""
deserializer = HostDeserializer()
serializer = HostSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

782
code/daisy/daisy/api/v1/roles.py Executable file
View File

@@ -0,0 +1,782 @@
# Copyright 2013 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.
"""
/roles endpoint for Daisy v1 API
"""
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
SUPPORTED_DEPLOYMENT_BACKENDS = ('tecs', 'zenic', 'proton')
SUPPORTED_ROLE = ('CONTROLLER_LB', 'CONTROLLER_HA', 'COMPUTER', 'ZENIC_CTL', 'ZENIC_NFM',
'ZENIC_MDB', 'PROTON', 'CHILD_CELL_1_COMPUTER', 'CONTROLLER_CHILD_CELL_1')
SUPPORT_DISK_LOCATION = ('local', 'share')
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
class Controller(controller.BaseController):
"""
WSGI controller for roles resource in Daisy v1 API
The roles resource API is a RESTful web role for role data. The API
is as follows::
GET /roles -- Returns a set of brief metadata about roles
GET /roles/detail -- Returns a set of detailed metadata about
roles
HEAD /roles/<ID> -- Return metadata about an role with id <ID>
GET /roles/<ID> -- Return role data for role with id <ID>
POST /roles -- Store role data and return metadata about the
newly-stored role
PUT /roles/<ID> -- Update role metadata and/or upload role
data for a previously-reserved role
DELETE /roles/<ID> -- Delete the role with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
def _raise_404_if_host_deleted(self, req, host_id):
host = self.get_host_meta_or_404(req, host_id)
if host['deleted']:
msg = _("Node with identifier %s has been deleted.") % host_id
raise HTTPNotFound(msg)
def _raise_404_if_service_deleted(self, req, service_id):
service = self.get_service_meta_or_404(req, service_id)
if service['deleted']:
msg = _("Service with identifier %s has been deleted.") % service_id
raise HTTPNotFound(msg)
def _raise_404_if_config_set_deleted(self, req, config_set_id):
config_set = self.get_config_set_meta_or_404(req, config_set_id)
if config_set['deleted']:
msg = _("Config_Set with identifier %s has been deleted.") % config_set_id
raise HTTPNotFound(msg)
def _raise_404_if_cluster_deleted(self, req, cluster_id):
cluster = self.get_cluster_meta_or_404(req, cluster_id)
if cluster['deleted']:
msg = _("cluster with identifier %s has been deleted.") % cluster_id
raise HTTPNotFound(msg)
def _get_service_name_list(self, req, role_service_id_list):
service_name_list = []
for service_id in role_service_id_list:
service_meta = registry.get_service_metadata(req.context, service_id)
service_name_list.append(service_meta['name'])
return service_name_list
def _get_host_disk_except_os_disk_by_info(self, host_info):
'''
type(host_info): <type 'dict'>
host_disk_except_os_disk_lists: disk_size , type = int
'''
#import pdb;pdb.set_trace()
host_disk_except_os_disk_lists = 0
os_disk_m = host_info.get('root_lv_size', 51200)
swap_size_m = host_info.get('swap_lv_size', None)
if swap_size_m:
swap_size_m = (swap_size_m / 4)*4
else:
swap_size_m = 0
boot_partition_m = 400
redundant_partiton_m = 600
if not os_disk_m:
os_disk_m = 51200
#host_disk = 1024
host_disks = host_info.get('disks', None)
host_disk_size_m = 0
if host_disks:
for key, value in host_disks.items():
disk_size_b = str(value.get('size', None))
disk_size_b_str = disk_size_b.strip().split()[0]
if disk_size_b_str:
disk_size_b_int = int(disk_size_b_str)
disk_size_m = disk_size_b_int//(1024*1024)
host_disk_size_m = host_disk_size_m + disk_size_m
host_disk_except_os_disk_lists = host_disk_size_m - os_disk_m - swap_size_m - boot_partition_m - redundant_partiton_m
LOG.warn('----start----host_disk_except_os_disk_lists: %s -----end--' % host_disk_except_os_disk_lists)
return host_disk_except_os_disk_lists
def _check_host_validity(self, **paras):
'''
paras['db_lv_size'], paras['glance_lv_size'] , paras['disk_size']
'''
disk_size = paras.get('disk_size', None)
LOG.warn('--------disk_size:----- %s'% disk_size)
if disk_size:
disk_size_m = int(disk_size)
else:
disk_size_m = 0
if disk_size_m == 0: #Host hard disk size was 0, think that the host does not need to install the system
return #Don't need to ckeck the validity of hard disk size
db_lv_size_m = paras.get('db_lv_size', 300)
if db_lv_size_m:
db_lv_size_m = int(db_lv_size_m)
else:
db_lv_size_m = 0
glance_lv_size_m = paras.get('glance_lv_size', 17100)
if glance_lv_size_m:
glance_lv_size_m = int(glance_lv_size_m)
else:
glance_lv_size_m = 0
nova_lv_size_m = paras.get('nova_lv_size', 0)
if nova_lv_size_m:
nova_lv_size_m = int(nova_lv_size_m)
else:
nova_lv_size_m = 0
if nova_lv_size_m == -1:
nova_lv_size_m = 0
glance_lv_size_m = (glance_lv_size_m/4)*4
db_lv_size_m = (db_lv_size_m/4)*4
nova_lv_size_m = (nova_lv_size_m/4)*4
if glance_lv_size_m + db_lv_size_m + nova_lv_size_m > disk_size_m:
msg = _("There isn't enough disk space to specify database or glance or nova disk, please specify database or glance or nova disk size again")
LOG.debug(msg)
raise HTTPForbidden(msg)
def _check_nodes_exist(self, req, nodes):
for role_host_id in nodes:
self._raise_404_if_host_deleted(req, role_host_id)
def _check_services_exist(self, req, services):
for role_service_id in services:
self._raise_404_if_service_deleted(req, role_service_id)
def _check_config_set_id_exist(self, req, config_set_id):
self._raise_404_if_config_set_deleted(req, config_set_id)
def _check_glance_lv_value(self, req, glance_lv_value, role_name, service_name_list):
if int(glance_lv_value) < 0 and int(glance_lv_value) != -1:
msg = _("glance_lv_size can't be negative except -1.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
if not service_name_list or 'glance' not in service_name_list:
msg = _("service 'glance' is not in role %s, so can't "
"set the size of glance lv.") % role_name
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
def _check_db_lv_size(self, req, db_lv_size, service_name_list):
if int(db_lv_size) < 0 and int(db_lv_size) != -1 :
msg = _("The size of database disk can't be negative except -1.")
LOG.debug(msg)
raise HTTPForbidden(msg)
#Only the role with database service can be formulated the size of a database.
if 'mariadb' not in service_name_list and 'mongodb' not in service_name_list:
msg = _('The role without database service is unable '
'to specify the size of the database!')
LOG.debug(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
def _check_nova_lv_size(self, req, nova_lv_size, role_name):
if role_name != "COMPUTER":
msg = _("The role is not COMPUTER, it can't set logic "
"volume disk for nova.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
try:
if int(nova_lv_size) < 0 and int(nova_lv_size) != -1:
msg = _("The nova_lv_size must be -1 or [0, N).")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except:
msg = _("The nova_lv_size must be -1 or [0, N).")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
def _check_all_lv_size(self, req, db_lv_size, glance_lv_size, nova_lv_size,
host_id_list, cluster_id, argws):
if db_lv_size or glance_lv_size or nova_lv_size:
for host_id in host_id_list:
host_disk_db_glance_nova_size = self.get_host_disk_db_glance_nova_size(req, host_id, cluster_id)
if host_disk_db_glance_nova_size['db_lv_size'] and db_lv_size and \
int(db_lv_size) < int(host_disk_db_glance_nova_size['db_lv_size']):
argws['db_lv_size'] = host_disk_db_glance_nova_size['db_lv_size']
else:
argws['db_lv_size'] = db_lv_size
if host_disk_db_glance_nova_size['glance_lv_size'] and glance_lv_size and \
int(glance_lv_size) < int(host_disk_db_glance_nova_size['glance_lv_size']):
argws['glance_lv_size'] = host_disk_db_glance_nova_size['glance_lv_size']
else:
argws['glance_lv_size'] = glance_lv_size
if host_disk_db_glance_nova_size['nova_lv_size'] and nova_lv_size and \
int(nova_lv_size) < int(host_disk_db_glance_nova_size['nova_lv_size']):
argws['nova_lv_size'] = host_disk_db_glance_nova_size['nova_lv_size']
else:
argws['nova_lv_size'] = nova_lv_size
argws['disk_size'] = host_disk_db_glance_nova_size['disk_size']
LOG.warn('--------host(%s) check_host_validity argws:----- %s'% (host_id, argws))
self._check_host_validity(**argws)
def _check_deployment_backend(self, req, deployment_backend):
if deployment_backend not in SUPPORTED_DEPLOYMENT_BACKENDS:
msg = "deployment backend '%s' is not supported." % deployment_backend
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
def _check_role_type_in_update_role(self, req, role_type, orig_role_meta):
if orig_role_meta['type'].lower() != role_type.lower():
msg = _("Role type can not be updated to other type.")
LOG.debug(msg)
raise HTTPForbidden(msg)
def _check_cluster_id_in_role_update(self, req, role_cluster, orig_role_meta):
if orig_role_meta['type'].lower() == 'template':
msg = _("The template role does not belong to any cluster.")
LOG.debug(msg)
raise HTTPForbidden(msg)
orig_role_cluster = orig_role_meta['cluster_id']
if orig_role_cluster != role_cluster: #Can not change the cluster which the role belongs to
msg = _("Can't update the cluster of the role.")
LOG.debug(msg)
raise HTTPForbidden(msg)
else:
self._raise_404_if_cluster_deleted(req, role_cluster)
def _check_role_name_in_role_update(self, req, role_meta, orig_role_meta):
role_name = role_meta['name']
cluster_id = role_meta.get('cluster_id', orig_role_meta['cluster_id'])
if cluster_id:
self.check_cluster_role_name_repetition(req, role_name, cluster_id)
else: #role type was template, cluster id was None
self.check_template_role_name_repetition(req, role_name)
def _check_all_lv_size_of_nodes_with_role_in_role_update(self, req, role_meta, orig_role_meta,
role_host_id_list):
#check host with this role at the same time
cluster_id = role_meta.get('cluster_id', None)
if not cluster_id: #role with cluster
cluster_id = orig_role_meta['cluster_id']
if not cluster_id: #without cluster id, raise Error
msg = _("The cluster_id parameter can not be None!")
LOG.debug(msg)
raise HTTPForbidden(msg)
argws = dict()
if role_meta.has_key('db_lv_size'):
db_lv_size = role_meta['db_lv_size']
else: #The db_lv_size has been specified before.
db_lv_size = orig_role_meta.get('db_lv_size')
if role_meta.has_key('glance_lv_size'):
glance_lv_size = role_meta['glance_lv_size']
else:
glance_lv_size = orig_role_meta.get('glance_lv_size')
if role_meta.has_key('nova_lv_size'):
nova_lv_size = role_meta['nova_lv_size']
else:
nova_lv_size = orig_role_meta.get('nova_lv_size')
if role_meta.has_key('nodes'):
host_id_list = list(eval(role_meta['nodes'])) + role_host_id_list
else:
host_id_list = role_host_id_list
self._check_all_lv_size(req, db_lv_size, glance_lv_size,
nova_lv_size, host_id_list, cluster_id, argws)
def _check_ntp_server(self, req, role_name):
if role_name != 'CONTROLLER_HA':
msg = 'The role %s need no ntp_server' % role_name
raise HTTPForbidden(explanation=msg)
def _check_role_type_in_role_add(self, req, role_meta):
#role_type == None or not template, cluster id must not be None
role_type = role_meta['type']
if role_type.lower() != 'template':
role_cluster_id = role_meta.get('cluster_id', None)
if not role_cluster_id: #add role without cluster id parameter, raise error
msg = _("The cluster_id parameter can not be None if role was not a template type.")
LOG.debug(msg)
raise HTTPForbidden(msg)
else: #role_type == template, cluster id is not necessary
if role_meta.has_key('cluster_id'):
msg = _("Tht template role cannot be added to any cluster.")
LOG.debug(msg)
raise HTTPForbidden(msg)
def _check_all_lv_size_with_role_in_role_add(self, req, role_meta):
cluster_id = role_meta.get('cluster_id', None)
if not cluster_id: #without cluster id, raise Error
msg = _("The cluster_id parameter can not be None!")
LOG.debug(msg)
raise HTTPForbidden(msg)
argws = dict()
db_lv_size = role_meta.get('db_lv_size', 0)
glance_lv_size = role_meta.get('glance_lv_size', 0)
nova_lv_size = role_meta.get('nova_lv_size', 0)
host_id_list = list(eval(role_meta['nodes']))
self._check_all_lv_size(req, db_lv_size, glance_lv_size,
nova_lv_size, host_id_list, cluster_id, argws)
def get_host_disk_db_glance_nova_size(self, req, host_id, cluster_id):
'''
return :
host_disk_db_glance_nova_size['disk_size'] = 1024000
host_disk_db_glance_nova_size['db_lv_size'] = 1011
host_disk_db_glance_nova_size['glance_lv_size'] = 1011
host_disk_db_glance_nova_size['nova_lv_size'] = 1011
'''
#import pdb;pdb.set_trace()
host_disk_db_glance_nova_size = dict()
db_lv_size = list()
glance_lv_size = list()
nova_lv_size= list()
disk_size = list()
host_info = self.get_host_meta_or_404(req, host_id)
if host_info:
if host_info.has_key('deleted') and host_info['deleted']:
msg = _("Node with identifier %s has been deleted.") % host_info['id']
LOG.debug(msg)
raise HTTPNotFound(msg)
#get host disk infomation
host_disk = self._get_host_disk_except_os_disk_by_info(host_info)
host_disk_db_glance_nova_size['disk_size'] = host_disk
#get role_host db/galnce/nova infomation
cluster_info = self.get_cluster_meta_or_404(req, cluster_id)
if host_info.has_key('cluster'): #host with cluster
if host_info['cluster'] != cluster_info['name']:
#type(host_info['cluster']) = list, type(cluster_info['name']) = str
msg = _("Role and hosts belong to different cluster.")
LOG.debug(msg)
raise HTTPNotFound(msg)
else:
all_roles = registry.get_roles_detail(req.context)
cluster_roles = [role for role in all_roles if role['cluster_id'] == cluster_id]
#roles infomation saved in cluster_roles
if host_info.has_key('role') and host_info['role']: #host with role
for role in cluster_roles:
if role['name'] in host_info['role'] and cluster_roles:
db_lv_size.append(role.get('db_lv_size', None))
glance_lv_size.append(role.get('glance_lv_size', None))
nova_lv_size.append(role.get('nova_lv_size', None))
if db_lv_size:
host_disk_db_glance_nova_size['db_lv_size'] = max(db_lv_size)
else: #host without cluster
host_disk_db_glance_nova_size['db_lv_size'] = 0
if glance_lv_size:
host_disk_db_glance_nova_size['glance_lv_size'] = max(glance_lv_size)
else:
host_disk_db_glance_nova_size['glance_lv_size'] = 0
if nova_lv_size:
host_disk_db_glance_nova_size['nova_lv_size'] = max(nova_lv_size)
else:
host_disk_db_glance_nova_size['nova_lv_size'] = 0
LOG.warn('--------host(%s)disk_db_glance_nova_size:----- %s'% (host_id, host_disk_db_glance_nova_size))
return host_disk_db_glance_nova_size
def check_cluster_role_name_repetition(self, req, role_name, cluster_id):
all_roles = registry.get_roles_detail(req.context)
cluster_roles = [role for role in all_roles if role['cluster_id'] == cluster_id]
cluster_roles_name = [role['name'].lower() for role in cluster_roles]
if role_name.lower() in cluster_roles_name:
msg = _("The role %s has already been in the cluster %s!" % (role_name, cluster_id))
LOG.debug(msg)
raise HTTPForbidden(msg)
def check_template_role_name_repetition(self, req, role_name):
all_roles = registry.get_roles_detail(req.context)
template_roles = [role for role in all_roles if role['cluster_id'] == None]
template_roles_name = [role['name'].lower() for role in template_roles]
if role_name.lower() in template_roles_name:
msg = _("The role %s has already been in the the template role." % role_name)
LOG.debug(msg)
raise HTTPForbidden(msg)
def _check_disk_parameters(self, req, role_meta):
if (role_meta.has_key('disk_location') and
role_meta['disk_location'] not in SUPPORT_DISK_LOCATION):
msg = _("value of disk_location is not supported.")
raise HTTPForbidden(msg)
def _check_type_role_reasonable(self, req, role_meta):
if role_meta['role_type'] not in SUPPORTED_ROLE:
msg = 'The role type %s is illegal' % role_meta['role_type']
raise HTTPForbidden(explanation=msg)
def _check_role_update_parameters(self, req, role_meta, orig_role_meta,
role_service_id_list, role_host_id_list):
role_name = orig_role_meta['name']
if role_meta.get('type', None):
self._check_role_type_in_update_role(req, role_meta['type'], orig_role_meta)
if role_meta.has_key('ntp_server'):
self._check_ntp_server(req, role_name)
if role_meta.has_key('nodes'):
self._check_nodes_exist(req, list(eval(role_meta['nodes'])))
if role_meta.has_key('services'):
self._check_services_exist(req, list(eval(role_meta['services'])))
role_service_id_list.extend(list(eval(role_meta['services'])))
if role_meta.has_key('config_set_id'):
self._check_config_set_id_exist(req, str(role_meta['config_set_id']))
if role_meta.has_key('cluster_id'):
self._check_cluster_id_in_role_update(req, str(role_meta['cluster_id']), orig_role_meta)
if role_meta.has_key('name'):
self._check_role_name_in_role_update(req, role_meta, orig_role_meta)
service_name_list = self._get_service_name_list(req, role_service_id_list)
glance_lv_value = role_meta.get('glance_lv_size', orig_role_meta['glance_lv_size'])
if glance_lv_value:
self._check_glance_lv_value(req, glance_lv_value, role_name, service_name_list)
if role_meta.get('db_lv_size', None) and role_meta['db_lv_size']:
self._check_db_lv_size(req, role_meta['db_lv_size'], service_name_list)
if role_meta.get('nova_lv_size', None):
self._check_nova_lv_size(req, role_meta['nova_lv_size'], role_name)
if role_meta.has_key('nodes') or role_host_id_list:
self._check_all_lv_size_of_nodes_with_role_in_role_update(req, role_meta, orig_role_meta,
role_host_id_list)
self._check_disk_parameters(req, role_meta)
if role_meta.has_key('deployment_backend'):
self._check_deployment_backend(req, role_meta['deployment_backend'])
if role_meta.get('role_type', None):
self._check_type_role_reasonable(req, role_meta)
def _check_role_add_parameters(self, req, role_meta, role_service_id_list):
role_type = role_meta.get('type', None)
role_name = role_meta.get('name', None)
if role_meta.get('type', None):
self._check_role_type_in_role_add(req, role_meta)
if role_meta.has_key('nodes'):
self._check_nodes_exist(req, list(eval(role_meta['nodes'])))
if role_meta.has_key('services'):
self._check_services_exist(req, list(eval(role_meta['services'])))
role_service_id_list.extend(list(eval(role_meta['services'])))
if role_meta.has_key('config_set_id'):
self._check_config_set_id_exist(req, str(role_meta['config_set_id']))
if role_meta.has_key('cluster_id'):
orig_cluster = str(role_meta['cluster_id'])
self._raise_404_if_cluster_deleted(req, orig_cluster)
self.check_cluster_role_name_repetition(req, role_name, orig_cluster)
else:
self.check_template_role_name_repetition(req, role_name)
service_name_list = self._get_service_name_list(req, role_service_id_list)
glance_lv_value = role_meta.get('glance_lv_size', None)
if glance_lv_value:
self._check_glance_lv_value(req, glance_lv_value, role_name, service_name_list)
if role_meta.get('db_lv_size', None) and role_meta['db_lv_size']:
self._check_db_lv_size(req, role_meta['db_lv_size'], service_name_list)
if role_meta.get('nova_lv_size', None):
self._check_nova_lv_size(req, role_meta['nova_lv_size'], role_name)
if role_meta.has_key('nodes'):
self._check_all_lv_size_with_role_in_role_add(req, role_meta)
self._check_disk_parameters(req, role_meta)
if role_meta.has_key('deployment_backend'):
self._check_deployment_backend(req, role_meta['deployment_backend'])
else:
role_meta['deployment_backend'] = 'tecs'
if role_meta.get('role_type', None):
self._check_type_role_reasonable(req, role_meta)
@utils.mutating
def add_role(self, req, role_meta):
"""
Adds a new role to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about role
:raises HTTPBadRequest if x-role-name is missing
"""
self._enforce(req, 'add_role')
role_service_id_list = []
self._check_role_add_parameters(req, role_meta, role_service_id_list)
role_name = role_meta["name"]
role_description = role_meta["description"]
print role_name
print role_description
role_meta = registry.add_role_metadata(req.context, role_meta)
return {'role_meta': role_meta}
@utils.mutating
def delete_role(self, req, id):
"""
Deletes a role from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about role
:raises HTTPBadRequest if x-role-name is missing
"""
self._enforce(req, 'delete_role')
#role = self.get_role_meta_or_404(req, id)
print "delete_role:%s" % id
try:
registry.delete_role_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find role to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete role: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("role %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('role.delete', role)
return Response(body='', status=200)
@utils.mutating
def get_role(self, req, id):
"""
Returns metadata about an role in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque role identifier
:raises HTTPNotFound if role metadata is not available to user
"""
self._enforce(req, 'get_role')
role_meta = self.get_role_meta_or_404(req, id)
return {'role_meta': role_meta}
def detail(self, req):
"""
Returns detailed information for all available roles
:param req: The WSGI/Webob Request object
:retval The response body is a mapping of the following form::
{'roles': [
{'id': <ID>,
'name': <NAME>,
'description': <DESCRIPTION>,
'created_at': <TIMESTAMP>,
'updated_at': <TIMESTAMP>,
'deleted_at': <TIMESTAMP>|<NONE>,}, ...
]}
"""
self._enforce(req, 'get_roles')
params = self._get_query_params(req)
filters=params.get('filters',None)
if 'cluster_id' in filters:
cluster_id=filters['cluster_id']
self._raise_404_if_cluster_deleted(req, cluster_id)
try:
roles = registry.get_roles_detail(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(roles=roles)
@utils.mutating
def update_role(self, req, id, role_meta):
"""
Updates an existing role with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
orig_role_meta = self.get_role_meta_or_404(req, id)
role_service_list = registry.get_role_services(req.context, id)
role_service_id_list = [ role_service['service_id'] for role_service in role_service_list ]
role_host_info_list = registry.get_role_host_metadata(req.context, id)
role_host_id_list = [role_host['host_id'] for role_host in role_host_info_list]
self._check_role_update_parameters(req, role_meta, orig_role_meta, role_service_id_list, role_host_id_list)
self._enforce(req, 'modify_image')
#orig_role_meta = self.get_role_meta_or_404(req, id)
# Do not allow any updates on a deleted image.
# Fix for LP Bug #1060930
if orig_role_meta['deleted']:
msg = _("Forbidden to update deleted role.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
try:
role_meta = registry.update_role_metadata(req.context,
id,
role_meta)
except exception.Invalid as e:
msg = (_("Failed to update role metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find role to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update role: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('Host operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('role.update', role_meta)
return {'role_meta': role_meta}
class RoleDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["role_meta"] = utils.get_role_meta(request)
return result
def add_role(self, request):
return self._deserialize(request)
def update_role(self, request):
return self._deserialize(request)
class RoleSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_role(self, response, result):
role_meta = result['role_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(role=role_meta))
return response
def delete_role(self, response, result):
role_meta = result['role_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(role=role_meta))
return response
def get_role(self, response, result):
role_meta = result['role_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(role=role_meta))
return response
def create_resource():
"""Roles resource factory method"""
deserializer = RoleDeserializer()
serializer = RoleSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

574
code/daisy/daisy/api/v1/router.py Executable file
View File

@@ -0,0 +1,574 @@
# Copyright 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.api.v1 import images
from daisy.api.v1 import hosts
from daisy.api.v1 import clusters
from daisy.api.v1 import template
from daisy.api.v1 import components
from daisy.api.v1 import services
from daisy.api.v1 import roles
from daisy.api.v1 import members
from daisy.api.v1 import config_files
from daisy.api.v1 import config_sets
from daisy.api.v1 import configs
from daisy.api.v1 import networks
from daisy.api.v1 import install
from daisy.api.v1 import disk_array
from daisy.api.v1 import host_template
from daisy.common import wsgi
class API(wsgi.Router):
"""WSGI router for Glance v1 API requests."""
def __init__(self, mapper):
reject_method_resource = wsgi.Resource(wsgi.RejectMethodController())
'''images_resource = images.create_resource()
mapper.connect("/",
controller=images_resource,
action="index")
mapper.connect("/images",
controller=images_resource,
action='index',
conditions={'method': ['GET']})
mapper.connect("/images",
controller=images_resource,
action='create',
conditions={'method': ['POST']})
mapper.connect("/images",
controller=reject_method_resource,
action='reject',
allowed_methods='GET, POST',
conditions={'method': ['PUT', 'DELETE', 'HEAD',
'PATCH']})
mapper.connect("/images/detail",
controller=images_resource,
action='detail',
conditions={'method': ['GET', 'HEAD']})
mapper.connect("/images/detail",
controller=reject_method_resource,
action='reject',
allowed_methods='GET, HEAD',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH']})
mapper.connect("/images/{id}",
controller=images_resource,
action="meta",
conditions=dict(method=["HEAD"]))
mapper.connect("/images/{id}",
controller=images_resource,
action="show",
conditions=dict(method=["GET"]))
mapper.connect("/images/{id}",
controller=images_resource,
action="update",
conditions=dict(method=["PUT"]))
mapper.connect("/images/{id}",
controller=images_resource,
action="delete",
conditions=dict(method=["DELETE"]))
mapper.connect("/images/{id}",
controller=reject_method_resource,
action='reject',
allowed_methods='GET, HEAD, PUT, DELETE',
conditions={'method': ['POST', 'PATCH']})
members_resource = members.create_resource()
mapper.connect("/images/{image_id}/members",
controller=members_resource,
action="index",
conditions={'method': ['GET']})
mapper.connect("/images/{image_id}/members",
controller=members_resource,
action="update_all",
conditions=dict(method=["PUT"]))
mapper.connect("/images/{image_id}/members",
controller=reject_method_resource,
action='reject',
allowed_methods='GET, PUT',
conditions={'method': ['POST', 'DELETE', 'HEAD',
'PATCH']})
mapper.connect("/images/{image_id}/members/{id}",
controller=members_resource,
action="show",
conditions={'method': ['GET']})
mapper.connect("/images/{image_id}/members/{id}",
controller=members_resource,
action="update",
conditions={'method': ['PUT']})
mapper.connect("/images/{image_id}/members/{id}",
controller=members_resource,
action="delete",
conditions={'method': ['DELETE']})
mapper.connect("/images/{image_id}/members/{id}",
controller=reject_method_resource,
action='reject',
allowed_methods='GET, PUT, DELETE',
conditions={'method': ['POST', 'HEAD', 'PATCH']})
mapper.connect("/shared-images/{id}",
controller=members_resource,
action="index_shared_images")'''
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',
conditions={'method': ['GET']})
mapper.connect("/nodes/{id}",
controller=hosts_resource,
action='get_host',
conditions={'method': ['GET']})
mapper.connect("/discover_host/",
controller=hosts_resource,
action='discover_host',
conditions={'method': ['POST']})
mapper.connect("/discover/nodes",
controller=hosts_resource,
action='add_discover_host',
conditions={'method': ['POST']})
mapper.connect("/discover/nodes/{id}",
controller=hosts_resource,
action='delete_discover_host',
conditions={'method': ['DELETE']})
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_detail',
conditions={'method': ['GET']})
clusters_resource = clusters.create_resource()
mapper.connect("/clusters",
controller=clusters_resource,
action='add_cluster',
conditions={'method': ['POST']})
mapper.connect("/clusters/{id}",
controller=clusters_resource,
action='delete_cluster',
conditions={'method': ['DELETE']})
mapper.connect("/clusters/{id}",
controller=clusters_resource,
action='update_cluster',
conditions={'method': ['PUT']})
mapper.connect("/clusters",
controller=clusters_resource,
action='detail',
conditions={'method': ['GET']})
mapper.connect("/clusters/{id}",
controller=clusters_resource,
action='get_cluster',
conditions={'method': ['GET']})
mapper.connect("/clusters/{id}",
controller=clusters_resource,
action='update_cluster',
conditions={'method': ['PUT']})
template_resource = template.create_resource()
mapper.connect("/template",
controller=template_resource,
action='add_template',
conditions={'method': ['POST']})
mapper.connect("/template/{template_id}",
controller=template_resource,
action='update_template',
conditions={'method': ['PUT']})
mapper.connect("/template/{template_id}",
controller=template_resource,
action='delete_template',
conditions={'method': ['DELETE']})
mapper.connect("/template/lists",
controller=template_resource,
action='get_template_lists',
conditions={'method': ['GET']})
mapper.connect("/template/{template_id}",
controller=template_resource,
action='get_template_detail',
conditions={'method': ['GET']})
mapper.connect("/export_db_to_json",
controller=template_resource,
action='export_db_to_json',
conditions={'method': ['POST']})
mapper.connect("/import_json_to_template",
controller=template_resource,
action='import_json_to_template',
conditions={'method': ['POST']})
mapper.connect("/import_template_to_db",
controller=template_resource,
action='import_template_to_db',
conditions={'method': ['POST']})
host_template_resource = host_template.create_resource()
mapper.connect("/host_template",
controller=host_template_resource,
action='add_host_template',
conditions={'method': ['POST']})
mapper.connect("/host_template/{template_id}",
controller=host_template_resource,
action='update_host_template',
conditions={'method': ['PUT']})
mapper.connect("/host_template",
controller=host_template_resource,
action='delete_host_template',
conditions={'method': ['PUT']})
mapper.connect("/host_template/lists",
controller=host_template_resource,
action='get_host_template_lists',
conditions={'method': ['GET']})
mapper.connect("/host_template/{template_id}",
controller=host_template_resource,
action='get_host_template_detail',
conditions={'method': ['GET']})
mapper.connect("/host_to_template",
controller=host_template_resource,
action='host_to_template',
conditions={'method': ['POST']})
mapper.connect("/template_to_host",
controller=host_template_resource,
action='template_to_host',
conditions={'method': ['PUT']})
components_resource = components.create_resource()
mapper.connect("/components",
controller=components_resource,
action='add_component',
conditions={'method': ['POST']})
mapper.connect("/components/{id}",
controller=components_resource,
action='delete_component',
conditions={'method': ['DELETE']})
mapper.connect("/components/detail",
controller=components_resource,
action='detail',
conditions={'method': ['GET']})
mapper.connect("/components/{id}",
controller=components_resource,
action='get_component',
conditions={'method': ['GET']})
mapper.connect("/components/{id}",
controller=components_resource,
action='update_component',
conditions={'method': ['PUT']})
services_resource = services.create_resource()
mapper.connect("/services",
controller=services_resource,
action='add_service',
conditions={'method': ['POST']})
mapper.connect("/services/{id}",
controller=services_resource,
action='delete_service',
conditions={'method': ['DELETE']})
mapper.connect("/services/detail",
controller=services_resource,
action='detail',
conditions={'method': ['GET']})
mapper.connect("/services/{id}",
controller=services_resource,
action='get_service',
conditions={'method': ['GET']})
mapper.connect("/services/{id}",
controller=services_resource,
action='update_service',
conditions={'method': ['PUT']})
roles_resource = roles.create_resource()
mapper.connect("/roles",
controller=roles_resource,
action='add_role',
conditions={'method': ['POST']})
mapper.connect("/roles/{id}",
controller=roles_resource,
action='delete_role',
conditions={'method': ['DELETE']})
mapper.connect("/roles/detail",
controller=roles_resource,
action='detail',
conditions={'method': ['GET']})
mapper.connect("/roles/{id}",
controller=roles_resource,
action='get_role',
conditions={'method': ['GET']})
mapper.connect("/roles/{id}",
controller=roles_resource,
action='update_role',
conditions={'method': ['PUT']})
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']})
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",
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",
conditions={'method': ['GET']})
mapper.connect("/config_sets/{id}",
controller=config_sets_resource,
action="get_config_set",
conditions=dict(method=["GET"]))
mapper.connect("/cluster_config_set_update",
controller=config_sets_resource,
action="cluster_config_set_update",
conditions={'method': ['POST']})
mapper.connect("/cluster_config_set_progress",
controller=config_sets_resource,
action="cluster_config_set_progress",
conditions={'method': ['POST']})
configs_resource = configs.create_resource()
mapper.connect("/configs",
controller=configs_resource,
action="add_config",
conditions={'method': ['POST']})
mapper.connect("/configs_delete",
controller=configs_resource,
action="delete_config",
conditions={'method': ['DELETE']})
mapper.connect("/configs/detail",
controller=configs_resource,
action="detail",
conditions={'method': ['GET']})
mapper.connect("/configs/{id}",
controller=configs_resource,
action="get_config",
conditions=dict(method=["GET"]))
networks_resource = networks.create_resource()
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("/clusters/{id}/networks",
controller=networks_resource,
action='detail',
conditions={'method': ['GET']})
mapper.connect("/networks/{id}",
controller=networks_resource,
action='get_network',
conditions={'method': ['GET']})
mapper.connect("/networks",
controller=networks_resource,
action='get_all_network',
conditions={'method': ['GET']})
install_resource = install.create_resource()
mapper.connect("/install",
controller=install_resource,
action='install_cluster',
conditions={'method': ['POST']})
mapper.connect("/export_db",
controller=install_resource,
action='export_db',
conditions={'method': ['POST']})
mapper.connect("/uninstall/{cluster_id}",
controller=install_resource,
action='uninstall_cluster',
conditions={'method': ['POST']})
mapper.connect("/uninstall/{cluster_id}",
controller=install_resource,
action='uninstall_progress',
conditions={'method': ['GET']})
mapper.connect("/update/{cluster_id}",
controller=install_resource,
action='update_cluster',
conditions={'method': ['POST']})
mapper.connect("/update/{cluster_id}",
controller=install_resource,
action='update_progress',
conditions={'method': ['GET']})
mapper.connect("/disk_array/{cluster_id}",
controller=install_resource,
action='update_disk_array',
conditions={'method': ['POST']})
#mapper.connect("/update/{cluster_id}/versions/{versions_id}",
# controller=update_resource,
# action='update_cluster_version',
# 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']})
super(API, self).__init__(mapper)

View File

@@ -0,0 +1,334 @@
# Copyright 2013 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.
"""
/services endpoint for Daisy v1 API
"""
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
class Controller(controller.BaseController):
"""
WSGI controller for services resource in Daisy v1 API
The services resource API is a RESTful web service for service data. The API
is as follows::
GET /services -- Returns a set of brief metadata about services
GET /services/detail -- Returns a set of detailed metadata about
services
HEAD /services/<ID> -- Return metadata about an service with id <ID>
GET /services/<ID> -- Return service data for service with id <ID>
POST /services -- Store service data and return metadata about the
newly-stored service
PUT /services/<ID> -- Update service metadata and/or upload service
data for a previously-reserved service
DELETE /services/<ID> -- Delete the service with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
def _raise_404_if_component_deleted(self, req, component_id):
component = self.get_component_meta_or_404(req, component_id)
if component['deleted']:
msg = _("Component with identifier %s has been deleted.") % component_id
raise HTTPNotFound(msg)
@utils.mutating
def add_service(self, req, service_meta):
"""
Adds a new service to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about service
:raises HTTPBadRequest if x-service-name is missing
"""
self._enforce(req, 'add_service')
service_name = service_meta["name"]
service_description = service_meta["description"]
if service_meta.has_key('component_id'):
orig_component_id = str(service_meta['component_id'])
self._raise_404_if_component_deleted(req, orig_component_id)
print service_name
print service_description
service_meta = registry.add_service_metadata(req.context, service_meta)
return {'service_meta': service_meta}
@utils.mutating
def delete_service(self, req, id):
"""
Deletes a service from Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about service
:raises HTTPBadRequest if x-service-name is missing
"""
self._enforce(req, 'delete_service')
#service = self.get_service_meta_or_404(req, id)
print "delete_service:%s" % id
try:
registry.delete_service_metadata(req.context, id)
except exception.NotFound as e:
msg = (_("Failed to find service to delete: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete service: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("service %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": id, "exc": utils.exception_to_str(e)})
LOG.warn(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
#self.notifier.info('service.delete', service)
return Response(body='', status=200)
@utils.mutating
def get_service(self, req, id):
"""
Returns metadata about an service in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque service identifier
:raises HTTPNotFound if service metadata is not available to user
"""
self._enforce(req, 'get_service')
service_meta = self.get_service_meta_or_404(req, id)
return {'service_meta': service_meta}
def detail(self, req):
"""
Returns detailed information for all available services
:param req: The WSGI/Webob Request object
:retval The response body is a mapping of the following form::
{'services': [
{'id': <ID>,
'name': <NAME>,
'description': <DESCRIPTION>,
'created_at': <TIMESTAMP>,
'updated_at': <TIMESTAMP>,
'deleted_at': <TIMESTAMP>|<NONE>,}, ...
]}
"""
self._enforce(req, 'get_services')
params = self._get_query_params(req)
try:
services = registry.get_services_detail(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(services=services)
@utils.mutating
def update_service(self, req, id, service_meta):
"""
Updates an existing service with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'modify_image')
orig_service_meta = self.get_service_meta_or_404(req, id)
# Do not allow any updates on a deleted image.
# Fix for LP Bug #1060930
if orig_service_meta['deleted']:
msg = _("Forbidden to update deleted service.")
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
try:
service_meta = registry.update_service_metadata(req.context,
id,
service_meta)
except exception.Invalid as e:
msg = (_("Failed to update service metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find service to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update service: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('Host operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('service.update', service_meta)
return {'service_meta': service_meta}
class ServiceDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["service_meta"] = utils.get_service_meta(request)
return result
def add_service(self, request):
return self._deserialize(request)
def update_service(self, request):
return self._deserialize(request)
class ServiceSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_service(self, response, result):
service_meta = result['service_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(service=service_meta))
return response
def delete_service(self, response, result):
service_meta = result['service_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(service=service_meta))
return response
def get_service(self, response, result):
service_meta = result['service_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(service=service_meta))
return response
def create_resource():
"""Services resource factory method"""
deserializer = ServiceDeserializer()
serializer = ServiceSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,629 @@
# Copyright 2013 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.
"""
/Templates endpoint for Daisy v1 API
"""
from oslo_config import cfg
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPConflict
from webob.exc import HTTPForbidden
from webob.exc import HTTPNotFound
from webob import Response
import copy
import json
from daisy.api import policy
import daisy.api.v1
from daisy.api.v1 import controller
from daisy.api.v1 import filters
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
from daisy import i18n
from daisy import notifier
import daisy.registry.client.v1.api as registry
from daisy.registry.api.v1 import template
import daisy.api.backends.tecs.common as tecs_cmn
import daisy.api.backends.common as daisy_cmn
try:
import simplejson as json
except ImportError:
import json
daisy_tecs_path = tecs_cmn.daisy_tecs_path
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = template.SUPPORTED_PARAMS
SUPPORTED_FILTERS = template.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'daisy.common.config', group='image_format')
CONF.import_opt('container_formats', 'daisy.common.config',
group='image_format')
CONF.import_opt('image_property_quota', 'daisy.common.config')
class Controller(controller.BaseController):
"""
WSGI controller for Templates resource in Daisy v1 API
The Templates resource API is a RESTful web Template for Template data. The API
is as follows::
GET /Templates -- Returns a set of brief metadata about Templates
GET /Templates/detail -- Returns a set of detailed metadata about
Templates
HEAD /Templates/<ID> -- Return metadata about an Template with id <ID>
GET /Templates/<ID> -- Return Template data for Template with id <ID>
POST /Templates -- Store Template data and return metadata about the
newly-stored Template
PUT /Templates/<ID> -- Update Template metadata and/or upload Template
data for a previously-reserved Template
DELETE /Templates/<ID> -- Delete the Template with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
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
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
def _raise_404_if_cluster_deleted(self, req, cluster_id):
cluster = self.get_cluster_meta_or_404(req, cluster_id)
if cluster['deleted']:
msg = _("Cluster with identifier %s has been deleted.") % cluster_id
raise webob.exc.HTTPNotFound(msg)
@utils.mutating
def add_template(self, req, template):
"""
Adds a new cluster template to Daisy.
:param req: The WSGI/Webob Request object
:param image_meta: Mapping of metadata about Template
:raises HTTPBadRequest if x-Template-name is missing
"""
self._enforce(req, 'add_template')
template_name = template["name"]
template = registry.add_template_metadata(req.context, template)
return {'template': template}
@utils.mutating
def update_template(self, req, template_id, template):
"""
Updates an existing Template with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'update_template')
try:
template = registry.update_template_metadata(req.context,
template_id,
template)
except exception.Invalid as e:
msg = (_("Failed to update template metadata. Got error: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPBadRequest(explanation=msg,
request=req,
content_type="text/plain")
except exception.NotFound as e:
msg = (_("Failed to find template to update: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to update template: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except (exception.Conflict, exception.Duplicate) as e:
LOG.warn(utils.exception_to_str(e))
raise HTTPConflict(body=_('template operation conflicts'),
request=req,
content_type='text/plain')
else:
self.notifier.info('template.update', template)
return {'template': template}
@utils.mutating
def delete_template(self, req, template_id):
"""
delete a existing cluster template with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifier
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'delete_template')
try:
registry.delete_template_metadata(req.context, template_id)
except exception.NotFound as e:
msg = (_("Failed to find template to delete: %s") %
utils.exception_to_str(e))
LOG.error(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to delete template: %s") %
utils.exception_to_str(e))
LOG.error(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("template %(id)s could not be deleted because it is in use: "
"%(exc)s") % {"id": template_id, "exc": utils.exception_to_str(e)})
LOG.error(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
return Response(body='', status=200)
def _del_general_params(self,param):
del param['created_at']
del param['updated_at']
del param['deleted']
del param['deleted_at']
del param['id']
def _del_cluster_params(self,cluster):
del cluster['networks']
del cluster['vlan_start']
del cluster['vlan_end']
del cluster['vni_start']
del cluster['vni_end']
del cluster['gre_id_start']
del cluster['gre_id_end']
del cluster['net_l23_provider']
del cluster['public_vip']
del cluster['segmentation_type']
del cluster['base_mac']
del cluster['name']
@utils.mutating
def export_db_to_json(self, req, template):
"""
Template TECS to a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-Template-cluster is missing
"""
cluster_name = template.get('cluster_name',None)
type = template.get('type',None)
description = template.get('description',None)
template_name = template.get('template_name',None)
self._enforce(req, 'export_db_to_json')
cinder_volume_list = []
template_content = {}
template_json = {}
template_id = ""
if not type or type == "tecs":
try:
params = {'filters': {'name':cluster_name}}
clusters = registry.get_clusters_detail(req.context, **params)
if clusters:
cluster_id = clusters[0]['id']
else:
msg = "the cluster %s is not exist"%cluster_name
LOG.error(msg)
raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain")
params = {'filters': {'cluster_id':cluster_id}}
cluster = registry.get_cluster_metadata(req.context, cluster_id)
roles = registry.get_roles_detail(req.context, **params)
networks = registry.get_networks_detail(req.context, cluster_id,**params)
for role in roles:
cinder_volume_params = {'filters': {'role_id':role['id']}}
cinder_volumes = registry.list_cinder_volume_metadata(req.context, **cinder_volume_params)
for cinder_volume in cinder_volumes:
if cinder_volume.get('role_id',None):
cinder_volume['role_id'] = role['name']
self._del_general_params(cinder_volume)
cinder_volume_list.append(cinder_volume)
if role.get('config_set_id',None):
config_set = registry.get_config_set_metadata(req.context, role['config_set_id'])
role['config_set_id'] = config_set['name']
del role['cluster_id']
del role['status']
del role['progress']
del role['messages']
del role['config_set_update_progress']
self._del_general_params(role)
for network in networks:
network_detail = registry.get_network_metadata(req.context, network['id'])
if network_detail.get('ip_ranges',None):
network['ip_ranges'] = network_detail['ip_ranges']
del network['cluster_id']
self._del_general_params(network)
if cluster.get('routers',None):
for router in cluster['routers']:
del router['cluster_id']
self._del_general_params(router)
if cluster.get('logic_networks',None):
for logic_network in cluster['logic_networks']:
for subnet in logic_network['subnets']:
del subnet['logic_network_id']
del subnet['router_id']
self._del_general_params(subnet)
del logic_network['cluster_id']
self._del_general_params(logic_network)
if cluster.get('nodes',None):
del cluster['nodes']
self._del_general_params(cluster)
self._del_cluster_params(cluster)
template_content['cluster'] = cluster
template_content['roles'] = roles
template_content['networks'] = networks
template_content['cinder_volumes'] = cinder_volume_list
template_json['content'] = json.dumps(template_content)
template_json['type'] = 'tecs'
template_json['name'] = template_name
template_json['description'] = description
template_host_params = {'cluster_name':cluster_name}
template_hosts = registry.host_template_lists_metadata(req.context, **template_host_params)
if template_hosts:
template_json['hosts'] = template_hosts[0]['hosts']
else:
template_json['hosts'] = "[]"
template_params = {'filters': {'name':template_name}}
template_list = registry.template_lists_metadata(req.context, **template_params)
if template_list:
update_template = registry.update_template_metadata(req.context, template_list[0]['id'], template_json)
template_id = template_list[0]['id']
else:
add_template = registry.add_template_metadata(req.context, template_json)
template_id = add_template['id']
if template_id:
template_detail = registry.template_detail_metadata(req.context, template_id)
self._del_general_params(template_detail)
template_detail['content'] = json.loads(template_detail['content'])
if template_detail['hosts']:
template_detail['hosts'] = json.loads(template_detail['hosts'])
tecs_json = daisy_tecs_path + "%s.json"%template_name
cmd = 'rm -rf %s' % (tecs_json,)
daisy_cmn.subprocess_call(cmd)
with open(tecs_json, "w+") as fp:
fp.write(json.dumps(template_detail))
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return {"template":template_detail}
@utils.mutating
def import_json_to_template(self, req, template):
template_id = ""
template = json.loads(template.get('template',None))
template_cluster = copy.deepcopy(template)
template_name = template_cluster.get('name',None)
template_params = {'filters': {'name':template_name}}
try:
if template_cluster.get('content',None):
template_cluster['content'] = json.dumps(template_cluster['content'])
if template_cluster.get('hosts',None):
template_cluster['hosts'] = json.dumps(template_cluster['hosts'])
else:
template_cluster['hosts'] = "[]"
template_list = registry.template_lists_metadata(req.context, **template_params)
if template_list:
update_template_cluster = registry.update_template_metadata(req.context, template_list[0]['id'], template_cluster)
template_id = template_list[0]['id']
else:
add_template_cluster = registry.add_template_metadata(req.context, template_cluster)
template_id = add_template_cluster['id']
if template_id:
template_detail = registry.template_detail_metadata(req.context, template_id)
del template_detail['deleted']
del template_detail['deleted_at']
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return {"template":template_detail}
@utils.mutating
def import_template_to_db(self, req, template):
cluster_id = ""
template_cluster = {}
cluster_meta = {}
template_meta = copy.deepcopy(template)
template_name = template_meta.get('name',None)
cluster_name = template_meta.get('cluster',None)
template_params = {'filters': {'name':template_name}}
template_list = registry.template_lists_metadata(req.context, **template_params)
if template_list:
template_cluster = template_list[0]
else:
msg = "the template %s is not exist" % template_name
LOG.error(msg)
raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain")
try:
template_content = json.loads(template_cluster['content'])
template_content_cluster = template_content['cluster']
template_content_cluster['name'] = cluster_name
template_content_cluster['networking_parameters'] = str(template_content_cluster['networking_parameters'])
template_content_cluster['logic_networks'] = str(template_content_cluster['logic_networks'])
template_content_cluster['logic_networks'] = template_content_cluster['logic_networks'].replace("\'true\'","True")
template_content_cluster['routers'] = str(template_content_cluster['routers'])
if template_cluster['hosts']:
template_hosts = json.loads(template_cluster['hosts'])
template_host_params = {'cluster_name':cluster_name}
template_host_list = registry.host_template_lists_metadata(req.context, **template_host_params)
if template_host_list:
update_template_meta = {"cluster_name": cluster_name, "hosts":json.dumps(template_hosts)}
registry.update_host_template_metadata(req.context, template_host_list[0]['id'], update_template_meta)
else:
template_meta = {"cluster_name": cluster_name, "hosts":json.dumps(template_hosts)}
registry.add_host_template_metadata(req.context, template_meta)
cluster_params = {'filters': {'name':cluster_name}}
clusters = registry.get_clusters_detail(req.context, **cluster_params)
if clusters:
msg = "the cluster %s is exist" % clusters[0]['name']
LOG.error(msg)
raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain")
else:
cluster_meta = registry.add_cluster_metadata(req.context, template_content['cluster'])
cluster_id = cluster_meta['id']
params = {'filters':{}}
networks = registry.get_networks_detail(req.context, cluster_id,**params)
template_content_networks = template_content['networks']
for template_content_network in template_content_networks:
template_content_network['ip_ranges'] = str(template_content_network['ip_ranges'])
network_exist = 'false'
for network in networks:
if template_content_network['name'] == network['name']:
update_network_meta = registry.update_network_metadata(req.context, network['id'], template_content_network)
network_exist = 'true'
if network_exist == 'false':
template_content_network['cluster_id'] = cluster_id
add_network_meta = registry.add_network_metadata(req.context, template_content_network)
params = {'filters': {'cluster_id':cluster_id}}
roles = registry.get_roles_detail(req.context, **params)
template_content_roles = template_content['roles']
for template_content_role in template_content_roles:
role_exist = 'false'
del template_content_role['config_set_id']
for role in roles:
if template_content_role['name'] == role['name']:
update_role_meta = registry.update_role_metadata(req.context, role['id'], template_content_role)
role_exist = 'true'
if role_exist == 'false':
template_content_role['cluster_id'] = cluster_id
add_role_meta = registry.add_role_metadata(req.context, template_content_role)
cinder_volumes = registry.list_cinder_volume_metadata(req.context, **params)
template_content_cinder_volumes = template_content['cinder_volumes']
for template_content_cinder_volume in template_content_cinder_volumes:
cinder_volume_exist = 'false'
roles = registry.get_roles_detail(req.context, **params)
for role in roles:
if template_content_cinder_volume['role_id'] == role['name']:
template_content_cinder_volume['role_id'] = role['id']
for cinder_volume in cinder_volumes:
if template_content_cinder_volume['role_id'] == cinder_volume['role_id']:
update_cinder_volume_meta = registry.update_cinder_volume_metadata(req.context, cinder_volume['id'], template_content_cinder_volume)
cinder_volume_exist = 'true'
if cinder_volume_exist == 'false':
add_cinder_volumes = registry.add_cinder_volume_metadata(req.context, template_content_cinder_volume)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return {"template":cluster_meta}
@utils.mutating
def get_template_detail(self, req, template_id):
"""
delete a existing cluster template with the registry.
:param request: The WSGI/Webob Request object
:param id: The opaque image identifie
:retval Returns the updated image information as a mapping
"""
self._enforce(req, 'get_template_detail')
try:
template = registry.template_detail_metadata(req.context, template_id)
return {'template': template}
except exception.NotFound as e:
msg = (_("Failed to find template: %s") %
utils.exception_to_str(e))
LOG.error(msg)
raise HTTPNotFound(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden to get template: %s") %
utils.exception_to_str(e))
LOG.error(msg)
raise HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except exception.InUseByStore as e:
msg = (_("template %(id)s could not be get because it is in use: "
"%(exc)s") % {"id": template_id, "exc": utils.exception_to_str(e)})
LOG.error(msg)
raise HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
else:
return Response(body='', status=200)
@utils.mutating
def get_template_lists(self, req):
self._enforce(req, 'get_template_lists')
params = self._get_query_params(req)
try:
template_lists = registry.template_lists_metadata(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(template=template_lists)
class TemplateDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["template"] = utils.get_template_meta(request)
return result
def add_template(self, request):
return self._deserialize(request)
def update_template(self, request):
return self._deserialize(request)
def export_db_to_json(self, request):
return self._deserialize(request)
def import_json_to_template(self, request):
return self._deserialize(request)
def import_template_to_db(self, request):
return self._deserialize(request)
class TemplateSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def add_template(self, response, result):
template = result['template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(template=template))
return response
def delete_template(self, response, result):
template = result['template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(template=template))
return response
def get_template_detail(self, response, result):
template = result['template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(template=template))
return response
def update_template(self, response, result):
template = result['template']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(template=template))
return response
def export_db_to_json(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def import_json_to_template(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def import_template_to_db(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def create_resource():
"""Templates resource factory method"""
deserializer = TemplateDeserializer()
serializer = TemplateSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)

View File

@@ -0,0 +1,289 @@
# Copyright 2013 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.
import glance_store as store_api
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
import webob.exc
from daisy.common import exception
from daisy.common import store_utils
from daisy.common import utils
import daisy.db
from daisy import i18n
import daisy.registry.client.v1.api as registry
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
def initiate_deletion(req, location_data, id):
"""
Deletes image data from the location of backend store.
:param req: The WSGI/Webob Request object
:param location_data: Location to the image data in a data store
:param id: Opaque image identifier
"""
store_utils.delete_image_location_from_backend(req.context,
id, location_data)
def _kill(req, image_id, from_state):
"""
Marks the image status to `killed`.
:param req: The WSGI/Webob Request object
:param image_id: Opaque image identifier
:param from_state: Permitted current status for transition to 'killed'
"""
# TODO(dosaboy): http://docs.openstack.org/developer/glance/statuses.html
# needs updating to reflect the fact that queued->killed and saving->killed
# are both allowed.
registry.update_image_metadata(req.context, image_id,
{'status': 'killed'},
from_state=from_state)
def safe_kill(req, image_id, from_state):
"""
Mark image killed without raising exceptions if it fails.
Since _kill is meant to be called from exceptions handlers, it should
not raise itself, rather it should just log its error.
:param req: The WSGI/Webob Request object
:param image_id: Opaque image identifier
:param from_state: Permitted current status for transition to 'killed'
"""
try:
_kill(req, image_id, from_state)
except Exception:
LOG.exception(_LE("Unable to kill image %(id)s: ") % {'id': image_id})
def upload_data_to_store(req, image_meta, image_data, store, notifier):
"""
Upload image data to specified store.
Upload image data to the store and cleans up on error.
"""
image_id = image_meta['id']
db_api = daisy.db.get_api()
image_size = image_meta.get('size')
try:
# By default image_data will be passed as CooperativeReader object.
# But if 'user_storage_quota' is enabled and 'remaining' is not None
# then it will be passed as object of LimitingReader to
# 'store_add_to_backend' method.
image_data = utils.CooperativeReader(image_data)
remaining = daisy.api.common.check_quota(
req.context, image_size, db_api, image_id=image_id)
if remaining is not None:
image_data = utils.LimitingReader(image_data, remaining)
(uri,
size,
checksum,
location_metadata) = store_api.store_add_to_backend(
image_meta['id'],
image_data,
image_meta['size'],
store,
context=req.context)
location_data = {'url': uri,
'metadata': location_metadata,
'status': 'active'}
try:
# recheck the quota in case there were simultaneous uploads that
# did not provide the size
daisy.api.common.check_quota(
req.context, size, db_api, image_id=image_id)
except exception.StorageQuotaFull:
with excutils.save_and_reraise_exception():
LOG.info(_LI('Cleaning up %s after exceeding '
'the quota') % image_id)
store_utils.safe_delete_from_backend(
req.context, image_meta['id'], location_data)
def _kill_mismatched(image_meta, attr, actual):
supplied = image_meta.get(attr)
if supplied and supplied != actual:
msg = (_("Supplied %(attr)s (%(supplied)s) and "
"%(attr)s generated from uploaded image "
"(%(actual)s) did not match. Setting image "
"status to 'killed'.") % {'attr': attr,
'supplied': supplied,
'actual': actual})
LOG.error(msg)
safe_kill(req, image_id, 'saving')
initiate_deletion(req, location_data, image_id)
raise webob.exc.HTTPBadRequest(explanation=msg,
content_type="text/plain",
request=req)
# Verify any supplied size/checksum value matches size/checksum
# returned from store when adding image
_kill_mismatched(image_meta, 'size', size)
_kill_mismatched(image_meta, 'checksum', checksum)
# Update the database with the checksum returned
# from the backend store
LOG.debug("Updating image %(image_id)s data. "
"Checksum set to %(checksum)s, size set "
"to %(size)d", {'image_id': image_id,
'checksum': checksum,
'size': size})
update_data = {'checksum': checksum,
'size': size}
try:
try:
state = 'saving'
image_meta = registry.update_image_metadata(req.context,
image_id,
update_data,
from_state=state)
except exception.Duplicate:
image = registry.get_image_metadata(req.context, image_id)
if image['status'] == 'deleted':
raise exception.NotFound()
else:
raise
except exception.NotFound:
msg = _LI("Image %s could not be found after upload. The image may"
" have been deleted during the upload.") % image_id
LOG.info(msg)
# NOTE(jculp): we need to clean up the datastore if an image
# resource is deleted while the image data is being uploaded
#
# We get "location_data" from above call to store.add(), any
# exceptions that occur there handle this same issue internally,
# Since this is store-agnostic, should apply to all stores.
initiate_deletion(req, location_data, image_id)
raise webob.exc.HTTPPreconditionFailed(explanation=msg,
request=req,
content_type='text/plain')
except store_api.StoreAddDisabled:
msg = _("Error in store configuration. Adding images to store "
"is disabled.")
LOG.exception(msg)
safe_kill(req, image_id, 'saving')
notifier.error('image.upload', msg)
raise webob.exc.HTTPGone(explanation=msg, request=req,
content_type='text/plain')
except exception.Duplicate as e:
msg = (_("Attempt to upload duplicate image: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
# NOTE(dosaboy): do not delete the image since it is likely that this
# conflict is a result of another concurrent upload that will be
# successful.
notifier.error('image.upload', msg)
raise webob.exc.HTTPConflict(explanation=msg,
request=req,
content_type="text/plain")
except exception.Forbidden as e:
msg = (_("Forbidden upload attempt: %s") %
utils.exception_to_str(e))
LOG.warn(msg)
safe_kill(req, image_id, 'saving')
notifier.error('image.upload', msg)
raise webob.exc.HTTPForbidden(explanation=msg,
request=req,
content_type="text/plain")
except store_api.StorageFull as e:
msg = (_("Image storage media is full: %s") %
utils.exception_to_str(e))
LOG.error(msg)
safe_kill(req, image_id, 'saving')
notifier.error('image.upload', msg)
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
request=req,
content_type='text/plain')
except store_api.StorageWriteDenied as e:
msg = (_("Insufficient permissions on image storage media: %s") %
utils.exception_to_str(e))
LOG.error(msg)
safe_kill(req, image_id, 'saving')
notifier.error('image.upload', msg)
raise webob.exc.HTTPServiceUnavailable(explanation=msg,
request=req,
content_type='text/plain')
except exception.ImageSizeLimitExceeded as e:
msg = (_("Denying attempt to upload image larger than %d bytes.")
% CONF.image_size_cap)
LOG.warn(msg)
safe_kill(req, image_id, 'saving')
notifier.error('image.upload', msg)
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
request=req,
content_type='text/plain')
except exception.StorageQuotaFull as e:
msg = (_("Denying attempt to upload image because it exceeds the "
"quota: %s") % utils.exception_to_str(e))
LOG.warn(msg)
safe_kill(req, image_id, 'saving')
notifier.error('image.upload', msg)
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
request=req,
content_type='text/plain')
except webob.exc.HTTPError:
# NOTE(bcwaldon): Ideally, we would just call 'raise' here,
# but something in the above function calls is affecting the
# exception context and we must explicitly re-raise the
# caught exception.
msg = _LE("Received HTTP error while uploading image %s") % image_id
notifier.error('image.upload', msg)
with excutils.save_and_reraise_exception():
LOG.exception(msg)
safe_kill(req, image_id, 'saving')
except (ValueError, IOError) as e:
msg = _("Client disconnected before sending all data to backend")
LOG.warn(msg)
safe_kill(req, image_id, 'saving')
raise webob.exc.HTTPBadRequest(explanation=msg,
content_type="text/plain",
request=req)
except Exception as e:
msg = _("Failed to upload image %s") % image_id
LOG.exception(msg)
safe_kill(req, image_id, 'saving')
notifier.error('image.upload', msg)
raise webob.exc.HTTPInternalServerError(explanation=msg,
request=req,
content_type='text/plain')
return image_meta, location_data