Merge pull request #175 from DreadPirateShawn/SnapshotAttributes
Implementation for ModifySnapshotAttribute and DescribeSnapshotAttribute.
This commit is contained in:
		
						commit
						b69179818c
					
				| @ -110,6 +110,14 @@ class InvalidAMIIdError(EC2ClientError): | |||||||
|             .format(ami_id)) |             .format(ami_id)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class InvalidAMIAttributeItemValueError(EC2ClientError): | ||||||
|  |     def __init__(self, attribute, value): | ||||||
|  |         super(InvalidAMIAttributeItemValueError, self).__init__( | ||||||
|  |             "InvalidAMIAttributeItemValue", | ||||||
|  |             "Invalid attribute item value \"{0}\" for {1} item type." | ||||||
|  |             .format(value, attribute)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class InvalidSnapshotIdError(EC2ClientError): | class InvalidSnapshotIdError(EC2ClientError): | ||||||
|     def __init__(self, snapshot_id): |     def __init__(self, snapshot_id): | ||||||
|         super(InvalidSnapshotIdError, self).__init__( |         super(InvalidSnapshotIdError, self).__init__( | ||||||
|  | |||||||
| @ -29,6 +29,7 @@ from .exceptions import ( | |||||||
|     InvalidPermissionNotFoundError, |     InvalidPermissionNotFoundError, | ||||||
|     InvalidInstanceIdError, |     InvalidInstanceIdError, | ||||||
|     InvalidAMIIdError, |     InvalidAMIIdError, | ||||||
|  |     InvalidAMIAttributeItemValueError, | ||||||
|     InvalidSnapshotIdError, |     InvalidSnapshotIdError, | ||||||
|     InvalidVolumeIdError, |     InvalidVolumeIdError, | ||||||
|     InvalidVolumeAttachmentError, |     InvalidVolumeAttachmentError, | ||||||
| @ -656,6 +657,7 @@ class Snapshot(object): | |||||||
|         self.id = snapshot_id |         self.id = snapshot_id | ||||||
|         self.volume = volume |         self.volume = volume | ||||||
|         self.description = description |         self.description = description | ||||||
|  |         self.create_volume_permission_groups = set() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class EBSBackend(object): | class EBSBackend(object): | ||||||
| @ -717,11 +719,41 @@ class EBSBackend(object): | |||||||
|     def describe_snapshots(self): |     def describe_snapshots(self): | ||||||
|         return self.snapshots.values() |         return self.snapshots.values() | ||||||
| 
 | 
 | ||||||
|  |     def get_snapshot(self, snapshot_id): | ||||||
|  |         snapshot = self.snapshots.get(snapshot_id, None) | ||||||
|  |         if not snapshot: | ||||||
|  |             raise InvalidSnapshotIdError(snapshot_id) | ||||||
|  |         return snapshot | ||||||
|  | 
 | ||||||
|     def delete_snapshot(self, snapshot_id): |     def delete_snapshot(self, snapshot_id): | ||||||
|         if snapshot_id in self.snapshots: |         if snapshot_id in self.snapshots: | ||||||
|             return self.snapshots.pop(snapshot_id) |             return self.snapshots.pop(snapshot_id) | ||||||
|         raise InvalidSnapshotIdError(snapshot_id) |         raise InvalidSnapshotIdError(snapshot_id) | ||||||
| 
 | 
 | ||||||
|  |     def get_create_volume_permission_groups(self, snapshot_id): | ||||||
|  |         snapshot = self.get_snapshot(snapshot_id) | ||||||
|  |         return snapshot.create_volume_permission_groups | ||||||
|  | 
 | ||||||
|  |     def add_create_volume_permission(self, snapshot_id, user_id=None, group=None): | ||||||
|  |         if user_id: | ||||||
|  |             ec2_backend.raise_not_implemented_error("The UserId parameter for ModifySnapshotAttribute") | ||||||
|  | 
 | ||||||
|  |         if group != 'all': | ||||||
|  |             raise InvalidAMIAttributeItemValueError("UserGroup", group) | ||||||
|  |         snapshot = self.get_snapshot(snapshot_id) | ||||||
|  |         snapshot.create_volume_permission_groups.add(group) | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def remove_create_volume_permission(self, snapshot_id, user_id=None, group=None): | ||||||
|  |         if user_id: | ||||||
|  |             ec2_backend.raise_not_implemented_error("The UserId parameter for ModifySnapshotAttribute") | ||||||
|  | 
 | ||||||
|  |         if group != 'all': | ||||||
|  |             raise InvalidAMIAttributeItemValueError("UserGroup", group) | ||||||
|  |         snapshot = self.get_snapshot(snapshot_id) | ||||||
|  |         snapshot.create_volume_permission_groups.discard(group) | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class VPC(TaggedEC2Instance): | class VPC(TaggedEC2Instance): | ||||||
|     def __init__(self, vpc_id, cidr_block): |     def __init__(self, vpc_id, cidr_block): | ||||||
| @ -1380,6 +1412,12 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, | |||||||
|     def raise_error(self, code, message): |     def raise_error(self, code, message): | ||||||
|         raise EC2ClientError(code, message) |         raise EC2ClientError(code, message) | ||||||
| 
 | 
 | ||||||
|  |     def raise_not_implemented_error(self, blurb): | ||||||
|  |         msg = "{0} has not been implemented in Moto yet." \ | ||||||
|  |               " Feel free to open an issue at" \ | ||||||
|  |               " https://github.com/spulec/moto/issues".format(blurb) | ||||||
|  |         raise NotImplementedError(msg) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| ec2_backends = {} | ec2_backends = {} | ||||||
| for region in boto.ec2.regions(): | for region in boto.ec2.regions(): | ||||||
|  | |||||||
| @ -43,9 +43,6 @@ class ElasticBlockStore(BaseResponse): | |||||||
|         success = ec2_backend.delete_volume(volume_id) |         success = ec2_backend.delete_volume(volume_id) | ||||||
|         return DELETE_VOLUME_RESPONSE |         return DELETE_VOLUME_RESPONSE | ||||||
| 
 | 
 | ||||||
|     def describe_snapshot_attribute(self): |  | ||||||
|         raise NotImplementedError('ElasticBlockStore.describe_snapshot_attribute is not yet implemented') |  | ||||||
| 
 |  | ||||||
|     def describe_snapshots(self): |     def describe_snapshots(self): | ||||||
|         snapshots = ec2_backend.describe_snapshots() |         snapshots = ec2_backend.describe_snapshots() | ||||||
|         template = Template(DESCRIBE_SNAPSHOTS_RESPONSE) |         template = Template(DESCRIBE_SNAPSHOTS_RESPONSE) | ||||||
| @ -77,8 +74,22 @@ class ElasticBlockStore(BaseResponse): | |||||||
|     def import_volume(self): |     def import_volume(self): | ||||||
|         raise NotImplementedError('ElasticBlockStore.import_volume is not yet implemented') |         raise NotImplementedError('ElasticBlockStore.import_volume is not yet implemented') | ||||||
| 
 | 
 | ||||||
|  |     def describe_snapshot_attribute(self): | ||||||
|  |         snapshot_id = self.querystring.get('SnapshotId')[0] | ||||||
|  |         groups = ec2_backend.get_create_volume_permission_groups(snapshot_id) | ||||||
|  |         template = Template(DESCRIBE_SNAPSHOT_ATTRIBUTES_RESPONSE) | ||||||
|  |         return template.render(snapshot_id=snapshot_id, groups=groups) | ||||||
|  | 
 | ||||||
|     def modify_snapshot_attribute(self): |     def modify_snapshot_attribute(self): | ||||||
|         raise NotImplementedError('ElasticBlockStore.modify_snapshot_attribute is not yet implemented') |         snapshot_id = self.querystring.get('SnapshotId')[0] | ||||||
|  |         operation_type = self.querystring.get('OperationType')[0] | ||||||
|  |         group = self.querystring.get('UserGroup.1', [None])[0] | ||||||
|  |         user_id = self.querystring.get('UserId.1', [None])[0] | ||||||
|  |         if (operation_type == 'add'): | ||||||
|  |             ec2_backend.add_create_volume_permission(snapshot_id, user_id=user_id, group=group) | ||||||
|  |         elif (operation_type == 'remove'): | ||||||
|  |             ec2_backend.remove_create_volume_permission(snapshot_id, user_id=user_id, group=group) | ||||||
|  |         return MODIFY_SNAPSHOT_ATTRIBUTE_RESPONSE | ||||||
| 
 | 
 | ||||||
|     def modify_volume_attribute(self): |     def modify_volume_attribute(self): | ||||||
|         raise NotImplementedError('ElasticBlockStore.modify_volume_attribute is not yet implemented') |         raise NotImplementedError('ElasticBlockStore.modify_volume_attribute is not yet implemented') | ||||||
| @ -186,3 +197,29 @@ DELETE_SNAPSHOT_RESPONSE = """<DeleteSnapshotResponse xmlns="http://ec2.amazonaw | |||||||
|   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId> |   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId> | ||||||
|   <return>true</return> |   <return>true</return> | ||||||
| </DeleteSnapshotResponse>""" | </DeleteSnapshotResponse>""" | ||||||
|  | 
 | ||||||
|  | DESCRIBE_SNAPSHOT_ATTRIBUTES_RESPONSE = """ | ||||||
|  | <DescribeSnapshotAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/"> | ||||||
|  |     <requestId>a9540c9f-161a-45d8-9cc1-1182b89ad69f</requestId> | ||||||
|  |     <snapshotId>snap-a0332ee0</snapshotId> | ||||||
|  |    {% if not groups %} | ||||||
|  |       <createVolumePermission/> | ||||||
|  |    {% endif %} | ||||||
|  |    {% if groups %} | ||||||
|  |       <createVolumePermission> | ||||||
|  |          {% for group in groups %} | ||||||
|  |             <item> | ||||||
|  |                <group>{{ group }}</group> | ||||||
|  |             </item> | ||||||
|  |          {% endfor %} | ||||||
|  |       </createVolumePermission> | ||||||
|  |    {% endif %} | ||||||
|  | </DescribeSnapshotAttributeResponse> | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | MODIFY_SNAPSHOT_ATTRIBUTE_RESPONSE = """ | ||||||
|  | <ModifySnapshotAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/"> | ||||||
|  |     <requestId>666d2944-9276-4d6a-be12-1f4ada972fd8</requestId> | ||||||
|  |     <return>true</return> | ||||||
|  | </ModifySnapshotAttributeResponse> | ||||||
|  | """ | ||||||
|  | |||||||
| @ -100,6 +100,87 @@ def test_create_snapshot(): | |||||||
|     cm.exception.request_id.should_not.be.none |     cm.exception.request_id.should_not.be.none | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @mock_ec2 | ||||||
|  | def test_snapshot_attribute(): | ||||||
|  |     conn = boto.connect_ec2('the_key', 'the_secret') | ||||||
|  |     volume = conn.create_volume(80, "us-east-1a") | ||||||
|  |     snapshot = volume.create_snapshot() | ||||||
|  | 
 | ||||||
|  |     # Baseline | ||||||
|  |     attributes = conn.get_snapshot_attribute(snapshot.id, attribute='createVolumePermission') | ||||||
|  |     attributes.name.should.equal('create_volume_permission') | ||||||
|  |     attributes.attrs.should.have.length_of(0) | ||||||
|  | 
 | ||||||
|  |     ADD_GROUP_ARGS = {'snapshot_id': snapshot.id, | ||||||
|  |                       'attribute': 'createVolumePermission', | ||||||
|  |                       'operation': 'add', | ||||||
|  |                       'groups': 'all'} | ||||||
|  | 
 | ||||||
|  |     REMOVE_GROUP_ARGS = {'snapshot_id': snapshot.id, | ||||||
|  |                          'attribute': 'createVolumePermission', | ||||||
|  |                          'operation': 'remove', | ||||||
|  |                          'groups': 'all'} | ||||||
|  | 
 | ||||||
|  |     # Add 'all' group and confirm | ||||||
|  |     conn.modify_snapshot_attribute(**ADD_GROUP_ARGS) | ||||||
|  | 
 | ||||||
|  |     attributes = conn.get_snapshot_attribute(snapshot.id, attribute='createVolumePermission') | ||||||
|  |     attributes.attrs['groups'].should.have.length_of(1) | ||||||
|  |     attributes.attrs['groups'].should.equal(['all']) | ||||||
|  | 
 | ||||||
|  |     # Add is idempotent | ||||||
|  |     conn.modify_snapshot_attribute.when.called_with(**ADD_GROUP_ARGS).should_not.throw(EC2ResponseError) | ||||||
|  | 
 | ||||||
|  |     # Remove 'all' group and confirm | ||||||
|  |     conn.modify_snapshot_attribute(**REMOVE_GROUP_ARGS) | ||||||
|  | 
 | ||||||
|  |     attributes = conn.get_snapshot_attribute(snapshot.id, attribute='createVolumePermission') | ||||||
|  |     attributes.attrs.should.have.length_of(0) | ||||||
|  | 
 | ||||||
|  |     # Remove is idempotent | ||||||
|  |     conn.modify_snapshot_attribute.when.called_with(**REMOVE_GROUP_ARGS).should_not.throw(EC2ResponseError) | ||||||
|  | 
 | ||||||
|  |     # Error: Add with group != 'all' | ||||||
|  |     with assert_raises(EC2ResponseError) as cm: | ||||||
|  |         conn.modify_snapshot_attribute(snapshot.id, | ||||||
|  |                                        attribute='createVolumePermission', | ||||||
|  |                                        operation='add', | ||||||
|  |                                        groups='everyone') | ||||||
|  |     cm.exception.code.should.equal('InvalidAMIAttributeItemValue') | ||||||
|  |     cm.exception.status.should.equal(400) | ||||||
|  |     cm.exception.request_id.should_not.be.none | ||||||
|  | 
 | ||||||
|  |     # Error: Add with invalid snapshot ID | ||||||
|  |     with assert_raises(EC2ResponseError) as cm: | ||||||
|  |         conn.modify_snapshot_attribute("snapshot-abcd1234", | ||||||
|  |                                        attribute='createVolumePermission', | ||||||
|  |                                        operation='add', | ||||||
|  |                                        groups='all') | ||||||
|  |     cm.exception.code.should.equal('InvalidSnapshot.NotFound') | ||||||
|  |     cm.exception.status.should.equal(400) | ||||||
|  |     cm.exception.request_id.should_not.be.none | ||||||
|  | 
 | ||||||
|  |     # Error: Remove with invalid snapshot ID | ||||||
|  |     with assert_raises(EC2ResponseError) as cm: | ||||||
|  |         conn.modify_snapshot_attribute("snapshot-abcd1234", | ||||||
|  |                                        attribute='createVolumePermission', | ||||||
|  |                                        operation='remove', | ||||||
|  |                                        groups='all') | ||||||
|  |     cm.exception.code.should.equal('InvalidSnapshot.NotFound') | ||||||
|  |     cm.exception.status.should.equal(400) | ||||||
|  |     cm.exception.request_id.should_not.be.none | ||||||
|  | 
 | ||||||
|  |     # Error: Add or remove with user ID instead of group | ||||||
|  |     conn.modify_snapshot_attribute.when.called_with(snapshot.id, | ||||||
|  |                                                     attribute='createVolumePermission', | ||||||
|  |                                                     operation='add', | ||||||
|  |                                                     user_ids=['user']).should.throw(NotImplementedError) | ||||||
|  |     conn.modify_snapshot_attribute.when.called_with(snapshot.id, | ||||||
|  |                                                     attribute='createVolumePermission', | ||||||
|  |                                                     operation='remove', | ||||||
|  |                                                     user_ids=['user']).should.throw(NotImplementedError) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @mock_ec2 | @mock_ec2 | ||||||
| def test_modify_attribute_blockDeviceMapping(): | def test_modify_attribute_blockDeviceMapping(): | ||||||
|     """ |     """ | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user