diff --git a/ec2api/api/availability_zone.py b/ec2api/api/availability_zone.py index 3d977f61..d282e63d 100644 --- a/ec2api/api/availability_zone.py +++ b/ec2api/api/availability_zone.py @@ -53,6 +53,9 @@ LOG = logging.getLogger(__name__) """ +Validator = common.Validator + + class AvailabilityZoneDescriber(common.UniversalDescriber): KIND = 'az' diff --git a/ec2api/api/cloud.py b/ec2api/api/cloud.py index 8852a916..4ebff48e 100644 --- a/ec2api/api/cloud.py +++ b/ec2api/api/cloud.py @@ -36,7 +36,7 @@ from ec2api.api import route_table from ec2api.api import security_group from ec2api.api import snapshot from ec2api.api import subnet -from ec2api.api import tag as tag_api +from ec2api.api import tag from ec2api.api import volume from ec2api.api import vpc from ec2api import exception @@ -374,16 +374,16 @@ class CloudController(object): This action doesn't apply to security groups for use in EC2-Classic. """ - @module_and_param_types(instance, 'ami_id', 'dummy', 'dummy', + @module_and_param_types(instance, 'ami_id', 'int', 'int', 'str255', 'sg_ids', - 'str255s', 'dummy', 'dummy', + 'security_group_strs', 'str', 'str', 'dummy', 'ami_id', 'ami_id', 'dummy', 'dummy', - 'subnet_id', 'dummy', - 'dummy', + 'subnet_id', 'bool', + 'str', + 'ip', 'str64', 'dummy', 'dummy', - 'dummy', 'dummy', - 'dummy') + 'bool') def run_instances(self, context, image_id, min_count, max_count, key_name=None, security_group_id=None, security_group=None, user_data=None, instance_type=None, @@ -514,7 +514,7 @@ class CloudController(object): """ @module_and_param_types(instance, 'i_ids', 'filter', - 'dummy', 'dummy') + 'int', 'str') def describe_instances(self, context, instance_id=None, filter=None, max_results=None, next_token=None): """Describes one or more of your instances. @@ -551,7 +551,7 @@ class CloudController(object): true if the request succeeds. """ - @module_and_param_types(instance, 'i_ids', 'dummy') + @module_and_param_types(instance, 'i_ids', 'bool') def stop_instances(self, context, instance_id, force=False): """Stops one or more instances. @@ -598,6 +598,7 @@ class CloudController(object): Specified attribute. """ + @module_and_param_types(key_pair, 'str255s', 'filter') def describe_key_pairs(self, context, key_name=None, filter=None): """Describes one or more of your key pairs. @@ -609,8 +610,8 @@ class CloudController(object): Returns: Specified keypairs. """ - return key_pair.describe_key_pairs(context, key_name, filter) + @module_and_param_types(key_pair, 'str255') def create_key_pair(self, context, key_name): """Creates a 2048-bit RSA key pair with the specified name. @@ -621,8 +622,8 @@ class CloudController(object): Returns: Created keypair. """ - return key_pair.create_key_pair(context, key_name) + @module_and_param_types(key_pair, 'str255') def delete_key_pair(self, context, key_name): """Deletes the specified key pair. @@ -633,8 +634,8 @@ class CloudController(object): Returns: Returns true if the request succeeds. """ - return key_pair.delete_key_pair(context, key_name) + @module_and_param_types(key_pair, 'str255', 'str') def import_key_pair(self, context, key_name, public_key_material): """Imports the public key from an existing RSA key pair. @@ -647,9 +648,8 @@ class CloudController(object): Returns: Imported keypair. """ - return key_pair.import_key_pair(context, key_name, - public_key_material) + @module_and_param_types(availability_zone, 'strs', 'filter') def describe_availability_zones(self, context, zone_name=None, filter=None): """Describes one or more of the available Availability Zones. @@ -662,10 +662,8 @@ class CloudController(object): Returns: Specified availability zones. """ - return availability_zone.describe_availability_zones(context, - zone_name, - filter) + @module_and_param_types(availability_zone, 'strs', 'filter') def describe_regions(self, context, region_name=None, filter=None): """Describes one or more regions that are currently available to you. @@ -677,10 +675,8 @@ class CloudController(object): Returns: Specified regions. """ - return availability_zone.describe_regions(context, - region_name, - filter) + @module_and_param_types(instance, 'i_id') def get_password_data(self, context, instance_id): """Retrieves the encrypted administrator password for Windows instance. @@ -694,8 +690,8 @@ class CloudController(object): The password is encrypted using the key pair that you specified when you launched the instance. """ - return instance.get_password_data(context, instance_id) + @module_and_param_types(instance, 'i_id') def get_console_output(self, context, instance_id): """Gets the console output for the specified instance. @@ -706,8 +702,10 @@ class CloudController(object): Returns: The console output of the instance, timestamp and instance id. """ - return instance.get_console_output(context, instance_id) + @module_and_param_types(volume, 'str', 'int', + 'snap_id', 'str', 'int', + 'bool', 'str') def create_volume(self, context, availability_zone=None, size=None, snapshot_id=None, volume_type=None, iops=None, encrypted=None, kms_key_id=None): @@ -741,10 +739,8 @@ class CloudController(object): You can create a new empty volume or restore a volume from an EBS snapshot. """ - return volume.create_volume(context, availability_zone, size, - snapshot_id, volume_type, iops, - encrypted, kms_key_id) + @module_and_param_types(volume, 'vol_id', 'i_id', 'str') def attach_volume(self, context, volume_id, instance_id, device): """Attaches an EBS volume to a running or stopped instance. @@ -759,8 +755,8 @@ class CloudController(object): The instance and volume must be in the same Availability Zone. """ - return volume.attach_volume(context, volume_id, instance_id, device) + @module_and_param_types(volume, 'vol_id', 'i_id', 'str') def detach_volume(self, context, volume_id, instance_id=None, device=None, force=None): """Detaches an EBS volume from an instance. @@ -778,9 +774,8 @@ class CloudController(object): Returns: Information about the detachment. """ - return volume.detach_volume(context, volume_id, instance_id, device, - force) + @module_and_param_types(volume, 'vol_id') def delete_volume(self, context, volume_id): """Deletes the specified EBS volume. @@ -793,8 +788,9 @@ class CloudController(object): The volume must be in the available state. """ - return volume.delete_volume(context, volume_id) + @module_and_param_types(volume, 'vol_ids', 'filter', + 'int', 'str') def describe_volumes(self, context, volume_id=None, filter=None, max_results=None, next_token=None): """Describes the specified EBS volumes. @@ -812,9 +808,8 @@ class CloudController(object): Returns: A list of volumes. """ - return volume.describe_volumes(context, volume_id, filter, - max_results, next_token) + @module_and_param_types(snapshot, 'vol_id', 'str') def create_snapshot(self, context, volume_id, description=None): """Creates a snapshot of an EBS volume. @@ -826,8 +821,8 @@ class CloudController(object): Returns: Information about the snapshot. """ - return snapshot.create_snapshot(context, volume_id, description) + @module_and_param_types(snapshot, 'snap_id') def delete_snapshot(self, context, snapshot_id): """Deletes the specified snapshot. @@ -838,8 +833,9 @@ class CloudController(object): Returns: Returns true if the request succeeds. """ - return snapshot.delete_snapshot(context, snapshot_id) + @module_and_param_types(snapshot, 'snap_ids', 'strs', + 'strs', 'filter') def describe_snapshots(self, context, snapshot_id=None, owner=None, restorable_by=None, filter=None): """Describes one or more of the snapshots available to you. @@ -859,9 +855,9 @@ class CloudController(object): Returns: A list of snapshots. """ - return snapshot.describe_snapshots(context, snapshot_id, owner, - restorable_by, filter) + @module_and_param_types(image, 'i_id', 'str', 'str', + 'bool', 'dummy') def create_image(self, context, instance_id, name=None, description=None, no_reboot=False, block_device_mapping=None): """Creates an EBS-backed AMI from an EBS-backed instance. @@ -900,6 +896,11 @@ class CloudController(object): return image.create_image(context, instance_id, name, description, no_reboot, block_device_mapping) + @module_and_param_types(image, 'str', 'str', + 'str', 'str', + 'str', 'dummy', + 'str', 'ami_id', + 'ami_id', 'str') def register_image(self, context, name=None, image_location=None, description=None, architecture=None, root_device_name=None, block_device_mapping=None, @@ -946,12 +947,8 @@ class CloudController(object): Returns: The ID of the new AMI. """ - return image.register_image(context, name, image_location, - description, architecture, - root_device_name, block_device_mapping, - virtualization_type, kernel_id, - ramdisk_id, sriov_net_support) + @module_and_param_types(image, 'ami_id') def deregister_image(self, context, image_id): """Deregisters the specified AMI. @@ -962,8 +959,9 @@ class CloudController(object): Returns: true if the request succeeds. """ - return image.deregister_image(context, image_id) + @module_and_param_types(image, 'strs', 'ami_ids', + 'strs', 'filter') def describe_images(self, context, executable_by=None, image_id=None, owner=None, filter=None): """Describes one or more of the images available to you. @@ -982,9 +980,8 @@ class CloudController(object): Returns: A list of images. """ - return image.describe_images(context, executable_by, image_id, - owner, filter) + @module_and_param_types(image, 'ami_id', 'str') def describe_image_attribute(self, context, image_id, attribute): """Describes the specified attribute of the specified AMI. @@ -1001,6 +998,10 @@ class CloudController(object): """ return image.describe_image_attribute(context, image_id, attribute) + @module_and_param_types(image, 'ami_id', 'str', + 'strs', 'str', + 'dummy', 'dummy', + 'dummy', 'dummy', 'dummy') def modify_image_attribute(self, context, image_id, attribute, user_group, operation_type, description=None, launch_permission=None, @@ -1027,11 +1028,8 @@ class CloudController(object): Returns: true if the request succeeds. """ - return image.modify_image_attribute(context, image_id, attribute, - user_group, operation_type, - description, launch_permission, - product_code, user_id, value) + @module_and_param_types(tag, 'ec2_ids', 'dummy') def create_tags(self, context, resource_id, tag): """Adds or overwrites one or more tags for the specified resources. @@ -1045,8 +1043,8 @@ class CloudController(object): Returns: true if the request succeeds. """ - return tag_api.create_tags(context, resource_id, tag) + @module_and_param_types(tag, 'ec2_ids', 'dummy') def delete_tags(self, context, resource_id, tag=None): """Deletes the specified tags from the specified resources. @@ -1065,8 +1063,9 @@ class CloudController(object): its value. If you specify this parameter with an empty string as the value, we delete the key only if its value is an empty string. """ - return tag_api.delete_tags(context, resource_id, tag) + @module_and_param_types(tag, 'filter', 'int', + 'str') def describe_tags(self, context, filter=None, max_results=None, next_token=None): """Describes one or more of the tags for your EC2 resources. @@ -1083,7 +1082,6 @@ class CloudController(object): Returns: A list of tags. """ - return tag_api.describe_tags(context, filter, max_results, next_token) class VpcCloudController(CloudController): diff --git a/ec2api/api/common.py b/ec2api/api/common.py index 82cb4ca4..736b77ff 100644 --- a/ec2api/api/common.py +++ b/ec2api/api/common.py @@ -40,20 +40,32 @@ class Validator(object): self.action = action self.params = params + def multi(self, items, validation_func): + validator.validate_list(items, self.param_name) + for item in items: + validation_func(item) + def dummy(self, value): pass def bool(self, value): validator.validate_bool(value, self.param_name) + def int(self, value): + validator.validate_int(value, self.param_name) + + def str(self, value): + validator.validate_str(value, self.param_name) + + def strs(self, values): + self.multi(values, self.str) + + def str64(self, value): + validator.validate_str(value, self.param_name, 64) + def str255(self, value): validator.validate_str(value, self.param_name, 255) - def multi(self, items, validation_func): - validator.validate_list(items, self.param_name) - for item in items: - validation_func(item) - def str255s(self, values): self.multi(values, self.str255) @@ -75,9 +87,12 @@ class Validator(object): def filter(self, filter): validator.validate_filter(filter) - def ec2_id(self, id, prefices): + def ec2_id(self, id, prefices=[]): validator.validate_ec2_id(id, self.param_name, prefices) + def ec2_ids(self, ids): + self.multi(ids, self.ec2_id) + def i_id(self, id): self.ec2_id(id, ['i']) @@ -88,7 +103,7 @@ class Validator(object): self.ec2_id(id, ['ami', 'ari', 'aki']) def ami_ids(self, ids): - self.multi(ids, self.aki_id) + self.multi(ids, self.ami_id) def sg_id(self, id): self.ec2_id(id, ['sg']) @@ -141,6 +156,18 @@ class Validator(object): def sg_ids(self, ids): self.multi(ids, self.sg_id) + def snap_id(self, id): + self.ec2_id(id, ['snap']) + + def snap_ids(self, ids): + self.multi(ids, self.snap_id) + + def vol_id(self, id): + self.ec2_id(id, ['vol']) + + def vol_ids(self, ids): + self.multi(ids, self.vol_id) + def security_group_str(self, value): validator.validate_security_group_str(value, self.param_name, self.params.get('vpc_id')) diff --git a/ec2api/api/image.py b/ec2api/api/image.py index 556c03a3..1f6915a9 100644 --- a/ec2api/api/image.py +++ b/ec2api/api/image.py @@ -74,6 +74,13 @@ rpcapi_opts = [ CONF.register_opts(rpcapi_opts) +"""Volume related API implementation +""" + + +Validator = common.Validator + + CONTAINER_TO_KIND = {'aki': 'aki', 'ari': 'ari', 'ami': 'ami', diff --git a/ec2api/api/key_pair.py b/ec2api/api/key_pair.py index b84d8b56..2ceb850a 100644 --- a/ec2api/api/key_pair.py +++ b/ec2api/api/key_pair.py @@ -31,6 +31,9 @@ LOG = logging.getLogger(__name__) """ +Validator = common.Validator + + class KeyPairDescriber(common.UniversalDescriber): KIND = 'kp' diff --git a/ec2api/api/snapshot.py b/ec2api/api/snapshot.py index f5e37378..cbe03813 100644 --- a/ec2api/api/snapshot.py +++ b/ec2api/api/snapshot.py @@ -23,6 +23,13 @@ from ec2api import exception from ec2api.openstack.common.gettextutils import _ +"""Snapshot related API implementation +""" + + +Validator = common.Validator + + def create_snapshot(context, volume_id, description=None): volume = ec2utils.get_db_item(context, 'vol', volume_id) cinder = clients.cinder(context) diff --git a/ec2api/api/tag.py b/ec2api/api/tag.py index e4d60356..cb46a965 100644 --- a/ec2api/api/tag.py +++ b/ec2api/api/tag.py @@ -19,6 +19,13 @@ from ec2api import exception from ec2api.openstack.common.gettextutils import _ +"""Tag related API implementation +""" + + +Validator = common.Validator + + RESOURCE_TYPES = { 'dopt': 'dhcp-options', 'ami': 'image', diff --git a/ec2api/api/validator.py b/ec2api/api/validator.py index 160329f9..f3a5566e 100644 --- a/ec2api/api/validator.py +++ b/ec2api/api/validator.py @@ -40,6 +40,14 @@ def validate_bool(val, parameter_name): reason=_("Expected a boolean value for parameter %s") % parameter_name) +def validate_int(val, parameter_name): + if isinstance(val, int): + return True + raise exception.ValidationError( + reason=(_("Expected an integer value for parameter %s") % + parameter_name)) + + def validate_list(items, parameter_name): if not isinstance(items, list): raise exception.InvalidParameterValue( @@ -129,7 +137,7 @@ def validate_ec2_id(val, parameter_name, prefices): try: prefix, value = val.rsplit('-', 1) int(value, 16) - if prefix in prefices: + if not prefices or prefix in prefices: return True except Exception: pass diff --git a/ec2api/api/volume.py b/ec2api/api/volume.py index 7c7af09b..9ba991a8 100644 --- a/ec2api/api/volume.py +++ b/ec2api/api/volume.py @@ -23,6 +23,13 @@ from ec2api import exception from ec2api.openstack.common.gettextutils import _ +"""Volume related API implementation +""" + + +Validator = common.Validator + + def create_volume(context, availability_zone=None, size=None, snapshot_id=None, volume_type=None, iops=None, encrypted=None, kms_key_id=None): diff --git a/ec2api/tests/test_availability_zone.py b/ec2api/tests/test_availability_zone.py index 37526dd1..00432620 100644 --- a/ec2api/tests/test_availability_zone.py +++ b/ec2api/tests/test_availability_zone.py @@ -34,7 +34,7 @@ class AvailabilityZoneCase(base.ApiTestCase): fakes.NovaAvailabilityZone(fakes.OS_AVAILABILITY_ZONE), fakes.NovaAvailabilityZone(fakes.OS_AVAILABILITY_ZONE_INTERNAL)] resp = self.execute('DescribeAvailabilityZones', - {'zoneName': 'verbose'}) + {'zoneName.1': 'verbose'}) self.assertEqual(200, resp['http_status_code']) self.assertEqual(len(resp['availabilityZoneInfo']), 7) self.nova_availability_zones.list.assert_called_once() diff --git a/ec2api/tests/test_instance.py b/ec2api/tests/test_instance.py index 7e00546e..ee0664c9 100644 --- a/ec2api/tests/test_instance.py +++ b/ec2api/tests/test_instance.py @@ -176,11 +176,9 @@ class InstanceTestCase(base.ApiTestCase): 'security_group_id': [fakes.ID_EC2_SECURITY_GROUP_1, fakes.ID_EC2_SECURITY_GROUP_2]}) do_check({'SubnetId': fakes.ID_EC2_SUBNET_1, - 'PrivateIpAddress.1': fakes.IP_FIRST_SUBNET_1, - 'PrivateIpAddress.2': fakes.IP_LAST_SUBNET_1}, + 'PrivateIpAddress': fakes.IP_FIRST_SUBNET_1}, create_network_interface_kwargs={ - 'private_ip_address': [fakes.IP_FIRST_SUBNET_1, - fakes.IP_LAST_SUBNET_1]}) + 'private_ip_address': fakes.IP_FIRST_SUBNET_1}) do_check({'NetworkInterface.1.SubnetId': fakes.ID_EC2_SUBNET_1, 'NetworkInterface.1.SecurityGroupId.1': diff --git a/ec2api/tests/test_key_pair.py b/ec2api/tests/test_key_pair.py index 95f7c184..3997a554 100644 --- a/ec2api/tests/test_key_pair.py +++ b/ec2api/tests/test_key_pair.py @@ -41,7 +41,7 @@ class KeyPairCase(base.ApiTestCase): self.assertEqual('InvalidKeyPair.Duplicate', resp['Error']['Code']) resp = self.execute('CreateKeyPair', {'KeyName': 'k' * 256}) self.assertEqual(400, resp['http_status_code']) - self.assertEqual('InvalidParameterValue', resp['Error']['Code']) + self.assertEqual('ValidationError', resp['Error']['Code']) self.nova_key_pairs.create.side_effect = ( nova_exception.OverLimit(413)) resp = self.execute('CreateKeyPair', {'KeyName': fakes.NAME_KEY_PAIR}) @@ -95,7 +95,7 @@ class KeyPairCase(base.ApiTestCase): def test_describe_key_pairs_invalid(self): self.nova_key_pairs.list.return_value = [fakes.NovaKeyPair( fakes.OS_KEY_PAIR)] - resp = self.execute('DescribeKeyPairs', {'KeyName': 'badname'}) + resp = self.execute('DescribeKeyPairs', {'KeyName.1': 'badname'}) self.assertEqual(404, resp['http_status_code']) self.assertEqual('InvalidKeyPair.NotFound', resp['Error']['Code']) self.nova_key_pairs.list.assert_called_once() diff --git a/ec2api/tests/test_tag.py b/ec2api/tests/test_tag.py index 023ed605..ad9611eb 100644 --- a/ec2api/tests/test_tag.py +++ b/ec2api/tests/test_tag.py @@ -189,7 +189,7 @@ class TagTestCase(base.ApiTestCase): filter_fields = ['resource-type', 'resource-id', 'key', 'value'] filter_param = dict(('Filter.%s.Name' % num, field) for num, field in enumerate(filter_fields)) - filter_param.update(dict(('Filter.%s.Value' % num, 'fake') + filter_param.update(dict(('Filter.%s.Value.1' % num, 'fake') for num, field in enumerate(filter_fields))) resp = self.execute('DescribeTags', filter_param) self.assertEqual({'http_status_code': 200,