diff options
author | Mitch.Garnaat <Mitch.Garnaat@604d75c7-a419-0410-a38f-bde1a0bd1dbf> | 2009-12-04 15:18:17 +0000 |
---|---|---|
committer | Mitch.Garnaat <Mitch.Garnaat@604d75c7-a419-0410-a38f-bde1a0bd1dbf> | 2009-12-04 15:18:17 +0000 |
commit | fd02f84a4af98b1fc9064bc98803e9a2d348b3a2 (patch) | |
tree | 55530befff7fc67964c0abcbd0a163018d1938c3 | |
parent | 597614d6378dbe0a84e95303a0911b5b91ad5af4 (diff) | |
download | boto-fd02f84a4af98b1fc9064bc98803e9a2d348b3a2.tar.gz |
Initial support for Boot From EBS.
-rw-r--r-- | boto/ec2/blockdevicemapping.py | 89 | ||||
-rw-r--r-- | boto/ec2/connection.py | 200 | ||||
-rw-r--r-- | boto/ec2/image.py | 41 | ||||
-rw-r--r-- | boto/ec2/instance.py | 40 |
4 files changed, 353 insertions, 17 deletions
diff --git a/boto/ec2/blockdevicemapping.py b/boto/ec2/blockdevicemapping.py new file mode 100644 index 00000000..e70fa9c6 --- /dev/null +++ b/boto/ec2/blockdevicemapping.py @@ -0,0 +1,89 @@ +# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/ +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# + +class EBSBlockDeviceType(object): + + def __init__(self, connection=None): + self.connection = connection + self.volume_id = None + self.snapshot_id = None + self.status = None + self.attach_time = None + self.delete_on_termination = False + self.size = None + + def startElement(self, name, attrs, connection): + pass + + def endElement(self, name, value, connection): + if name =='volumeId': + self.volume_id = value + elif name =='snapshotId': + self.snapshot_id = value + elif name == 'volumeSize': + self.size = int(value) + elif name == 'status': + self.status = value + elif name == 'attachTime': + self.attach_time = value + elif name == 'deleteOnTermination': + if value == 'true': + self.delete_on_termination = True + else: + self.delete_on_termination = False + else: + setattr(self, name, value) + +class BlockDeviceMapping(dict): + + def __init__(self, connection=None): + dict.__init__(self) + self.connection = connection + self.current_name = None + self.current_value = None + + def startElement(self, name, attrs, connection): + if name == 'ebs': + self.current_value = EBSBlockDeviceType(self) + return self.current_value + + def endElement(self, name, value, connection): + if name == 'device' or name == 'deviceName': + self.current_name = value + elif name == 'item': + self[self.current_name] = self.current_value + + def build_list_params(self, params): + i = 1 + for dev_name in self: + pre = 'BlockDeviceMapping.%d' % i + params['%s.DeviceName' % pre] = dev_name + ebs = self[dev_name] + if ebs.snapshot_id: + params['%s.Ebs.SnapshotId' % pre] = ebs.snapshot_id + if ebs.size: + params['%s.Ebs.VolumeSize' % pre] = ebs.size + if ebs.delete_on_termination: + params['%s.Ebs.DeleteOnTermination' % pre] = 'true' + else: + params['%s.Ebs.DeleteOnTermination' % pre] = 'false' + i += 1 diff --git a/boto/ec2/connection.py b/boto/ec2/connection.py index 80b509a3..b8be0e23 100644 --- a/boto/ec2/connection.py +++ b/boto/ec2/connection.py @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/ +# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/ # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the @@ -31,7 +31,7 @@ from boto import config from boto.connection import AWSQueryConnection from boto.resultset import ResultSet from boto.ec2.image import Image, ImageAttribute -from boto.ec2.instance import Reservation, Instance, ConsoleOutput +from boto.ec2.instance import Reservation, Instance, ConsoleOutput, InstanceAttribute from boto.ec2.keypair import KeyPair from boto.ec2.address import Address from boto.ec2.volume import Volume @@ -46,10 +46,10 @@ from boto.exception import EC2ResponseError class EC2Connection(AWSQueryConnection): - APIVersion = boto.config.get('Boto', 'ec2_version', '2009-08-15') + APIVersion = boto.config.get('Boto', 'ec2_version', '2009-10-31') DefaultRegionName = boto.config.get('Boto', 'ec2_region_name', 'us-east-1') DefaultRegionEndpoint = boto.config.get('Boto', 'ec2_region_endpoint', - 'us-east-1.ec2.amazonaws.com') + 'ec2-pinotage.amazonaws.com') SignatureVersion = '2' ResponseError = EC2ResponseError @@ -166,17 +166,55 @@ class EC2Connection(AWSQueryConnection): except IndexError: # None of those images available return None - def register_image(self, image_location): + def register_image(self, name, description=None, image_location=None, + architecture=None, kernel_id=None, ramdisk_id=None, + root_device_name=None, block_device_map=None): """ Register an image. + :type name: string + :param name: The name of the AMI. + + :type description: string + :param description: The description of the AMI. + :type image_location: string :param image_location: Full path to your AMI manifest in Amazon S3 storage. + Only used for S3-based AMI's. + + :type architecture: string + :param architecture: The architecture of the AMI. Valid choices are: + i386 | x86_64 + + :type kernel_id: string + :param kernel_id: The ID of the kernel with which to launch the instances + + :type root_device_name: string + :param root_device_name: The root device name (e.g. /dev/sdh) + + :type block_device_mapping: :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping` + :param block_device_mapping: A BlockDeviceMapping data structure + describing the EBS volumes associated + with the Image. :rtype: string :return: The new image id """ - params = {'ImageLocation':image_location} + params = {'Name': name} + if description: + params['Description'] = description + if architecture: + params['Architecture'] = architecture + if kernel_id: + params['KernelId'] = kernel_id + if ramdisk_id: + params['RamdiskId'] = ramdisk_id + if image_location: + params['Location'] = image_location + if root_device_name: + params['RootDeviceName'] = root_device_name + if block_device_map: + block_device_map.build_list_params(params) rs = self.get_object('RegisterImage', params, ResultSet) image_id = getattr(rs, 'imageId', None) return image_id @@ -193,6 +231,43 @@ class EC2Connection(AWSQueryConnection): """ return self.get_status('DeregisterImage', {'ImageId':image_id}) + def create_image(self, instance_id, name, description=None, no_reboot=False): + """ + Will create an AMI from the instance in the running or stopped + state. + + :type instance_id: string + :param instance_id: the ID of the instance to image. + + :type name: string + :param name: The name of the new image + + :type description: string + :param description: An optional human-readable string describing + the contents and purpose of the AMI. + + :type no_reboot: bool + :param no_reboot: An optional flag indicating that the bundling process + should not attempt to shutdown the instance before + bundling. If this flag is True, the responsibility + of maintaining file system integrity is left to the + owner of the instance. + + :rtype: string + :return: The new image id + """ + params = {'InstanceId' : instance_id, + 'Name' : name} + if description: + params['Description'] = description + if no_reboot: + params['NoReboot'] = 'true' + rs = self.get_object('CreateImage', params, Image) + image_id = getattr(rs, 'imageId', None) + if not image_id: + image_id = getattr(rs, 'ImageId', None) + return image_id + # ImageAttribute methods def get_image_attribute(self, image_id, attribute='launchPermission'): @@ -380,6 +455,36 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, instance_ids, 'InstanceId') return self.get_list('TerminateInstances', params, [('item', Instance)]) + def stop_instances(self, instance_ids=None): + """ + Stop the instances specified + + :type instance_ids: list + :param instance_ids: A list of strings of the Instance IDs to stop + + :rtype: list + :return: A list of the instances stopped + """ + params = {} + if instance_ids: + self.build_list_params(params, instance_ids, 'InstanceId') + return self.get_list('StopInstances', params, [('item', Instance)]) + + def start_instances(self, instance_ids=None): + """ + Start the instances specified + + :type instance_ids: list + :param instance_ids: A list of strings of the Instance IDs to start + + :rtype: list + :return: A list of the instances started + """ + params = {} + if instance_ids: + self.build_list_params(params, instance_ids, 'InstanceId') + return self.get_list('StartInstances', params, [('item', Instance)]) + def get_console_output(self, instance_id): """ Retrieves the console output for the specified instance. @@ -413,6 +518,87 @@ class EC2Connection(AWSQueryConnection): rs = self.get_object('ConfirmProductInstance', params, ResultSet) return (rs.status, rs.ownerId) + # InstanceAttribute methods + + def get_instance_attribute(self, instance_id, attribute): + """ + Gets an attribute from an instance. + + :type instance_id: string + :param instance_id: The Amazon id of the instance + + :type attribute: string + :param attribute: The attribute you need information about + Valid choices are: + instanceType|kernel|ramdisk|userData| + disableApiTermination| + instanceInitiatedShutdownBehavior| + rootDeviceName|blockDeviceMapping + + :rtype: :class:`boto.ec2.image.ImageAttribute` + :return: An ImageAttribute object representing the value of the attribute requested + """ + params = {'InstanceId' : instance_id} + if attribute: + params['Attribute'] = attribute + return self.get_object('DescribeInstanceAttribute', params, InstanceAttribute) + + def modify_image_attribute(self, image_id, attribute='launchPermission', + operation='add', user_ids=None, groups=None, + product_codes=None): + """ + Changes an attribute of an image. + See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-ModifyImageAttribute.html + + :type image_id: string + :param image_id: The image id you wish to change + + :type attribute: string + :param attribute: The attribute you wish to change + + :type operation: string + :param operation: Either add or remove (this is required for changing launchPermissions) + + :type user_ids: list + :param user_ids: The Amazon IDs of users to add/remove attributes + + :type groups: list + :param groups: The groups to add/remove attributes + + :type product_codes: list + :param product_codes: Amazon DevPay product code. Currently only one + product code can be associated with an AMI. Once + set, the product code cannot be changed or reset. + """ + params = {'ImageId' : image_id, + 'Attribute' : attribute, + 'OperationType' : operation} + if user_ids: + self.build_list_params(params, user_ids, 'UserId') + if groups: + self.build_list_params(params, groups, 'UserGroup') + if product_codes: + self.build_list_params(params, product_codes, 'ProductCode') + return self.get_status('ModifyImageAttribute', params) + + def reset_image_attribute(self, image_id, attribute='launchPermission'): + """ + Resets an attribute of an AMI to its default value. + See http://docs.amazonwebservices.com/AWSEC2/2008-02-01/DeveloperGuide/ApiReference-Query-ResetImageAttribute.html + + :type image_id: string + :param image_id: ID of the AMI for which an attribute will be described + + :type attribute: string + :param attribute: The attribute to reset + + :rtype: bool + :return: Whether the operation succeeded or not + """ + params = {'ImageId' : image_id, + 'Attribute' : attribute} + return self.get_status('ResetImageAttribute', params) + # Zone methods def get_all_zones(self, zones=None): @@ -657,7 +843,7 @@ class EC2Connection(AWSQueryConnection): :param volume_id: The ID of the volume to be snapshot'ed :type description: str - :param description: A description of the snapshot. Limited to 256 characters. + :param description: A description of the snapshot. Limited to 255 characters. :rtype: bool :return: True if successful diff --git a/boto/ec2/image.py b/boto/ec2/image.py index 2140c3cc..6b163382 100644 --- a/boto/ec2/image.py +++ b/boto/ec2/image.py @@ -20,6 +20,7 @@ # IN THE SOFTWARE. from boto.ec2.ec2object import EC2Object +from boto.ec2.blockdevicemapping import BlockDeviceMapping class ProductCodes(list): @@ -41,22 +42,32 @@ class Image(EC2Object): self.location = None self.state = None self.ownerId = None + self.owner_alias = None self.is_public = False self.architecture = None + self.platform = None self.type = None self.kernel_id = None self.ramdisk_id = None + self.name = None + self.description = None self.product_codes = ProductCodes() + self.block_device_mapping = None + self.root_device_type = None + self.root_device_name = None def __repr__(self): return 'Image:%s' % self.id def startElement(self, name, attrs, connection): - if name == 'productCodes': + if name == 'blockDeviceMapping': + self.block_device_mapping = BlockDeviceMapping() + return self.block_device_mapping + elif name == 'productCodes': return self.product_codes else: return None - + def endElement(self, name, value, connection): if name == 'imageId': self.id = value @@ -66,12 +77,6 @@ class Image(EC2Object): self.state = value elif name == 'imageOwnerId': self.ownerId = value - elif name == 'imageType': - self.type = value - elif name == 'kernelId': - self.kernel_id = value - elif name == 'ramdiskId': - self.ramdisk_id = value elif name == 'isPublic': if value == 'false': self.is_public = False @@ -84,6 +89,26 @@ class Image(EC2Object): self.id ) ) + elif name == 'architecture': + self.architecture = value + elif name == 'imageType': + self.type = value + elif name == 'kernelId': + self.kernel_id = value + elif name == 'ramdiskId': + self.ramdisk_id = value + elif name == 'imageOwnerAlias': + self.owner_alias = value + elif name == 'platform': + self.platform = value + elif name == 'name': + self.name = value + elif name == 'description': + self.description = value + elif name == 'rootDeviceType': + self.root_device_type = value + elif name == 'rootDeviceName': + self.root_device_name = value else: setattr(self, name, value) diff --git a/boto/ec2/instance.py b/boto/ec2/instance.py index b99da21b..08dfe87f 100644 --- a/boto/ec2/instance.py +++ b/boto/ec2/instance.py @@ -22,10 +22,11 @@ """ Represents an EC2 Instance """ - +import boto from boto.ec2.ec2object import EC2Object from boto.resultset import ResultSet from boto.ec2.address import Address +from boto.ec2.blockdevicemapping import BlockDeviceMapping from boto.ec2.image import ProductCodes import base64 @@ -77,6 +78,7 @@ class Instance(EC2Object): self.shutdown_state = None self.previous_state = None self.instance_type = None + self.instance_class = None self.launch_time = None self.image_id = None self.placement = None @@ -91,6 +93,9 @@ class Instance(EC2Object): self.ip_address = None self.requester_id = None self._in_monitoring_element = False + self.persistent = False + self.root_device_name = None + self.block_device_mapping = None def __repr__(self): return 'Instance:%s' % self.id @@ -98,6 +103,9 @@ class Instance(EC2Object): def startElement(self, name, attrs, connection): if name == 'monitoring': self._in_monitoring_element = True + elif name == 'blockDeviceMapping': + self.block_device_mapping = BlockDeviceMapping() + return self.block_device_mapping elif name == 'productCodes': return self.product_codes return None @@ -123,9 +131,17 @@ class Instance(EC2Object): elif name == 'name': self.state = value elif name == 'code': - self.state_code = int(value) + try: + self.state_code = int(value) + except ValueError: + boto.log.warning('Error converting code (%s) to int' % value) + self.state_code = value elif name == 'instanceType': self.instance_type = value + elif name == 'instanceClass': + self.instance_class = value + elif name == 'rootDeviceName': + self.root_device_name = value elif name == 'launchTime': self.launch_time = value elif name == 'availabilityZone': @@ -151,6 +167,11 @@ class Instance(EC2Object): self.ip_address = value elif name == 'requesterId': self.requester_id = value + elif name == 'persistent': + if value == 'true': + self.persistent = True + else: + self.persistent = False else: setattr(self, name, value) @@ -236,3 +257,18 @@ class ConsoleOutput: self.output = base64.b64decode(value) else: setattr(self, name, value) + +class InstanceAttribute(dict): + + def __init__(self, parent=None): + dict.__init__(self) + self._current_value = None + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == 'value': + self._current_value = value + else: + self[name] = self._current_value |