tengine_resource_ec2 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,181 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/support/core_ext/enumerable/map_to_hash'
3
+
4
+ class Tengine::Resource::Credential::Ec2::LaunchOptions
5
+
6
+ def initialize(credential)
7
+ @credential = credential
8
+ end
9
+
10
+ LAUNCH_OPTIONS_KEYS = %w(current_region regions availability_zones key_pairs
11
+ security_groups images instance_types kernel_ids ramdisk_ids)
12
+
13
+ def launch_options(connection, current_region)
14
+ @connection, @current_region = connection, current_region
15
+ LAUNCH_OPTIONS_KEYS.map_to_hash{|m| send(m)}
16
+ ensure
17
+ @connection, @current_region = nil, nil
18
+ end
19
+
20
+ attr_reader :current_region
21
+
22
+ DEFAULT_REGION_CAPTIONS = {
23
+ "us-east-1" => "US East" ,
24
+ "us-west-1" => "US West" ,
25
+ "eu-west-1" => "EU West" ,
26
+ "ap-southeast-1" => "Asia Pacific"
27
+ }.freeze
28
+
29
+ def regions
30
+ # ["eu-west-1", "us-east-1", "us-west-1", "ap-southeast-1"]
31
+ raw = @connection.describe_regions
32
+ raw.inject([]) do |dest, region|
33
+ dest << {"name" => region, "caption" => DEFAULT_REGION_CAPTIONS[region]}
34
+ end
35
+ end
36
+
37
+ def availability_zones
38
+ # [
39
+ # {:region_name=>"us-east-1", :zone_name=>"us-east-1a", :zone_state=>"available"},
40
+ # {:region_name=>"us-east-1", :zone_name=>"us-east-1b", :zone_state=>"available"},
41
+ # {:region_name=>"us-east-1", :zone_name=>"us-east-1c", :zone_state=>"available"},
42
+ # {:region_name=>"us-east-1", :zone_name=>"us-east-1d", :zone_state=>"available"}
43
+ # ]
44
+ raw = @connection.describe_availability_zones
45
+ raw.map{|h| h[:zone_name]}.sort
46
+ end
47
+
48
+ def key_pairs
49
+ # [{:aws_key_name=>"west-dev01", :aws_fingerprint=>"7c:89:2f:c9:4a:1c:02:65:1b:14:dc:a5:c9:a0:da:fb:46:08:4a:99"}]
50
+ raw = @connection.describe_key_pairs
51
+ raw.map{|h| h[:aws_key_name]}
52
+ end
53
+
54
+ def security_groups
55
+ # [
56
+ # { :aws_owner=>"892601002221", :aws_group_name=>"default", :aws_description=>"default group",
57
+ # :aws_perms=>[{:owner=>"892601002221", :group=>"default"}, {:from_port=>"22", :to_port=>"22", :cidr_ips=>"0.0.0.0/0", :protocol=>"tcp"}]},
58
+ # { :aws_owner=>"892601002221", :aws_group_name=>"ruby-dev", :aws_description=>"for developmewnt with ruby",
59
+ # :aws_perms=>[{:from_port=>"80", :to_port=>"80", :cidr_ips=>"0.0.0.0/0", :protocol=>"tcp"}]}
60
+ # ]
61
+ raw = @connection.describe_security_groups
62
+ raw.map{|h| h[:aws_group_name]}
63
+ end
64
+
65
+ def images
66
+ # [
67
+ # {
68
+ # :aws_id=>"ami-5189d814",
69
+ # :aws_architecture=>"i386", :root_device_type=>"instance-store",
70
+ # :root_device_name=>"/dev/sda1",
71
+ # :aws_location=>"akm2000-us-west-2/dev-20100521-01.manifest.xml",
72
+ # :aws_image_type=>"machine", :aws_state=>"available",
73
+ # :aws_owner=>"892601002221", :aws_is_public=>false,
74
+ # :aws_kernel_id=>"aki-773c6d32", :aws_ramdisk_id=>"ari-c12e7f84",
75
+ # },
76
+ # ]
77
+ saved_images = Tengine::Resource::VirtualServerImage.all
78
+ # raw_images = @connection.describe_images_by_owner('self')
79
+ raw_images = @connection.describe_images(saved_images.map(&:provided_id).uniq.compact) #クラスタに登録されているAMI
80
+ # raw_images += @connection.describe_images_by_executable_by("self") # 実行可能なAMI
81
+ amiid_to_hash = raw_images.inject({}){|d, hash| d[hash[:aws_id]] = hash; d}
82
+ result = saved_images.map do |saved_image|
83
+ if ami = amiid_to_hash[saved_image.provided_id]
84
+ {
85
+ 'id' => saved_image.id,
86
+ 'name' => ami[:aws_id],
87
+ 'caption' => saved_image.description,
88
+ 'aws_architecture' => ami[:aws_architecture],
89
+ 'aws_arch_root_dev' => to_aws_arch_root_dev(ami),
90
+ }
91
+ else
92
+ nil
93
+ end
94
+ end
95
+ result.compact.uniq
96
+ end
97
+
98
+ def instance_types
99
+ # rawなし
100
+ INSTANCE_TYPES
101
+ end
102
+
103
+ def kernel_ids
104
+ # [
105
+ # {
106
+ # :aws_id=>"aki-233c6d66",
107
+ # :aws_architecture=>"i386", :root_device_type=>"instance-store",
108
+ # :aws_location=>"ec2-paid-ibm-images-us-west-1/vmlinuz-2.6.16.60-0.29-xenpae.i386.manifest.xml",
109
+ # :aws_image_type=>"kernel", :aws_state=>"available", :aws_owner=>"470254534024",
110
+ # :aws_is_public=>true, :image_owner_alias=>"amazon",
111
+ # },
112
+ # ]
113
+ raw = amazon_images.select{|img| img[:aws_image_type] == 'kernel'}
114
+ raw.inject({}) do |dest, hash|
115
+ key = hash[:aws_architecture]
116
+ dest[key] ||= []
117
+ dest[key] << hash[:aws_id]
118
+ dest
119
+ end
120
+ end
121
+
122
+ def ramdisk_ids
123
+ # [
124
+ # {
125
+ # :aws_id=>"ari-2d3c6d68",
126
+ # :aws_architecture=>"i386", :root_device_type=>"instance-store",
127
+ # :aws_location=>"ec2-paid-ibm-images-us-west-1/initrd-2.6.16.60-0.29-xenpae.i386.manifest.xml",
128
+ # :aws_image_type=>"ramdisk", :aws_state=>"available", :aws_owner=>"470254534024",
129
+ # :aws_is_public=>true, :image_owner_alias=>"amazon"
130
+ # },
131
+ # ]
132
+ raw = amazon_images.select{|img| img[:aws_image_type] == 'ramdisk'}
133
+ raw.inject({}) do |dest, hash|
134
+ key = hash[:aws_architecture]
135
+ dest[key] ||= []
136
+ dest[key] << hash[:aws_id]
137
+ dest
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def amazon_images
144
+ @amazon_images ||= @connection.describe_images_by_owner('amazon')
145
+ end
146
+
147
+
148
+ def to_aws_arch_root_dev(hash)
149
+ "#{hash[:aws_architecture]}_#{hash[:root_device_type]}"
150
+ end
151
+
152
+ INSTANCE_TYPES = {
153
+ 'i386_instance-store' => [
154
+ { "value" => "m1.small" , "caption" => "Small" },
155
+ { "value" => "c1.medium" , "caption" => "High-CPU Medium" },
156
+ ].freeze,
157
+ 'i386_ebs' => [
158
+ { "value" => "t1.micro" , "caption" => "Micro" },
159
+ { "value" => "m1.small" , "caption" => "Small" },
160
+ { "value" => "c1.medium" , "caption" => "High-CPU Medium" },
161
+ ].freeze,
162
+ 'x86_64_instance-store' => [
163
+ { "value" => "m1.large" , "caption" => "Large" },
164
+ { "value" => "m1.xlarge" , "caption" => "Extra Large" },
165
+ { "value" => "m2.xlarge" , "caption" => "High-Memory Extra Large" },
166
+ { "value" => "m2.2xlarge", "caption" => "High-Memory Double Extra Large" },
167
+ { "value" => "m2.4xlarge", "caption" => "High-Memory Quadruple Extra Large" },
168
+ { "value" => "c1.xlarge" , "caption" => "High-CPU Extra Large" },
169
+ ].freeze,
170
+ 'x86_64_ebs' => [
171
+ { "value" => "t1.micro" , "caption" => "Micro" },
172
+ { "value" => "m1.large" , "caption" => "Large" },
173
+ { "value" => "m1.xlarge" , "caption" => "Extra Large" },
174
+ { "value" => "m2.xlarge" , "caption" => "High-Memory Extra Large" },
175
+ { "value" => "m2.2xlarge", "caption" => "High-Memory Double Extra Large" },
176
+ { "value" => "m2.4xlarge", "caption" => "High-Memory Quadruple Extra Large" },
177
+ { "value" => "c1.xlarge" , "caption" => "High-CPU Extra Large" },
178
+ ].freeze,
179
+ }.freeze
180
+
181
+ end
@@ -0,0 +1,309 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/resource_ec2'
3
+
4
+ require 'right_aws'
5
+ require 'tengine/support/core_ext/hash/keys'
6
+
7
+
8
+ class Tengine::ResourceEc2::Provider < Tengine::Resource::Provider
9
+
10
+ # モデルの継承時に問題が発生する場合があるので、collectionの呼び出しによる接続先の事前確定を行います。
11
+ # https://github.com/tengine/tengine/commit/9a986a3b52b18a95b1d5324b9731f720d087d1e4
12
+ collection
13
+
14
+ field :connection_settings, :type => Hash
15
+
16
+ def synchronize_physical_servers
17
+ synchronize_by(:physical_servers)
18
+ end
19
+
20
+ def synchronize_virtual_server_images
21
+ synchronize_by(:virtual_server_images)
22
+ end
23
+
24
+ def synchronize_virtual_servers
25
+ synchronize_by(:virtual_servers)
26
+ end
27
+
28
+
29
+
30
+ class Synchronizer < Tengine::Resource::Provider::Synchronizer
31
+ end
32
+
33
+ class PhysicalServerSynchronizer < Synchronizer
34
+ fetch_known_target_method :describe_availability_zones
35
+
36
+ map(:provided_id, :zone_name)
37
+ map(:status , :zone_state)
38
+ map(:cpu_cores ) { 1000 }
39
+ map(:memory_size) { 1000 }
40
+
41
+ def attrs_to_create(properties)
42
+ result = super(properties)
43
+ # 初期登録時、default 値として name には一意な provided_id を name へ登録します
44
+ result[:name] = result[:provided_id]
45
+ result
46
+ end
47
+
48
+ def destroy_targets(targets)
49
+ targets.each do |target|
50
+ target.status = "not_found"
51
+ target.save!
52
+ end
53
+ end
54
+ end
55
+
56
+ class VirtualServerImageSynchronizer < Synchronizer
57
+ fetch_known_target_method :describe_images
58
+ map :provided_id , :aws_id
59
+
60
+ def attrs_to_create(properties)
61
+ result = super(properties)
62
+ # 初期登録時、default 値として name には一意な provided_id を name へ登録します
63
+ result[:name] = result[:provided_id]
64
+ result
65
+ end
66
+ end
67
+
68
+ class VirtualServerSynchronizer < Synchronizer
69
+ fetch_known_target_method :describe_instances
70
+
71
+ map :provided_id , :aws_instance_id
72
+ map :provided_image_id, :aws_image_id
73
+ map :status , :aws_state
74
+ map(:host_server) do |props, provider|
75
+ provider.physical_servers.where(:provided_id => props[:aws_availability_zone]).first
76
+ end
77
+ map :addresses do |props, provider|
78
+ {
79
+ :dns_name => props.delete(:dns_name ),
80
+ :ip_address => props.delete(:ip_address ),
81
+ :private_dns_name => props.delete(:private_dns_name ),
82
+ :private_ip_address => props.delete(:private_ip_address),
83
+ }
84
+ end
85
+
86
+ def attrs_to_create(properties)
87
+ result = super(properties)
88
+ result[:name] = result[:provided_id]
89
+ result
90
+ end
91
+
92
+ def mapped_attributes(properties)
93
+ properties.delete(:aws_state_code)
94
+ result = super(properties)
95
+ result
96
+ end
97
+ end
98
+
99
+ def describe_availability_zones
100
+ # ec2.describe_availability_zones #=> [{:region_name=>"us-east-1",
101
+ # :zone_name=>"us-east-1a",
102
+ # :zone_state=>"available"}, ... ]
103
+ # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeAvailabilityZones.html
104
+ connect{|conn| conn.describe_availability_zones }
105
+ end
106
+
107
+ def describe_images
108
+ connect{|conn| conn.describe_images_by_owner("self")}
109
+ end
110
+
111
+ def describe_instances
112
+ # http://rightscale.rubyforge.org/right_aws_gem_doc/
113
+ # ec2.describe_instances #=>
114
+ # [{:aws_image_id => "ami-e444444d",
115
+ # :aws_reason => "",
116
+ # :aws_state_code => "16",
117
+ # :aws_owner => "000000000888",
118
+ # :aws_instance_id => "i-123f1234",
119
+ # :aws_reservation_id => "r-aabbccdd",
120
+ # :aws_state => "running",
121
+ # :dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
122
+ # :ssh_key_name => "staging",
123
+ # :aws_groups => ["default"],
124
+ # :private_dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
125
+ # :aws_instance_type => "m1.small",
126
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z"},
127
+ # :aws_availability_zone => "us-east-1b",
128
+ # :aws_kernel_id => "aki-ba3adfd3",
129
+ # :aws_ramdisk_id => "ari-badbad00",
130
+ # ..., {...}]
131
+ # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeInstances.html
132
+ connect{|conn| conn.describe_instances }
133
+ end
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+ register_synchronizers({
142
+ :physical_servers => PhysicalServerSynchronizer,
143
+ # :virtual_server_types => VirtualServerTypeSynchronizer,
144
+ :virtual_server_images => VirtualServerImageSynchronizer,
145
+ :virtual_servers => VirtualServerSynchronizer,
146
+ })
147
+
148
+ private
149
+ def update_physical_servers_by(hashs)
150
+ found_ids = []
151
+ hashs.each do |hash|
152
+ server = self.physical_servers.where(:provided_id => hash[:provided_id]).first
153
+ if server
154
+ server.update_attributes(:status => hash[:status])
155
+ else
156
+ server = self.physical_servers.create!(
157
+ :provided_id => hash[:provided_id],
158
+ :name => hash[:name],
159
+ :status => hash[:status])
160
+ end
161
+ found_ids << server.id
162
+ end
163
+ self.physical_servers.not_in(:_id => found_ids).update_all(:status => "not_found")
164
+ end
165
+
166
+ def update_virtual_servers_by(hashs)
167
+ found_ids = []
168
+ hashs.each do |hash|
169
+ server = self.virtual_servers.where(:provided_id => hash[:provided_id]).first
170
+ if server
171
+ server.update_attributes(hash)
172
+ else
173
+ server = self.virtual_servers.create!(hash.merge(:name => hash[:provided_id]))
174
+ end
175
+ found_ids << server.id
176
+ end
177
+ self.virtual_servers.not_in(:_id => found_ids).destroy_all
178
+ end
179
+
180
+ def update_virtual_server_images_by(hashs)
181
+ found_ids = []
182
+ hashs.each do |hash|
183
+ img = self.virtual_server_images.where(:provided_id => hash[:provided_id]).first
184
+ if img
185
+ img.update_attributes(hash)
186
+ else
187
+ img = self.virtual_server_images.create!(hash.merge(:name => hash[:provided_id]))
188
+ end
189
+ found_ids << img.id
190
+ end
191
+ self.virtual_server_images.not_in(:_id => found_ids).destroy_all
192
+ end
193
+
194
+
195
+ public
196
+
197
+ # @param [String] name Name template for created virtual servers
198
+ # @param [Tengine::Resource::VirtualServerImage] image Virtual server image object
199
+ # @param [Tengine::Resource::VirtualServerType] type Virtual server type object
200
+ # @param [String] physical Data center name to put virtual machines (availability zone)
201
+ # @param [String] description What this virtual server is
202
+ # @param [Numeric] min_count Minimum number of vortial servers to boot
203
+ # @param [Numeric] max_count Maximum number of vortial servers to boot
204
+ # @param [Array<Strng>] group_ids Array of names of security group IDs
205
+ # @param [Strng] key_name Name of root key to sue
206
+ # @param [Strng] user_data User-specified
207
+ # @param [Strng] kernel_id Kernel image ID
208
+ # @param [Strng] ramdisk_id Ramdisk image ID
209
+ # @return [Array<Tengine::Resource::VirtualServer>]
210
+ def create_virtual_servers name, image, type, physical, description, min_count, max_count, group_ids, key_name, user_data = "", kernel_id, ramdisk_id
211
+ connect {|conn|
212
+ results = conn.run_instances(
213
+ image.provided_id,
214
+ min_count,
215
+ max_count,
216
+ group_ids,
217
+ key_name,
218
+ user_data,
219
+ nil, # <- addressing_type
220
+ type.provided_id,
221
+ kernel_id,
222
+ ramdisk_id,
223
+ physical,
224
+ nil # <- block_device_mappings
225
+ )
226
+ yield if block_given? # テスト用のブロックの呼び出し
227
+ results.map.with_index {|hash, idx|
228
+ provided_id = hash.delete(:aws_instance_id)
229
+ if server = self.virtual_servers.where({:provided_id => provided_id}).first
230
+ server
231
+ else
232
+ # findではなくfirstで検索しているので、もしhost_server_provided_idで指定されるサーバが見つからなくても
233
+ # host_serverがnilとして扱われるが、仮想サーバ自身の登録は行われます
234
+ host_server = Tengine::Resource::PhysicalServer.by_provided_id(
235
+ [hash[:aws_availability_zone], physical].detect{|i| !i.blank?})
236
+ self.find_virtual_server_on_duplicaion_error(provided_id) do
237
+ self.virtual_servers.create!(
238
+ :name => sprintf("%s%03d", name, idx + 1), # 1 origin
239
+ :address_order => address_order.dup,
240
+ :description => description,
241
+ :provided_id => provided_id,
242
+ :provided_image_id => hash.delete(:aws_image_id),
243
+ :provided_type_id => hash.delete(:aws_instance_type),
244
+ :host_server_id => host_server ? host_server.id : nil,
245
+ :status => hash.delete(:aws_state),
246
+ :properties => hash,
247
+ :addresses => {
248
+ # :dns_name => hash.delete(:dns_name),
249
+ # :ip_address => hash.delete(:ip_address),
250
+ # :private_dns_name => hash.delete(:private_dns_name),
251
+ # :private_ip_address => hash.delete(:private_ip_address),
252
+ })
253
+ end
254
+ end
255
+ }
256
+ }
257
+ end
258
+
259
+ def terminate_virtual_servers servers
260
+ connect do |conn|
261
+ # http://rightscale.rubyforge.org/right_aws_gem_doc/classes/RightAws/Ec2.html#M000287
262
+ # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-TerminateInstances.html
263
+ conn.terminate_instances(servers.map {|i| i.provided_id }).map do |hash|
264
+ serv = self.virtual_servers.where(:provided_id => hash[:aws_instance_id]).first
265
+ serv.update_attributes(:status => hash[:aws_current_state_name]) if serv
266
+ serv
267
+ end
268
+ end
269
+ end
270
+
271
+ # 仮想サーバタイプの監視
272
+ def synchronize_virtual_server_types
273
+ # ec2から取得する情報はありません
274
+ end
275
+
276
+ private
277
+ def address_order
278
+ @@address_order ||= %w"private_ip_address private_dns_name ip_address dns_name".each(&:freeze).freeze
279
+ end
280
+
281
+ def connect
282
+ klass = (ENV['EC2_DUMMY'] == "true") ? Tengine::ResourceEc2::DummyConnection : RightAws::Ec2
283
+ connection_settings.stringify_keys! # DBに保存されるとSymbolのキーはStringに変換される
284
+ Tengine.logger.info("now connecting by using #{connection_settings.inspect}")
285
+ connection = klass.new(
286
+ access_key,
287
+ secret_access_key,
288
+ {
289
+ :logger => Tengine.logger,
290
+ :region => connection_settings['region']
291
+ }
292
+ )
293
+ yield connection
294
+ end
295
+
296
+ def access_key
297
+ connection_settings['access_key'] || read_file_if_exist(connection_settings['access_key_file'])
298
+ end
299
+
300
+ def secret_access_key
301
+ connection_settings['secret_access_key'] || read_file_if_exist(connection_settings['secret_access_key_file'])
302
+ end
303
+
304
+ def read_file_if_exist(filepath)
305
+ return nil unless filepath
306
+ File.read(File.expand_path(filepath)).strip # ~をホームディレクトリに展開するためにFile.expand_pathを使っています
307
+ end
308
+
309
+ end
@@ -0,0 +1,189 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine_resource'
3
+
4
+ class Tengine::Resource::Provider::Ec2 < Tengine::Resource::Provider
5
+
6
+ field :connection_settings, :type => Hash
7
+
8
+ def update_physical_servers
9
+ connect do |conn|
10
+ # ec2.describe_availability_zones #=> [{:region_name=>"us-east-1",
11
+ # :zone_name=>"us-east-1a",
12
+ # :zone_state=>"available"}, ... ]
13
+ # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeAvailabilityZones.html
14
+ hashs = conn.describe_availability_zones.map do |hash|
15
+ {
16
+ :provided_id => hash[:zone_name],
17
+ :name => hash[:zone_name],
18
+ :status => hash[:zone_state],
19
+ }
20
+ end
21
+ update_physical_servers_by(hashs)
22
+ end
23
+ end
24
+
25
+ def update_virtual_servers
26
+ connect do |conn|
27
+ # http://rightscale.rubyforge.org/right_aws_gem_doc/
28
+ # ec2.describe_instances #=>
29
+ # [{:aws_image_id => "ami-e444444d",
30
+ # :aws_reason => "",
31
+ # :aws_state_code => "16",
32
+ # :aws_owner => "000000000888",
33
+ # :aws_instance_id => "i-123f1234",
34
+ # :aws_reservation_id => "r-aabbccdd",
35
+ # :aws_state => "running",
36
+ # :dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
37
+ # :ssh_key_name => "staging",
38
+ # :aws_groups => ["default"],
39
+ # :private_dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
40
+ # :aws_instance_type => "m1.small",
41
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z"},
42
+ # :aws_availability_zone => "us-east-1b",
43
+ # :aws_kernel_id => "aki-ba3adfd3",
44
+ # :aws_ramdisk_id => "ari-badbad00",
45
+ # ..., {...}]
46
+ # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeInstances.html
47
+ hashs = conn.describe_instances.map do |hash|
48
+ result = {
49
+ :provided_id => hash.delete(:aws_instance_id),
50
+ :provided_image_id => hash.delete(:aws_image_id),
51
+ :status => hash.delete(:aws_state),
52
+ }
53
+ hash.delete(:aws_state_code)
54
+ result[:properties] = hash
55
+ result[:addresses] = {
56
+ :dns_name => hash.delete(:dns_name),
57
+ :ip_address => hash.delete(:ip_address),
58
+ :private_dns_name => hash.delete(:private_dns_name),
59
+ :private_ip_address => hash.delete(:private_ip_address),
60
+ }
61
+ result
62
+ end
63
+ update_virtual_servers_by(hashs)
64
+ end
65
+ end
66
+
67
+ def update_virtual_server_images
68
+ connect do |conn|
69
+ hashs = conn.describe_images.map do |hash|
70
+ { :provided_id => hash.delete(:aws_id), }
71
+ end
72
+ update_virtual_server_images_by(hashs)
73
+ end
74
+ end
75
+
76
+ # @param [String] name Name template for created virtual servers
77
+ # @param [Tengine::Resource::VirtualServerImage] image Virtual server image object
78
+ # @param [Tengine::Resource::VirtualServerType] type Virtual server type object
79
+ # @param [String] physical Data center name to put virtual machines (availability zone)
80
+ # @param [String] description What this virtual server is
81
+ # @param [Numeric] min_count Minimum number of vortial servers to boot
82
+ # @param [Numeric] max_count Maximum number of vortial servers to boot
83
+ # @param [Array<Strng>] group_ids Array of names of security group IDs
84
+ # @param [Strng] key_name Name of root key to sue
85
+ # @param [Strng] user_data User-specified
86
+ # @param [Strng] kernel_id Kernel image ID
87
+ # @param [Strng] ramdisk_id Ramdisk image ID
88
+ # @return [Array<Tengine::Resource::VirtualServer>]
89
+ def create_virtual_servers name, image, type, physical, description, min_count, max_count, group_ids, key_name, user_data = "", kernel_id, ramdisk_id
90
+ connect {|conn|
91
+ results = conn.run_instances(
92
+ image.provided_id,
93
+ min_count,
94
+ max_count,
95
+ group_ids,
96
+ key_name,
97
+ user_data,
98
+ nil, # <- addressing_type
99
+ type.provided_id,
100
+ kernel_id,
101
+ ramdisk_id,
102
+ physical,
103
+ nil # <- block_device_mappings
104
+ )
105
+ yield if block_given? # テスト用のブロックの呼び出し
106
+ results.map.with_index {|hash, idx|
107
+ provided_id = hash.delete(:aws_instance_id)
108
+ if server = self.virtual_servers.find(:first, :conditions => {:provided_id => provided_id})
109
+ server
110
+ else
111
+ host_server_provided_id = hash[:aws_availability_zone]
112
+ host_server_provided_id = physical if host_server_provided_id.nil? || host_server_provided_id.blank?
113
+ # findではなくfirstで検索しているので、もしhost_server_provided_idで指定されるサーバが見つからなくても
114
+ # host_serverがnilとして扱われるが、仮想サーバ自身の登録は行われます
115
+ host_server = (host_server_provided_id && !host_server_provided_id.blank?) ?
116
+ Tengine::Resource::PhysicalServer.first(:conditions => {:provided_id => host_server_provided_id}) : nil
117
+ begin
118
+ self.virtual_servers.create!(
119
+ :name => sprintf("%s%03d", name, idx + 1), # 1 origin
120
+ :address_order => address_order,
121
+ :description => description,
122
+ :provided_id => provided_id,
123
+ :provided_image_id => hash.delete(:aws_image_id),
124
+ :provided_type_id => hash.delete(:aws_instance_type),
125
+ :host_server_id => host_server ? host_server.id : nil,
126
+ :status => hash.delete(:aws_state),
127
+ :properties => hash,
128
+ :addresses => {
129
+ # :dns_name => hash.delete(:dns_name),
130
+ # :ip_address => hash.delete(:ip_address),
131
+ # :private_dns_name => hash.delete(:private_dns_name),
132
+ # :private_ip_address => hash.delete(:private_ip_address),
133
+ })
134
+ rescue Mongo::OperationFailure => e
135
+ raise e unless e.message =~ /E11000 duplicate key error/
136
+ self.virtual_servers.find(:first, :conditions => {:provided_id => provided_id}) or
137
+ raise "VirtualServer not found for #{provided_id}"
138
+ rescue Mongoid::Errors::Validations => e
139
+ raise e unless e.document.errors[:provided_id].any?{|s| s =~ /taken/}
140
+ self.virtual_servers.find(:first, :conditions => {:provided_id => provided_id}) or
141
+ raise "VirtualServer not found for #{provided_id}"
142
+ end
143
+ end
144
+ }
145
+ }
146
+ end
147
+
148
+ def terminate_virtual_servers servers
149
+ connect do |conn|
150
+ # http://rightscale.rubyforge.org/right_aws_gem_doc/classes/RightAws/Ec2.html#M000287
151
+ # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-TerminateInstances.html
152
+ conn.terminate_instances(servers.map {|i| i.provided_id }).map do |hash|
153
+ serv = self.virtual_servers.where(:provided_id => hash[:aws_instance_id]).first
154
+ serv.update_attributes(:status => hash[:aws_current_state_name]) if serv
155
+ serv
156
+ end
157
+ end
158
+ end
159
+
160
+ # 仮想サーバタイプの監視
161
+ def synchronize_virtual_server_types
162
+ # ec2から取得する情報はありません
163
+ end
164
+
165
+ # 物理サーバの監視
166
+ def synchronize_physical_servers ; raise NotImplementedError end
167
+ # 仮想サーバの監視
168
+ def synchronize_virtual_servers ; raise NotImplementedError end
169
+ # 仮想サーバイメージの監視
170
+ def synchronize_virtual_server_images ; raise NotImplementedError end
171
+
172
+ private
173
+ def address_order
174
+ @@address_order ||= %w"private_ip_address private_dns_name ip_address dns_name".each(&:freeze).freeze
175
+ end
176
+
177
+ def connect
178
+ klass = (ENV['EC2_DUMMY'] == "true") ? Tengine::Resource::Credential::Ec2::Dummy : RightAws::Ec2
179
+ connection = klass.new(
180
+ self.connection_settings[:access_key],
181
+ self.connection_settings[:secret_access_key],
182
+ {
183
+ :logger => Tengine.logger,
184
+ :region => self.connection_settings[:region]
185
+ }
186
+ )
187
+ yield connection
188
+ end
189
+ end
@@ -0,0 +1,8 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine_resource_ec2'
3
+
4
+ module Tengine::ResourceEc2
5
+ autoload :Provider , 'tengine/resource_ec2/provider'
6
+ autoload :DummyConnection, 'tengine/resource_ec2/dummy_connection'
7
+ autoload :LaunchOptions , 'tengine/resource_ec2/launch_options'
8
+ end