summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorStephen Gran <stephen.gran@guardian.co.uk>2013-03-01 08:22:16 +0000
committerStephen Gran <stephen.gran@guardian.co.uk>2013-03-28 08:45:13 +0000
commit55a04a4bc3228e698bb84a641d50507810ae9a02 (patch)
treee09657d9e16f81a9f314d78a014ee6222cd59b03 /nova/api
parent7bf541cc907bd0e4c881a1bdbd6a14fd7146a5f9 (diff)
downloadnova-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.py135
-rw-r--r--nova/api/ec2/ec2utils.py25
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."""