EC2: Reorganize code for EC2 tags
This change makes the EC2 tags' code more generic, so that tags for new resources can be added easily. Progress on: Blueprint: ec2-volume-and-snapshot-tags Change-Id: Iba774d55c84dbbef57c56cd331b3ffcd7e41f3ec
This commit is contained in:
parent
f48827dcfa
commit
fc05ab545c
|
@ -208,6 +208,9 @@ def _format_mappings(properties, result):
|
|||
result['blockDeviceMapping'] = mappings
|
||||
|
||||
|
||||
_TAGS_SUPPORTED_RESOURCE_TYPES = ['instance']
|
||||
|
||||
|
||||
class CloudController(object):
|
||||
"""CloudController provides the critical dispatch between
|
||||
inbound API calls through the endpoint and messages
|
||||
|
@ -1785,8 +1788,9 @@ class CloudController(object):
|
|||
raise exception.InvalidParameterValue(message=msg)
|
||||
|
||||
for r in resources:
|
||||
if ec2utils.resource_type_from_id(context, r) != 'instance':
|
||||
msg = _('Only instances implemented')
|
||||
res_type = ec2utils.resource_type_from_id(context, r)
|
||||
if res_type not in _TAGS_SUPPORTED_RESOURCE_TYPES:
|
||||
msg = _('Tags not implemented for %s.') % res_type
|
||||
raise exception.InvalidParameterValue(message=msg)
|
||||
|
||||
if not isinstance(tags, (tuple, list, set)):
|
||||
|
@ -1808,12 +1812,14 @@ class CloudController(object):
|
|||
|
||||
metadata[key] = val
|
||||
|
||||
for ec2_id in resources:
|
||||
instance_uuid = ec2utils.ec2_inst_id_to_uuid(context, ec2_id)
|
||||
instance = self.compute_api.get(context, instance_uuid,
|
||||
want_objects=True)
|
||||
self.compute_api.update_instance_metadata(context,
|
||||
instance, metadata)
|
||||
for res_id in resources:
|
||||
res_type = ec2utils.resource_type_from_id(context, res_id)
|
||||
if res_type == 'instance':
|
||||
instance_uuid = ec2utils.ec2_inst_id_to_uuid(context, res_id)
|
||||
instance = self.compute_api.get(context, instance_uuid,
|
||||
want_objects=True)
|
||||
self.compute_api.update_instance_metadata(context,
|
||||
instance, metadata)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -1835,30 +1841,35 @@ class CloudController(object):
|
|||
raise exception.InvalidParameterValue(message=msg)
|
||||
|
||||
for r in resources:
|
||||
if ec2utils.resource_type_from_id(context, r) != 'instance':
|
||||
msg = _('Only instances implemented')
|
||||
res_type = ec2utils.resource_type_from_id(context, r)
|
||||
if res_type not in _TAGS_SUPPORTED_RESOURCE_TYPES:
|
||||
msg = _('Tags not implemented for %s.') % res_type
|
||||
raise exception.InvalidParameterValue(message=msg)
|
||||
|
||||
if not isinstance(tags, (tuple, list, set)):
|
||||
msg = _('Expecting a list of tagSets')
|
||||
raise exception.InvalidParameterValue(message=msg)
|
||||
|
||||
for ec2_id in resources:
|
||||
instance_uuid = ec2utils.ec2_inst_id_to_uuid(context, ec2_id)
|
||||
instance = self.compute_api.get(context, instance_uuid,
|
||||
want_objects=True)
|
||||
def _ensure_tags_correct(tags):
|
||||
for tag in tags:
|
||||
if not isinstance(tag, dict):
|
||||
msg = _('Expecting tagSet to be key/value pairs')
|
||||
raise exception.InvalidParameterValue(message=msg)
|
||||
|
||||
key = tag.get('key', None)
|
||||
if key is None:
|
||||
if tag.get('key') is None:
|
||||
msg = _('Expecting key to be set')
|
||||
raise exception.InvalidParameterValue(message=msg)
|
||||
|
||||
self.compute_api.delete_instance_metadata(context,
|
||||
instance, key)
|
||||
for res_id in resources:
|
||||
res_type = ec2utils.resource_type_from_id(context, res_id)
|
||||
if res_type == 'instance':
|
||||
instance_uuid = ec2utils.ec2_inst_id_to_uuid(context, res_id)
|
||||
instance = self.compute_api.get(context, instance_uuid,
|
||||
want_objects=True)
|
||||
_ensure_tags_correct(tags)
|
||||
for tag in tags:
|
||||
self.compute_api.delete_instance_metadata(context,
|
||||
instance, tag.get('key'))
|
||||
|
||||
return True
|
||||
|
||||
|
@ -1870,7 +1881,31 @@ class CloudController(object):
|
|||
:param context: context under which the method is called
|
||||
"""
|
||||
filters = kwargs.get('filter', None)
|
||||
search_filts = []
|
||||
|
||||
valid_resources = set(['customer-gateway', 'dhcp-options', 'image',
|
||||
'instance', 'internet-gateway', 'network-acl',
|
||||
'network-interface', 'reserved-instances',
|
||||
'route-table', 'security-group', 'snapshot',
|
||||
'spot-instances-request', 'subnet', 'volume',
|
||||
'vpc', 'vpn-connection', 'vpn-gateway'])
|
||||
|
||||
implemented_resources = set(_TAGS_SUPPORTED_RESOURCE_TYPES)
|
||||
|
||||
# NOTE(rushiagr): example of the above variable 'filters', for ref:
|
||||
# [{'name': 'resource-id', 'value': {'1': 'i-00000001'}},
|
||||
# {'name': 'resource-type', 'value': {'1': 'instance'}},
|
||||
# {'name': 'value', 'value': {'1': 'production', '2': 'beta'}},
|
||||
# {'name': 'key', 'value': {'1': 'stack'}}]
|
||||
|
||||
# Convert the 'filters' list to a dict for better usability. So for
|
||||
# example, the above list will be converted to:
|
||||
# {'resource-id': 'i-00000001',
|
||||
# 'resource-type': 'instance',
|
||||
# 'value': ['production', 'beta'],
|
||||
# 'key': 'stack'}
|
||||
|
||||
filter_dict = {}
|
||||
|
||||
if filters:
|
||||
for filter_block in filters:
|
||||
key_name = filter_block.get('name', None)
|
||||
|
@ -1881,32 +1916,103 @@ class CloudController(object):
|
|||
if not isinstance(val, (tuple, list, set)):
|
||||
val = (val,)
|
||||
if key_name:
|
||||
search_block = {}
|
||||
if key_name in ('resource_id', 'resource-id'):
|
||||
search_block['resource_id'] = []
|
||||
for res_id in val:
|
||||
search_block['resource_id'].append(
|
||||
ec2utils.ec2_inst_id_to_uuid(context, res_id))
|
||||
elif key_name in ['key', 'value']:
|
||||
search_block[key_name] = \
|
||||
if key_name in ['key', 'value']:
|
||||
filter_dict[key_name] = \
|
||||
[ec2utils.regex_from_ec2_regex(v) for v in val]
|
||||
elif key_name in ('resource_type', 'resource-type'):
|
||||
filter_dict['resource_type'] = []
|
||||
for res_type in val:
|
||||
if res_type != 'instance':
|
||||
if (res_type not in implemented_resources or
|
||||
res_type not in valid_resources):
|
||||
raise exception.InvalidParameterValue(
|
||||
message=_('Only instances implemented'))
|
||||
search_block[key_name] = 'instance'
|
||||
if len(search_block.keys()) > 0:
|
||||
search_filts.append(search_block)
|
||||
message=_('Resource %s not '
|
||||
'implemented/valid') % res_type)
|
||||
filter_dict['resource_type'].append(res_type)
|
||||
elif key_name in ('resource_id', 'resource-id'):
|
||||
filter_dict['resource_id'] = []
|
||||
for res_id in val:
|
||||
filter_dict['resource_id'].append(res_id)
|
||||
else:
|
||||
raise exception.InvalidParameterValue(
|
||||
message=_('Invalid key name: %s') % key_name)
|
||||
|
||||
resources_to_query = set()
|
||||
resource_id_dict = {} # Keys are resource types, values resource IDs
|
||||
|
||||
if 'resource_type' not in filter_dict and 'resource_id' in filter_dict:
|
||||
for res_id in filter_dict['resource_id']:
|
||||
res_type = ec2utils.resource_type_from_id(context, res_id)
|
||||
resources_to_query.add(res_type)
|
||||
if res_type not in resource_id_dict:
|
||||
resource_id_dict[res_type] = \
|
||||
[ec2utils.uuid_from_only_resource_id(res_id)]
|
||||
else:
|
||||
resource_id_dict[res_type].append(
|
||||
ec2utils.uuid_from_only_resource_id(res_id))
|
||||
elif ('resource_type' in filter_dict and
|
||||
'resource_id' not in filter_dict):
|
||||
for res_type in filter_dict['resource_type']:
|
||||
resources_to_query.add(res_type)
|
||||
elif 'resource_type' in filter_dict and 'resource_id' in filter_dict:
|
||||
# Say if resource_type has two values 'instance' and 'volume', and
|
||||
# resource_id has two values 'vol-00000001' and 'ami-00000001', we
|
||||
# should only check for volumes and not for instances or images.
|
||||
resources_from_id = set()
|
||||
resources_from_type = set()
|
||||
temp_resource_id_dict = {}
|
||||
for res_id in filter_dict['resource_id']:
|
||||
res_type = ec2utils.resource_type_from_id(context, res_id)
|
||||
resources_from_id.add(res_type)
|
||||
|
||||
if res_type not in temp_resource_id_dict:
|
||||
temp_resource_id_dict[res_type] = \
|
||||
[ec2utils.uuid_from_only_resource_id(res_id)]
|
||||
else:
|
||||
temp_resource_id_dict[res_type].append(
|
||||
ec2utils.uuid_from_only_resource_id(res_id))
|
||||
for res_type in filter_dict['resource_type']:
|
||||
resources_from_type.add(res_type)
|
||||
resources_to_query = resources_from_type.intersection(
|
||||
resources_from_id)
|
||||
|
||||
for res in resources_to_query:
|
||||
resource_id_dict[res] = temp_resource_id_dict[res]
|
||||
else:
|
||||
resources_to_query = implemented_resources
|
||||
|
||||
ts = []
|
||||
for tag in self.compute_api.get_all_instance_metadata(context,
|
||||
search_filts):
|
||||
ts.append({
|
||||
'resource_id': ec2utils.id_to_ec2_inst_id(tag['instance_id']),
|
||||
'resource_type': 'instance',
|
||||
'key': tag['key'],
|
||||
'value': tag['value']
|
||||
})
|
||||
|
||||
if not resources_to_query:
|
||||
LOG.warning(_LW("Specified resource ID(s) %(ids)s does not "
|
||||
"belong to specified resource type(s) %(types)s.") %
|
||||
{'ids': str(filter_dict['resource_id']),
|
||||
'types': str(filter_dict['resource_type'])})
|
||||
return {'tagSet': ts}
|
||||
|
||||
def _search_filters_for(resource_type):
|
||||
"""Prepares search filters for the specified resource."""
|
||||
resource_filter_dict = filter_dict
|
||||
if resource_type in resource_id_dict:
|
||||
resource_filter_dict['resource_id'] = \
|
||||
resource_id_dict[resource_type]
|
||||
if 'resource_type' in resource_filter_dict:
|
||||
resource_filter_dict.pop('resource_type')
|
||||
search_filters = [{k: v} for k, v in
|
||||
resource_filter_dict.iteritems()]
|
||||
return search_filters
|
||||
|
||||
if 'instance' in resources_to_query:
|
||||
tags = self.compute_api.get_all_instance_metadata(context,
|
||||
_search_filters_for('instance'))
|
||||
for tag in tags:
|
||||
ts.append({
|
||||
'resource_id': ec2utils.id_to_ec2_inst_id(
|
||||
tag['instance_id']),
|
||||
'resource_type': 'instance',
|
||||
'key': tag['key'],
|
||||
'value': tag['value']
|
||||
})
|
||||
|
||||
return {"tagSet": ts}
|
||||
|
||||
|
||||
|
|
|
@ -194,8 +194,24 @@ def id_to_ec2_inst_id(instance_id):
|
|||
return id_to_ec2_id(instance_id)
|
||||
|
||||
|
||||
def uuid_from_only_resource_id(res_id):
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
res_type = resource_type_from_id(ctxt, res_id)
|
||||
if res_type == 'volume':
|
||||
return ec2_vol_id_to_uuid(res_id)
|
||||
elif res_type == 'instance':
|
||||
return ec2_inst_id_to_uuid(ctxt, res_id)
|
||||
elif res_type == 'snapshot':
|
||||
return ec2_snap_id_to_uuid(res_id)
|
||||
else:
|
||||
raise exception.InvalidEc2Id(ec2_id=res_id)
|
||||
|
||||
|
||||
def ec2_inst_id_to_uuid(context, ec2_id):
|
||||
""""Convert an instance id to uuid."""
|
||||
if resource_type_from_id(context, ec2_id) != 'instance':
|
||||
raise exception.InvalidEc2Id(ec2_id)
|
||||
int_id = ec2_id_to_id(ec2_id)
|
||||
return get_instance_uuid_from_int_id(context, int_id)
|
||||
|
||||
|
@ -230,6 +246,9 @@ def ec2_vol_id_to_uuid(ec2_id):
|
|||
"""Get the corresponding UUID for the given ec2-id."""
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
if resource_type_from_id(context, ec2_id) != 'volume':
|
||||
raise exception.InvalidEc2Id(ec2_id)
|
||||
|
||||
# NOTE(jgriffith) first strip prefix to get just the numeric
|
||||
int_id = ec2_id_to_id(ec2_id)
|
||||
return get_volume_uuid_from_int_id(ctxt, int_id)
|
||||
|
@ -334,6 +353,9 @@ def ec2_snap_id_to_uuid(ec2_id):
|
|||
"""Get the corresponding UUID for the given ec2-id."""
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
if resource_type_from_id(context, ec2_id) != 'snapshot':
|
||||
raise exception.InvalidEc2Id(ec2_id)
|
||||
|
||||
# NOTE(jgriffith) first strip prefix to get just the numeric
|
||||
int_id = ec2_id_to_id(ec2_id)
|
||||
return get_snapshot_uuid_from_int_id(ctxt, int_id)
|
||||
|
|
|
@ -948,7 +948,7 @@ class CloudTestCase(test.TestCase):
|
|||
|
||||
# A filter with even one invalid id should cause an exception to be
|
||||
# raised
|
||||
self.assertRaises(exception.InstanceNotFound,
|
||||
self.assertRaises(exception.InvalidEc2Id,
|
||||
self.cloud.describe_instances, self.context,
|
||||
instance_id=[instance_id, '435679'])
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ class EC2ValidateTestCase(test.TestCase):
|
|||
self.volume_id_exception_map = [(x,
|
||||
exception.InvalidInstanceIDMalformed)
|
||||
for x in self.EC2_MALFORMED_IDS]
|
||||
self.volume_id_exception_map.extend([(x, exception.VolumeNotFound)
|
||||
self.volume_id_exception_map.extend([(x, exception.InvalidEc2Id)
|
||||
for x in self.EC2_VALID__IDS])
|
||||
|
||||
def fake_show(meh, context, id, **kwargs):
|
||||
|
|
Loading…
Reference in New Issue