From f40538fc69c667ae5912e10fdb869f42b4865045 Mon Sep 17 00:00:00 2001 From: Edward Middleton Date: Sat, 2 Jul 2011 13:30:32 +0900 Subject: [PATCH 1/3] add spot instance request models --- lib/fog/compute/aws.rb | 2 + lib/fog/compute/models/aws/spot_request.rb | 83 ++++++++++++ lib/fog/compute/models/aws/spot_requests.rb | 47 +++++++ .../requests/aws/request_spot_instance.rb | 122 ++++++++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 lib/fog/compute/models/aws/spot_request.rb create mode 100644 lib/fog/compute/models/aws/spot_requests.rb create mode 100644 lib/fog/compute/requests/aws/request_spot_instance.rb diff --git a/lib/fog/compute/aws.rb b/lib/fog/compute/aws.rb index 67b66dd51..24b70bfe9 100644 --- a/lib/fog/compute/aws.rb +++ b/lib/fog/compute/aws.rb @@ -24,6 +24,8 @@ module Fog collection :tags model :volume collection :volumes + model :spot_request + collection :spot_requests request_path 'fog/compute/requests/aws' request :allocate_address diff --git a/lib/fog/compute/models/aws/spot_request.rb b/lib/fog/compute/models/aws/spot_request.rb new file mode 100644 index 000000000..67293c3ec --- /dev/null +++ b/lib/fog/compute/models/aws/spot_request.rb @@ -0,0 +1,83 @@ +require 'fog/core/model' + +module Fog + module Compute + class AWS + + class SpotRequest < Fog::Model + + identity :id, :aliases => 'spotInstanceRequestId' + + attribute :price, :aliases => 'spotPrice' + attribute :request_type, :aliases => 'type' + attribute :created_at, :aliases => 'createTime' + attribute :instance_count, :aliases => 'instanceCount' + attribute :state + + # TODO: not sure how to handle + #attribute :fault + attribute :valid_from, :aliases => 'validFrom' + attribute :valid_until, :aliases => 'validUntil' + attribute :launch_group, :aliases => 'launchGroup' + attribute :availability_zone_group, :aliases => 'availabilityZoneGroup' + attribute :product_description, :aliases => 'productDescription' + + attribute :groups, :aliases => 'LaunchSpecification.SecurityGroup' + attribute :key_name, :aliases => 'LaunchSpecification.KeyName' + attribute :availability_zone, :aliases => 'launchedAvailabilityZone' + attribute :flavor_id, :aliases => 'LaunchSpecification.InstanceType' + attribute :image_id, :aliases => 'LaunchSpecification.ImageId' + attribute :monitoring, :aliases => 'LaunchSpecification.Monitoring' + + attr_writer :username + + def initialize(attributes={}) + self.groups ||= ["default"] + self.flavor_id ||= 't1.micro' + self.image_id ||= begin + self.username = 'ubuntu' + case attributes[:connection].instance_variable_get(:@region) # Ubuntu 10.04 LTS 64bit (EBS) + when 'ap-northeast-1' + 'ami-5e0fa45f' + when 'ap-southeast-1' + 'ami-f092eca2' + when 'eu-west-1' + 'ami-3d1f2b49' + when 'us-east-1' + 'ami-3202f25b' + when 'us-west-1' + 'ami-f5bfefb0' + end + end + self.instance_count ||= 1 + super + end + +# def destroy +# requires :name + +# connection.delete_spot_request(name) +# true +# end + + def save + requires :image_id, :flavor_id, :price + + options = { + 'InstanceCount' => instance_count, + 'LaunchSpecification.KeyName' => key_name, + 'LaunchSpecification.Placement.AvailabilityZone' => availability_zone, + 'LaunchSpecification.SecurityGroup' => groups, + 'Type' => request_type } + + connection.request_spot_instances(image_id, flavor_id, price, options) + end + + def ready? + state == 'active' + end + + end + end + end +end diff --git a/lib/fog/compute/models/aws/spot_requests.rb b/lib/fog/compute/models/aws/spot_requests.rb new file mode 100644 index 000000000..6db8b0574 --- /dev/null +++ b/lib/fog/compute/models/aws/spot_requests.rb @@ -0,0 +1,47 @@ +require 'fog/core/collection' +require 'fog/compute/models/aws/spot_request' + +module Fog + module Compute + class AWS + class SpotRequests < Fog::Collection + + attribute :filters + + model Fog::Compute::AWS::SpotRequest + + def initialize(attributes) + self.filters ||= {} + super + end + + def all(filters = self.filters) + unless filters.is_a?(Hash) + Formatador.display_line("[yellow][WARN] all with #{filters.class} param is deprecated, use all('spot-instance-request-id' => []) instead[/] [light_black](#{caller.first})[/]") + filters = {'spot-instance-request-id' => [*filters]} + end + self.filters = filters + data = connection.describe_spot_instance_requests(filters).body + load( + data['spotInstanceRequestSet'].map do |spot_instance_request| + spot_instance_request['launchSpecification'].each do |name,value| + spot_instance_request['LaunchSpecification.' + name[0,1].upcase + name[1..-1]] = value + end + spot_instance_request.merge(:groups => spot_instance_request['LaunchSpecification.GroupSet']) + spot_instance_request + end.flatten + ) + end + + def get(spot_request_id) + if spot_request_id + self.class.new(:connection => connection).all('spot-instance-request-id' => spot_request_id).first + end + rescue Fog::Errors::NotFound + nil + end + + end + end + end +end diff --git a/lib/fog/compute/requests/aws/request_spot_instance.rb b/lib/fog/compute/requests/aws/request_spot_instance.rb new file mode 100644 index 000000000..0a868ca35 --- /dev/null +++ b/lib/fog/compute/requests/aws/request_spot_instance.rb @@ -0,0 +1,122 @@ +module Fog + module AWS + class Compute + class Real + + require 'fog/compute/parsers/aws/request_spot_instance' + + # Creates a Spot Instance request + # + # ==== Parameters + # * image_id<~String> - Id of machine image to load on instances + # * max_price<~String> - The maximum hourly price + # * max_count<~Integer> -The maximium number of instances to launch + # * options<~Hash>: + # * Type<~String> - request type ['one-time'|'persistent'] + # * ValidFrom<> + # * ValidUntil<> + # * LaunchGroup<> + # * AvailabilityZoneGroup<> + # * LaunchSpecification<~Hash> + # * 'Placement.AvailabilityZone'<~String> - Placement constraint for instances + # * 'BlockDeviceMapping'<~Array>: array of hashes + # * 'DeviceName'<~String> - where the volume will be exposed to instance + # * 'VirtualName'<~String> - volume virtual device name + # * 'Ebs.SnapshotId'<~String> - id of snapshot to boot volume from + # * 'Ebs.VolumeSize'<~String> - size of volume in GiBs required unless snapshot is specified + # * 'Ebs.NoDevice'<~String> - specifies that no device should be mappped + # * 'Ebs.DeleteOnTermination'<~String> - specifies whether or not to delete the volume on instance termination + # * 'ClientToken'<~String> - unique case-sensitive token for ensuring idempotency + # * 'SecurityGroup'<~Array> or <~String> - Name of security group(s) for instances (you must omit this parameter if using Virtual Private Clouds) + # * 'InstanceInitiatedShutdownBehaviour'<~String> - specifies whether volumes are stopped or terminated when instance is shutdown, in [stop, terminate] + # * 'InstanceType'<~String> - Type of instance to boot. Valid options + # in ['m1.small', 'm1.large', 'm1.xlarge', 'c1.medium', 'c1.xlarge', 'm2.xlarge', 'm2.2xlarge', 'm2.4xlarge', 't1.micro'] + # default is 'm1.small' + # * 'KernelId'<~String> - Id of kernel with which to launch + # * 'KeyName'<~String> - Name of a keypair to add to booting instances + # * 'Monitoring.Enabled'<~Boolean> - Enables monitoring, defaults to + # disabled + # * 'RamdiskId'<~String> - Id of ramdisk with which to launch + # * 'UserData'<~String> - Additional data to provide to booting instances + # + # ==== Returns + # * response<~Excon::Response>: + # * body<~Hash>: + # * 'requestId'<~String> - Id of request + # * 'spotInstanceRequestSet'<~Array>: array of hashes + # * 'item'<~Hash>: + # * 'spotInstanceRequestId'<~String> - + # * 'spotPrice + # * 'amiLaunchIndex'<~Integer> - reference to instance in launch group + # * 'architecture'<~String> - architecture of image in [i386, x86_64] + # * 'blockDeviceMapping'<~Array> + # * 'attachTime'<~Time> - time of volume attachment + # * 'deleteOnTermination'<~Boolean> - whether or not to delete volume on termination + # * 'deviceName'<~String> - specifies how volume is exposed to instance + # * 'status'<~String> - status of attached volume + # * 'volumeId'<~String> - Id of attached volume + # * 'dnsName'<~String> - public dns name, blank until instance is running + # * 'imageId'<~String> - image id of ami used to launch instance + # * 'instanceId'<~String> - id of the instance + # * 'instanceState'<~Hash>: + # * 'code'<~Integer> - current status code + # * 'name'<~String> - current status name + # * 'instanceType'<~String> - type of instance + # * 'ipAddress'<~String> - public ip address assigned to instance + # * 'kernelId'<~String> - Id of kernel used to launch instance + # * 'keyName'<~String> - name of key used launch instances or blank + # * 'launchTime'<~Time> - time instance was launched + # * 'monitoring'<~Hash>: + # * 'state'<~Boolean - state of monitoring + # * 'placement'<~Hash>: + # * 'availabilityZone'<~String> - Availability zone of the instance + # * 'privateDnsName'<~String> - private dns name, blank until instance is running + # * 'privateIpAddress'<~String> - private ip address assigned to instance + # * 'productCodes'<~Array> - Product codes for the instance + # * 'ramdiskId'<~String> - Id of ramdisk used to launch instance + # * 'reason'<~String> - reason for most recent state transition, or blank + # * 'rootDeviceName'<~String> - specifies how the root device is exposed to the instance + # * 'rootDeviceType'<~String> - root device type used by AMI in [ebs, instance-store] + + def request_spot_instance(image_id, max_price, max_count, options = {}) + launch_specification = options.delete('LaunchSpecification') + if block_device_mapping = launch_specification.delete('BlockDeviceMapping') + block_device_mapping.each_with_index do |mapping, index| + for key, value in mapping + launch_specification.merge!({ format("BlockDeviceMapping.%d.#{key}", index) => value }) + end + end + end + if security_groups = launch_specification.delete('SecurityGroup') + launch_specification.merge!(AWS.indexed_param('SecurityGroup', [*security_groups])) + end + if launch_specification['UserData'] + launch_specification['UserData'] = Base64.encode64(launch_specification['UserData']) + end + idempotent = !(launch_specification['ClientToken'].nil? || launch_specification['ClientToken'].empty?) + request({ + 'Action' => 'RequestSpotInstances', + 'SpotPrice' => max_price, + 'InstanceCount' => max_count, + 'MinCount' => min_count, + 'LaunchSpecification.ImageId' => image_id, + :idempotent => idempotent, + :parser => Fog::Parsers::AWS::Compute::RunInstances.new + }.merge!('LaunchSpecification' => launch_specification)) + + end + + end + + class Mock + + def request_spot_instance(image_id, max_price, max_count, options = {}) + response = Excon::Response.new + response.status = 200 + + group_set = [ (options[' + end + end + end + end +end From 866409c2dac43f7c1c9fe444023f391622f41865 Mon Sep 17 00:00:00 2001 From: Edward Middleton Date: Sat, 2 Jul 2011 15:22:08 +0900 Subject: [PATCH 2/3] fixed spot_requests --- .../requests/aws/request_spot_instance.rb | 122 ------------------ 1 file changed, 122 deletions(-) delete mode 100644 lib/fog/compute/requests/aws/request_spot_instance.rb diff --git a/lib/fog/compute/requests/aws/request_spot_instance.rb b/lib/fog/compute/requests/aws/request_spot_instance.rb deleted file mode 100644 index 0a868ca35..000000000 --- a/lib/fog/compute/requests/aws/request_spot_instance.rb +++ /dev/null @@ -1,122 +0,0 @@ -module Fog - module AWS - class Compute - class Real - - require 'fog/compute/parsers/aws/request_spot_instance' - - # Creates a Spot Instance request - # - # ==== Parameters - # * image_id<~String> - Id of machine image to load on instances - # * max_price<~String> - The maximum hourly price - # * max_count<~Integer> -The maximium number of instances to launch - # * options<~Hash>: - # * Type<~String> - request type ['one-time'|'persistent'] - # * ValidFrom<> - # * ValidUntil<> - # * LaunchGroup<> - # * AvailabilityZoneGroup<> - # * LaunchSpecification<~Hash> - # * 'Placement.AvailabilityZone'<~String> - Placement constraint for instances - # * 'BlockDeviceMapping'<~Array>: array of hashes - # * 'DeviceName'<~String> - where the volume will be exposed to instance - # * 'VirtualName'<~String> - volume virtual device name - # * 'Ebs.SnapshotId'<~String> - id of snapshot to boot volume from - # * 'Ebs.VolumeSize'<~String> - size of volume in GiBs required unless snapshot is specified - # * 'Ebs.NoDevice'<~String> - specifies that no device should be mappped - # * 'Ebs.DeleteOnTermination'<~String> - specifies whether or not to delete the volume on instance termination - # * 'ClientToken'<~String> - unique case-sensitive token for ensuring idempotency - # * 'SecurityGroup'<~Array> or <~String> - Name of security group(s) for instances (you must omit this parameter if using Virtual Private Clouds) - # * 'InstanceInitiatedShutdownBehaviour'<~String> - specifies whether volumes are stopped or terminated when instance is shutdown, in [stop, terminate] - # * 'InstanceType'<~String> - Type of instance to boot. Valid options - # in ['m1.small', 'm1.large', 'm1.xlarge', 'c1.medium', 'c1.xlarge', 'm2.xlarge', 'm2.2xlarge', 'm2.4xlarge', 't1.micro'] - # default is 'm1.small' - # * 'KernelId'<~String> - Id of kernel with which to launch - # * 'KeyName'<~String> - Name of a keypair to add to booting instances - # * 'Monitoring.Enabled'<~Boolean> - Enables monitoring, defaults to - # disabled - # * 'RamdiskId'<~String> - Id of ramdisk with which to launch - # * 'UserData'<~String> - Additional data to provide to booting instances - # - # ==== Returns - # * response<~Excon::Response>: - # * body<~Hash>: - # * 'requestId'<~String> - Id of request - # * 'spotInstanceRequestSet'<~Array>: array of hashes - # * 'item'<~Hash>: - # * 'spotInstanceRequestId'<~String> - - # * 'spotPrice - # * 'amiLaunchIndex'<~Integer> - reference to instance in launch group - # * 'architecture'<~String> - architecture of image in [i386, x86_64] - # * 'blockDeviceMapping'<~Array> - # * 'attachTime'<~Time> - time of volume attachment - # * 'deleteOnTermination'<~Boolean> - whether or not to delete volume on termination - # * 'deviceName'<~String> - specifies how volume is exposed to instance - # * 'status'<~String> - status of attached volume - # * 'volumeId'<~String> - Id of attached volume - # * 'dnsName'<~String> - public dns name, blank until instance is running - # * 'imageId'<~String> - image id of ami used to launch instance - # * 'instanceId'<~String> - id of the instance - # * 'instanceState'<~Hash>: - # * 'code'<~Integer> - current status code - # * 'name'<~String> - current status name - # * 'instanceType'<~String> - type of instance - # * 'ipAddress'<~String> - public ip address assigned to instance - # * 'kernelId'<~String> - Id of kernel used to launch instance - # * 'keyName'<~String> - name of key used launch instances or blank - # * 'launchTime'<~Time> - time instance was launched - # * 'monitoring'<~Hash>: - # * 'state'<~Boolean - state of monitoring - # * 'placement'<~Hash>: - # * 'availabilityZone'<~String> - Availability zone of the instance - # * 'privateDnsName'<~String> - private dns name, blank until instance is running - # * 'privateIpAddress'<~String> - private ip address assigned to instance - # * 'productCodes'<~Array> - Product codes for the instance - # * 'ramdiskId'<~String> - Id of ramdisk used to launch instance - # * 'reason'<~String> - reason for most recent state transition, or blank - # * 'rootDeviceName'<~String> - specifies how the root device is exposed to the instance - # * 'rootDeviceType'<~String> - root device type used by AMI in [ebs, instance-store] - - def request_spot_instance(image_id, max_price, max_count, options = {}) - launch_specification = options.delete('LaunchSpecification') - if block_device_mapping = launch_specification.delete('BlockDeviceMapping') - block_device_mapping.each_with_index do |mapping, index| - for key, value in mapping - launch_specification.merge!({ format("BlockDeviceMapping.%d.#{key}", index) => value }) - end - end - end - if security_groups = launch_specification.delete('SecurityGroup') - launch_specification.merge!(AWS.indexed_param('SecurityGroup', [*security_groups])) - end - if launch_specification['UserData'] - launch_specification['UserData'] = Base64.encode64(launch_specification['UserData']) - end - idempotent = !(launch_specification['ClientToken'].nil? || launch_specification['ClientToken'].empty?) - request({ - 'Action' => 'RequestSpotInstances', - 'SpotPrice' => max_price, - 'InstanceCount' => max_count, - 'MinCount' => min_count, - 'LaunchSpecification.ImageId' => image_id, - :idempotent => idempotent, - :parser => Fog::Parsers::AWS::Compute::RunInstances.new - }.merge!('LaunchSpecification' => launch_specification)) - - end - - end - - class Mock - - def request_spot_instance(image_id, max_price, max_count, options = {}) - response = Excon::Response.new - response.status = 200 - - group_set = [ (options[' - end - end - end - end -end From 97fc035c01a71267a6515eccba70648c6006f418 Mon Sep 17 00:00:00 2001 From: Edward Middleton Date: Sat, 2 Jul 2011 16:54:37 +0900 Subject: [PATCH 3/3] spot instance fixes --- lib/fog/compute/models/aws/spot_request.rb | 36 +++++++++++++-------- lib/fog/compute/models/aws/spot_requests.rb | 1 + 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/fog/compute/models/aws/spot_request.rb b/lib/fog/compute/models/aws/spot_request.rb index 67293c3ec..b622175fb 100644 --- a/lib/fog/compute/models/aws/spot_request.rb +++ b/lib/fog/compute/models/aws/spot_request.rb @@ -12,10 +12,9 @@ module Fog attribute :request_type, :aliases => 'type' attribute :created_at, :aliases => 'createTime' attribute :instance_count, :aliases => 'instanceCount' + attribute :instance_id, :aliases => 'instanceId' attribute :state - # TODO: not sure how to handle - #attribute :fault attribute :valid_from, :aliases => 'validFrom' attribute :valid_until, :aliases => 'validUntil' attribute :launch_group, :aliases => 'launchGroup' @@ -24,10 +23,14 @@ module Fog attribute :groups, :aliases => 'LaunchSpecification.SecurityGroup' attribute :key_name, :aliases => 'LaunchSpecification.KeyName' - attribute :availability_zone, :aliases => 'launchedAvailabilityZone' + attribute :availability_zone, :aliases => 'LaunchSpecification.Placement.AvailabilityZone' attribute :flavor_id, :aliases => 'LaunchSpecification.InstanceType' attribute :image_id, :aliases => 'LaunchSpecification.ImageId' attribute :monitoring, :aliases => 'LaunchSpecification.Monitoring' + attribute :block_device_mapping, :aliases => 'LaunchSpecification.BlockDeviceMapping' + attribute :tags, :aliases => 'tagSet' + attribute :fault, :squash => 'message' + attribute :user_data attr_writer :username @@ -49,28 +52,35 @@ module Fog 'ami-f5bfefb0' end end - self.instance_count ||= 1 super end -# def destroy -# requires :name - -# connection.delete_spot_request(name) -# true -# end - def save requires :image_id, :flavor_id, :price options = { + 'AvailabilityZoneGroup' => availability_zone_group, 'InstanceCount' => instance_count, + 'LaunchGroup' => launch_group, + 'LaunchSpecification.BlockDeviceMapping' => block_device_mapping, 'LaunchSpecification.KeyName' => key_name, + 'LaunchSpecification.Monitoring.Enabled' => monitoring, 'LaunchSpecification.Placement.AvailabilityZone' => availability_zone, 'LaunchSpecification.SecurityGroup' => groups, - 'Type' => request_type } + 'LaunchSpecification.UserData' => user_data, + 'Type' => request_type, + 'ValidFrom' => valid_from, + 'ValidUntil' => valid_until } + options.delete_if {|key, value| value.nil?} - connection.request_spot_instances(image_id, flavor_id, price, options) + data = connection.request_spot_instances(image_id, flavor_id, price, options).body + spot_instance_request = data['spotInstanceRequestSet'].first + spot_instance_request['launchSpecification'].each do |name,value| + spot_instance_request['LaunchSpecification.' + name[0,1].upcase + name[1..-1]] = value + end + spot_instance_request.merge(:groups => spot_instance_request['LaunchSpecification.GroupSet']) + spot_instance_request.merge(options) + merge_attributes( spot_instance_request ) end def ready? diff --git a/lib/fog/compute/models/aws/spot_requests.rb b/lib/fog/compute/models/aws/spot_requests.rb index 6db8b0574..9db6f4727 100644 --- a/lib/fog/compute/models/aws/spot_requests.rb +++ b/lib/fog/compute/models/aws/spot_requests.rb @@ -24,6 +24,7 @@ module Fog data = connection.describe_spot_instance_requests(filters).body load( data['spotInstanceRequestSet'].map do |spot_instance_request| + spot_instance_request['LaunchSpecification.Placement.AvailabilityZone'] = spot_instance_request['launchedAvailabilityZone'] spot_instance_request['launchSpecification'].each do |name,value| spot_instance_request['LaunchSpecification.' + name[0,1].upcase + name[1..-1]] = value end