Skip to content
Browse files

Initial support for Boot From EBS.

  • Loading branch information...
1 parent 597614d commit fd02f84a4af98b1fc9064bc98803e9a2d348b3a2 Mitch.Garnaat committed Dec 4, 2009
Showing with 353 additions and 17 deletions.
  1. +89 −0 boto/ec2/blockdevicemapping.py
  2. +193 −7 boto/ec2/connection.py
  3. +33 −8 boto/ec2/image.py
  4. +38 −2 boto/ec2/instance.py
View
89 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
View
200 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.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 @@
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 @@ def get_image(self, image_id):
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 @@ def deregister_image(self, image_id):
"""
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 @@ def terminate_instances(self, instance_ids=None):
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 @@ def confirm_product_instance(self, product_code, instance_id):
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 @@ def create_snapshot(self, volume_id, description=None):
: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
View
41 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 @@ def __init__(self, connection=None):
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 @@ def endElement(self, name, value, connection):
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 @@ def endElement(self, name, value, connection):
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)
View
40 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 @@ def __init__(self, connection=None):
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,13 +93,19 @@ def __init__(self, connection=None):
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
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 @@ def endElement(self, name, value, connection):
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 @@ def endElement(self, name, value, connection):
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 @@ def endElement(self, name, value, connection):
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

0 comments on commit fd02f84

Please sign in to comment.
Something went wrong with that request. Please try again.