diff options
| author | Stephen Gran <stephen.gran@guardian.co.uk> | 2013-03-01 08:22:16 +0000 |
|---|---|---|
| committer | Stephen Gran <stephen.gran@guardian.co.uk> | 2013-03-28 08:45:13 +0000 |
| commit | 55a04a4bc3228e698bb84a641d50507810ae9a02 (patch) | |
| tree | e09657d9e16f81a9f314d78a014ee6222cd59b03 /nova/api | |
| parent | 7bf541cc907bd0e4c881a1bdbd6a14fd7146a5f9 (diff) | |
| download | nova-55a04a4bc3228e698bb84a641d50507810ae9a02.tar.gz nova-55a04a4bc3228e698bb84a641d50507810ae9a02.tar.xz nova-55a04a4bc3228e698bb84a641d50507810ae9a02.zip | |
Add CRUD methods for tags to the EC2 API.
This is an incomplete implementation of the EC2 tags API. In EC2, most
resources are able to be tagged. See
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html
In openstack, the only currently 'taggable' resource is an instance, as
it has an instance_metadata table associated. So far, only instance
tagging has been implemented, but it is relatively simple to extend this
to other resource types by creating the associated model and api calls.
Additionally, in EC2 searches, shell-style globs are allowed, eg:
fo* will match fo, foo, foobar
fo? will match foo
This has been left to do at a later date.
DocImpact: Adds new API calls: CreateTags, DeleteTags, DescribeTags
See:
http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateTags.html
http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteTags.html
http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeTags.html
Fixes: bug #1096821
Implements: blueprint ec2-tags-api
Change-Id: Idf1108f6a3476cabdbdb32ff41c00aa4bc2d9ffe
Signed-off-by: Stephen Gran <stephen.gran@guardian.co.uk>
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/ec2/cloud.py | 135 | ||||
| -rw-r--r-- | nova/api/ec2/ec2utils.py | 25 |
2 files changed, 160 insertions, 0 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 0d06dde33..3d210404c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1174,6 +1174,10 @@ class CloudController(object): i['ipAddress'] = floating_ip or fixed_ip i['dnsName'] = i['publicDnsName'] or i['privateDnsName'] i['keyName'] = instance['key_name'] + i['tagSet'] = [] + for k, v in self.compute_api.get_instance_metadata( + context, instance).iteritems(): + i['tagSet'].append({'key': k, 'value': v}) if context.is_admin: i['keyName'] = '%s (%s, %s)' % (i['keyName'], @@ -1677,6 +1681,137 @@ class CloudController(object): return {'imageId': ec2_id} + def create_tags(self, context, **kwargs): + """Add tags to a resource + + Returns True on success, error on failure. + + :param context: context under which the method is called + """ + resources = kwargs.get('resource_id', None) + tags = kwargs.get('tag', None) + if resources is None or tags is None: + raise exception.EC2APIError(_('resource_id and tag are required')) + + if not isinstance(resources, (tuple, list, set)): + raise exception.EC2APIError(_('Expecting a list of resources')) + + for r in resources: + if ec2utils.resource_type_from_id(context, r) != 'instance': + raise exception.EC2APIError(_('Only instances implemented')) + + if not isinstance(tags, (tuple, list, set)): + raise exception.EC2APIError(_('Expecting a list of tagSets')) + + metadata = {} + for tag in tags: + if not isinstance(tag, dict): + raise exception.EC2APIError(_ + ('Expecting tagSet to be key/value pairs')) + + key = tag.get('key', None) + val = tag.get('value', None) + + if key is None or val is None: + raise exception.EC2APIError(_ + ('Expecting both key and value to be set')) + + 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) + self.compute_api.update_instance_metadata(context, + instance, metadata) + + return True + + def delete_tags(self, context, **kwargs): + """Delete tags + + Returns True on success, error on failure. + + :param context: context under which the method is called + """ + resources = kwargs.get('resource_id', None) + tags = kwargs.get('tag', None) + if resources is None or tags is None: + raise exception.EC2APIError(_('resource_id and tag are required')) + + if not isinstance(resources, (tuple, list, set)): + raise exception.EC2APIError(_('Expecting a list of resources')) + + for r in resources: + if ec2utils.resource_type_from_id(context, r) != 'instance': + raise exception.EC2APIError(_('Only instances implemented')) + + if not isinstance(tags, (tuple, list, set)): + raise exception.EC2APIError(_('Expecting a list of tagSets')) + + for ec2_id in resources: + instance_uuid = ec2utils.ec2_inst_id_to_uuid(context, ec2_id) + instance = self.compute_api.get(context, instance_uuid) + for tag in tags: + if not isinstance(tag, dict): + raise exception.EC2APIError(_ + ('Expecting tagSet to be key/value pairs')) + + key = tag.get('key', None) + if key is None: + raise exception.EC2APIError(_('Expecting key to be set')) + + self.compute_api.delete_instance_metadata(context, + instance, key) + + return True + + def describe_tags(self, context, **kwargs): + """List tags + + Returns a dict with a single key 'tagSet' on success, error on failure. + + :param context: context under which the method is called + """ + filters = kwargs.get('filter', None) + + search_filts = [] + if filters: + for filter_block in filters: + key_name = filter_block.get('name', None) + val = filter_block.get('value', None) + if val: + if isinstance(val, dict): + val = val.values() + if not isinstance(val, (tuple, list, set)): + val = (val,) + if key_name: + search_block = {} + if key_name == '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] = val + elif key_name == 'resource_type': + for res_type in val: + if res_type != 'instance': + raise exception.EC2APIError(_ + ('Only instances implemented')) + search_block[key_name] = 'instance' + if len(search_block.keys()) > 0: + search_filts.append(search_block) + 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'] + }) + return {"tagSet": ts} + class EC2SecurityGroupExceptions(object): @staticmethod diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 1f4b8d6ee..8a73533c6 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -72,6 +72,31 @@ def image_type(image_type): return image_type +def resource_type_from_id(context, resource_id): + """Get resource type by ID + + Returns a string representation of the Amazon resource type, if known. + Returns None on failure. + + :param context: context under which the method is called + :param resource_id: resource_id to evaluate + """ + + known_types = { + 'i': 'instance', + 'r': 'reservation', + 'vol': 'volume', + 'snap': 'snapshot', + 'ami': 'image', + 'aki': 'image', + 'ari': 'image' + } + + type_marker = resource_id.split('-')[0] + + return known_types.get(type_marker) + + @memoize def id_to_glance_id(context, image_id): """Convert an internal (db) id to a glance id.""" |
