tengine_resource_ec2 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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