File: request_spot_instances.rb

package info (click to toggle)
ruby-fog-aws 3.18.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,140 kB
  • sloc: ruby: 73,328; javascript: 14; makefile: 9; sh: 4
file content (187 lines) | stat: -rw-r--r-- 9,635 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
module Fog
  module AWS
    class Compute
      class Real
        require 'fog/aws/parsers/compute/spot_instance_requests'

        # Launch specified instances
        #
        # ==== Parameters
        # * 'image_id'<~String> - Id of machine image to load on instances
        # * 'instance_type'<~String> - Type of instance
        # * 'spot_price'<~Float> - maximum hourly price for instances launched
        # * options<~Hash>:
        #   * 'AvailabilityZoneGroup'<~String> - specify whether or not to launch all instances in the same availability group
        #   * 'InstanceCount'<~Integer> - maximum number of instances to launch
        #   * 'LaunchGroup'<~String> - whether or not to launch/shutdown instances as a group
        #   * 'LaunchSpecification.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.NoDevice'<~String> - specifies that no device should be mapped
        #     * 'Ebs.VolumeSize'<~String> - size of volume in GiBs required unless snapshot is specified
        #     * 'Ebs.DeleteOnTermination'<~String> - specifies whether or not to delete the volume on instance termination
        #   * 'LaunchSpecification.KeyName'<~String> - Name of a keypair to add to booting instances
        #   * 'LaunchSpecification.Monitoring.Enabled'<~Boolean> - Enables monitoring, defaults to disabled
        #   * 'LaunchSpecification.SubnetId'<~String> - VPC subnet ID in which to launch the instance
        #   * 'LaunchSpecification.Placement.AvailabilityZone'<~String> - Placement constraint for instances
        #   * 'LaunchSpecification.SecurityGroup'<~Array> or <~String> - Name of security group(s) for instances, not supported in VPC
        #   * 'LaunchSpecification.SecurityGroupId'<~Array> or <~String> - Id of security group(s) for instances, use this or LaunchSpecification.SecurityGroup
        #   * 'LaunchSpecification.UserData'<~String> -  Additional data to provide to booting instances
        #   * 'LaunchSpecification.EbsOptimized'<~Boolean> - Whether the instance is optimized for EBS I/O
        #   * 'Type'<~String> - spot instance request type in ['one-time', 'persistent']
        #   * 'ValidFrom'<~Time> - start date for request
        #   * 'ValidUntil'<~Time> - end date for request
        #
        # ==== Returns
        # * response<~Excon::Response>:
        #   * body<~Hash>:
        #     * 'requestId'<~String> - Id of request
        #     * 'spotInstanceRequestSet'<~Array>:
        #       * 'createTime'<~Time> - time of instance request creation
        #       * 'instanceId'<~String> - instance id if one has been launched to fulfill request
        #       * 'launchedAvailabilityZone'<~String> - availability zone of instance if one has been launched to fulfill request
        #       * 'launchSpecification'<~Hash>:
        #         * 'blockDeviceMapping'<~Hash> - list of block device mappings for instance
        #         * 'groupSet'<~String> - security group(s) for instance
        #         * 'keyName'<~String> - keypair name for instance
        #         * 'imageId'<~String> - AMI for instance
        #         * 'instanceType'<~String> - type for instance
        #         * 'monitoring'<~Boolean> - monitoring status for instance
        #         * 'subnetId'<~String> - VPC subnet ID for instance
        #       * 'productDescription'<~String> - general description of AMI
        #       * 'spotInstanceRequestId'<~String> - id of spot instance request
        #       * 'spotPrice'<~Float> - maximum price for instances to be launched
        #       * 'state'<~String> - spot instance request state
        #       * 'type'<~String> - spot instance request type
        #
        # {Amazon API Reference}[http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-RequestSpotInstances.html]
        def request_spot_instances(image_id, instance_type, spot_price, options = {})
          if block_device_mapping = options.delete('LaunchSpecification.BlockDeviceMapping')
            block_device_mapping.each_with_index do |mapping, index|
              for key, value in mapping
                options.merge!({ format("LaunchSpecification.BlockDeviceMapping.%d.#{key}", index) => value })
              end
            end
          end
          if security_groups = options.delete('LaunchSpecification.SecurityGroup')
            options.merge!(Fog::AWS.indexed_param('LaunchSpecification.SecurityGroup', [*security_groups]))
          end
          if security_group_ids = options.delete('LaunchSpecification.SecurityGroupId')
            options.merge!(Fog::AWS.indexed_param('LaunchSpecification.SecurityGroupId', [*security_group_ids]))
          end
          if options['LaunchSpecification.UserData']
            options['LaunchSpecification.UserData'] = Base64.encode64(options['LaunchSpecification.UserData']).chomp!
          end

          if options['ValidFrom'] && options['ValidFrom'].is_a?(Time)
            options['ValidFrom'] =  options['ValidFrom'].iso8601
          end

          if options['ValidUntil'] && options['ValidUntil'].is_a?(Time)
            options['ValidUntil'] =  options['ValidUntil'].iso8601
          end

          request({
            'Action'                            => 'RequestSpotInstances',
            'LaunchSpecification.ImageId'       => image_id,
            'LaunchSpecification.InstanceType'  => instance_type,
            'SpotPrice'                         => spot_price,
            :parser                             => Fog::Parsers::AWS::Compute::SpotInstanceRequests.new
          }.merge!(options))
        end
      end

      class Mock
        def request_spot_instances(image_id, instance_type, spot_price, options = {})
          response = Excon::Response.new
          id       = Fog::AWS::Mock.spot_instance_request_id

          if (image_id && instance_type && spot_price)
            response.status = 200

            all_instance_types = flavors.map { |f| f.id }
            if !all_instance_types.include?(instance_type)
              message = "InvalidParameterValue => Invalid value '#{instance_type}' for InstanceType."
              raise Fog::AWS::Compute::Error.new(message)
            end

            spot_price = spot_price.to_f
            if !(spot_price > 0)
              message = "InvalidParameterValue => Value (#{spot_price}) for parameter price is invalid."
              message << " \"#{spot_price}\" is an invalid spot instance price"
              raise Fog::AWS::Compute::Error.new(message)
            end

            if !image_id.match(/^ami-[a-f0-9]{8,17}$/)
              message = "The image id '[#{image_id}]' does not exist"
              raise Fog::AWS::Compute::NotFound.new(message)
            end

          else
            message = 'MissingParameter => '
            message << 'The request must contain the parameter '
            if !image_id
              message << 'image_id'
            elsif !instance_type
              message << 'instance_type'
            else
              message << 'spot_price'
            end
            raise Fog::AWS::Compute::Error.new(message)
          end

          for key in %w(AvailabilityZoneGroup LaunchGroup)
            if options.is_a?(Hash) && options.key?(key)
              Fog::Logger.warning("#{key} filters are not yet mocked [light_black](#{caller.first})[/]")
              Fog::Mock.not_implemented
            end
          end

          launch_spec = {
            'iamInstanceProfile' => {},
            'blockDeviceMapping' => options['LaunchSpecification.BlockDeviceMapping'] || [],
            'groupSet'           => options['LaunchSpecification.SecurityGroupId']    || ['default'],
            'imageId'            => image_id,
            'instanceType'       => instance_type,
            'monitoring'         => options['LaunchSpecification.Monitoring.Enabled'] || false,
            'subnetId'           => options['LaunchSpecification.SubnetId']           || nil,
            'ebsOptimized'       => options['LaunchSpecification.EbsOptimized']       || false,
            'keyName'            => options['LaunchSpecification.KeyName']            || nil
          }

          if iam_arn = options['LaunchSpecification.IamInstanceProfile.Arn']
            launch_spec['iamInstanceProfile'].merge!('Arn' => iam_arn)
          end

          if iam_name = options['LaunchSpecification.IamInstanceProfile.Name']
            launch_spec['iamInstanceProfile'].merge!('Name' => iam_name)
          end

          spot_request = {
            'launchSpecification'   => launch_spec,
            'spotInstanceRequestId' => id,
            'spotPrice'             => spot_price,
            'type'                  => options['Type'] || 'one-time',
            'state'                 => 'open',
            'fault'                 => {
              'code'    => 'pending-evaluation',
              'message' => 'Your Spot request has been submitted for review, and is pending evaluation.'
            },
            'createTime'         => Time.now,
            'productDescription' => 'Linux/UNIX'
          }

          self.data[:spot_requests][id] = spot_request

          response.body = {
            'spotInstanceRequestSet' => [spot_request],
            'requestId' => Fog::AWS::Mock.request_id
          }

          response
        end
      end
    end
  end
end