initial merge
Change-Id: Id7cef7826092e191654da872ee1e11c4c6f50ddf Signed-off-by: Zhijiang Hu <hu.zhijiang@zte.com.cn>
This commit is contained in:
23
code/daisy/daisy/api/v1/__init__.py
Executable file
23
code/daisy/daisy/api/v1/__init__.py
Executable 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')
|
||||
742
code/daisy/daisy/api/v1/clusters.py
Executable file
742
code/daisy/daisy/api/v1/clusters.py
Executable 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)
|
||||
|
||||
328
code/daisy/daisy/api/v1/components.py
Executable file
328
code/daisy/daisy/api/v1/components.py
Executable 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)
|
||||
|
||||
325
code/daisy/daisy/api/v1/config_files.py
Executable file
325
code/daisy/daisy/api/v1/config_files.py
Executable 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)
|
||||
|
||||
434
code/daisy/daisy/api/v1/config_sets.py
Executable file
434
code/daisy/daisy/api/v1/config_sets.py
Executable 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)
|
||||
|
||||
301
code/daisy/daisy/api/v1/configs.py
Executable file
301
code/daisy/daisy/api/v1/configs.py
Executable 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)
|
||||
|
||||
369
code/daisy/daisy/api/v1/controller.py
Executable file
369
code/daisy/daisy/api/v1/controller.py
Executable 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')
|
||||
668
code/daisy/daisy/api/v1/disk_array.py
Executable file
668
code/daisy/daisy/api/v1/disk_array.py
Executable 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)
|
||||
40
code/daisy/daisy/api/v1/filters.py
Executable file
40
code/daisy/daisy/api/v1/filters.py
Executable 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, }
|
||||
569
code/daisy/daisy/api/v1/host_template.py
Executable file
569
code/daisy/daisy/api/v1/host_template.py
Executable 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
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
1264
code/daisy/daisy/api/v1/images.py
Executable file
File diff suppressed because it is too large
Load Diff
405
code/daisy/daisy/api/v1/install.py
Executable file
405
code/daisy/daisy/api/v1/install.py
Executable 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)
|
||||
278
code/daisy/daisy/api/v1/members.py
Executable file
278
code/daisy/daisy/api/v1/members.py
Executable 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)
|
||||
691
code/daisy/daisy/api/v1/networks.py
Executable file
691
code/daisy/daisy/api/v1/networks.py
Executable 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
782
code/daisy/daisy/api/v1/roles.py
Executable 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
574
code/daisy/daisy/api/v1/router.py
Executable 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)
|
||||
|
||||
|
||||
334
code/daisy/daisy/api/v1/services.py
Executable file
334
code/daisy/daisy/api/v1/services.py
Executable 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)
|
||||
629
code/daisy/daisy/api/v1/template.py
Executable file
629
code/daisy/daisy/api/v1/template.py
Executable 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)
|
||||
289
code/daisy/daisy/api/v1/upload_utils.py
Executable file
289
code/daisy/daisy/api/v1/upload_utils.py
Executable 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
|
||||
Reference in New Issue
Block a user