Class: Ironfan::Provider::Ec2::Machine

Inherits:
IaasProvider::Machine show all
Defined in:
lib/ironfan/headers.rb,
lib/ironfan/provider/ec2/machine.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from IaasProvider::Machine

cloud_init_user_data

Class Method Details

.block_device_mapping(computer) ⇒ Object

An array of hashes with dorky-looking keys, just like Fog wants it.



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/ironfan/provider/ec2/machine.rb', line 290

def self.block_device_mapping(computer)
  Ironfan.todo "CODE SMELL: Machine is too familiar with EbsVolume problems"
  computer.drives.values.map do |drive|
    next if drive.disk  # Don't create any disc already satisfied
    volume = drive.volume or next
    hsh = { 'DeviceName' => volume.device }
    if volume.attachable == 'ephemeral'
      hsh['VirtualName'] = drive.name
    # if set for creation at launch (and not already created)
    elsif drive.node[:volume_id].blank? && volume.create_at_launch
      if volume.snapshot_id.blank? && volume.size.blank?
        raise "Must specify a size or a snapshot ID for #{volume}"
      end
      hsh['Ebs.SnapshotId'] = volume.snapshot_id if volume.snapshot_id.present?
      hsh['Ebs.VolumeSize'] = volume.size.to_s   if volume.size.present?
      hsh['Ebs.VolumeType'] = volume.type        if volume.type.present?
      hsh['Ebs.Iops']       = volume.iops        if volume.type == 'io1'
      hsh['Ebs.DeleteOnTermination'] = (not volume.keep).to_s
    else next
    end
    hsh
  end.compact
end

.create!(computer) ⇒ Object

Manipulation



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/ironfan/provider/ec2/machine.rb', line 165

def self.create!(computer)
  Ironfan.todo("CODE SMELL: overly large method: #{caller}")
  return if computer.machine? and computer.machine.created?
  Ironfan.step(computer.name,"creating cloud machine", :green)
  #
  errors = lint(computer)
  if errors.present? then raise ArgumentError, "Failed validation: #{errors.inspect}" ; end
  #
  launch_desc = launch_description(computer)
  Chef::Log.debug(JSON.pretty_generate(launch_desc))

  fog_server = Ec2.connection.servers.create(launch_desc)
  machine = Machine.new(:adaptee => fog_server)
  computer.machine = machine
  remember machine, :id => computer.name

  Ironfan.step(fog_server.id,"waiting for machine to be ready", :gray)
  Ironfan.tell_you_thrice     :name           => fog_server.id,
                              :problem        => "server unavailable",
                              :error_class    => Fog::Errors::Error do
    fog_server.wait_for { ready? }
  end

  # tag the computer correctly
  tags = {
    'cluster' =>      computer.server.cluster_name,
    'facet' =>        computer.server.facet_name,
    'index' =>        computer.server.index,
    'name' =>         computer.name,
    'Name' =>         computer.name,
    'creator' =>      Chef::Config.username
  }
  Ec2.ensure_tags(tags, computer.machine)

  # register the new volumes for later save!, and tag appropriately
  computer.machine.volumes.each do |v|
    Ironfan.todo "CODE SMELL: Machine is too familiar with EbsVolume problems"
    ebs_vol = Ec2::EbsVolume.register v
    drive = computer.drives.values.select do |drive|
      drive.volume.device == ebs_vol.device
    end.first
    drive.disk = ebs_vol

    vol_name = "#{computer.name}-#{drive.volume.name}"
    tags['server'] = computer.name
    tags['name'] = vol_name
    tags['Name'] = vol_name
    tags['mount_point'] = drive.volume.mount_point
    tags['device'] = drive.volume.device
    Ec2.ensure_tags(tags,ebs_vol)
  end
end

.destroy!(computer) ⇒ Object



314
315
316
317
318
319
# File 'lib/ironfan/provider/ec2/machine.rb', line 314

def self.destroy!(computer)
  return unless computer.machine?
  forget computer.machine.name
  computer.machine.destroy
  computer.machine.reload            # show the node as shutting down
end

.expected_ids(computer) ⇒ Object



42
# File 'lib/ironfan/provider/ec2/machine.rb', line 42

def self.expected_ids(computer) [computer.server.full_name];   end

.launch_description(computer) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/ironfan/provider/ec2/machine.rb', line 240

def self.launch_description(computer)
  cloud = computer.server.cloud(:ec2)
  user_data = self.cloud_init_user_data(computer)

  # main machine info
  # note that Fog does not actually create tags when it creates a
  #  server; they and permanence are applied during sync
  description = {
    :image_id             => cloud.image_id,
    :flavor_id            => cloud.flavor,
    :vpc_id               => cloud.vpc,
    :subnet_id            => cloud.subnet,
    :key_name             => cloud.ssh_key_name(computer),
    :user_data            => user_data,
    :block_device_mapping => block_device_mapping(computer),
    :availability_zone    => cloud.default_availability_zone,
    :monitoring           => cloud.monitoring,
  }

  # VPC security_groups can only be addressed by id (not name)
  sec_group_ids = []
  [computer.server.security_groups, cloud.security_groups].each do |container|
    sec_group_ids += container.keys.map do |g|
      SecurityGroup.recall( SecurityGroup.group_name_with_vpc(g,cloud.vpc) ).group_id
    end
  end
  description[:security_group_ids] = sec_group_ids.uniq

  description[:iam_server_certificates] = cloud.iam_server_certificates.values.map do |cert|
    IamServerCertificate.recall(IamServerCertificate.full_name(computer, cert))
  end.compact.map(&:name)

  description[:elastic_load_balancers] = cloud.elastic_load_balancers.values.map do |elb|
    ElasticLoadBalancer.recall(ElasticLoadBalancer.full_name(computer, elb))
  end.compact.map(&:name)

  if cloud.flavor_info[:placement_groupable]
    description[:placement_group] = cloud.placement_group.to_s
  elsif cloud.placement_group
    Chef::Application.fatal!("A placement group was set but #{cloud.flavor} does not support placement!")
  end
  if cloud.flavor_info[:ebs_optimizable]
    description[:ebs_optimized] = cloud.ebs_optimized
  elsif cloud.ebs_optimized
    Chef::Application.fatal!("ebs_optimized set but #{cloud.flavor} does not support ebs optimization!")
  end
  description
end

.lint(computer) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/ironfan/provider/ec2/machine.rb', line 219

def self.lint(computer)
  cloud = computer.server.cloud(:ec2)
  info  = [computer.name, cloud.inspect]
  errors = {}
  server_errors = computer.server.lint
  errors["Unhappy Server"]      = server_errors   if server_errors.present?
  errors["No AMI found"]        = info            if cloud.image_id.blank?
  errors['Missing client']      = info            unless computer.client?
  errors['Missing private_key'] = computer.client unless computer.private_key
  #
  asserted_regions = {
    :ec2_connection => Ec2.connection.region,
    :cloud => cloud.region,
    :knife => Chef::Config[:knife][:region],
    :chef_config => Ironfan.chef_config[:region]
  }
  errors["mismatched region"] = asserted_regions unless asserted_regions.values.compact.uniq.count == 1
  #
  errors
end

.load!(cluster = nil) ⇒ Object

Discovery



117
118
119
# File 'lib/ironfan/provider/ec2/machine.rb', line 117

def self.load!(cluster = nil)
  load_once!
end

.load_once!Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/ironfan/provider/ec2/machine.rb', line 121

def self.load_once!
  return if @loaded
  Ec2.connection.servers.each do |fs|
    machine = new(:adaptee => fs)
    if (not machine.created?)
      next unless Ironfan.chef_config[:include_terminated]
      remember machine, :append_id => "terminated:#{machine.id}"
    elsif recall? machine.name
      machine.bogus <<                 :duplicate_machines
      recall(machine.name).bogus <<    :duplicate_machines
      remember machine, :append_id => "duplicate:#{machine.id}"
    else # never seen it
      remember machine
    end
  end
  @loaded = true
end

.multiple?Boolean

Returns:

  • (Boolean)


39
# File 'lib/ironfan/provider/ec2/machine.rb', line 39

def self.multiple?()    false;  end

.resource_typeObject

def self.resource_type() Ironfan::IaasProvider::Machine; end



41
# File 'lib/ironfan/provider/ec2/machine.rb', line 41

def self.resource_type()        :machine;   end

.save!(computer) ⇒ Object



321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/ironfan/provider/ec2/machine.rb', line 321

def self.save!(computer)
  return unless computer.machine?
  # the EC2 API does not surface disable_api_termination as a value, so we
  # have to set it every time.
  permanent = computer.server.cloud(:ec2).permanent
  return unless computer.created?
  Ironfan.step(computer.name, "setting termination flag #{permanent}", :blue)
  Ironfan.unless_dry_run do
    Ironfan.safely do
      Ec2.connection.modify_instance_attribute( computer.machine.id,
        {'DisableApiTermination.Value' => computer.permanent?, })
    end
  end
end

.shared?Boolean

Returns:

  • (Boolean)


38
# File 'lib/ironfan/provider/ec2/machine.rb', line 38

def self.shared?()      false;  end

.validate_resources!(computers) ⇒ Object

Find active machines that haven’t matched, but should have,

make sure all bogus machines have a computer to attach to
for display purposes


147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/ironfan/provider/ec2/machine.rb', line 147

def self.validate_resources!(computers)
  recall.each_value do |machine|
    next unless machine.users.empty? and machine.name
    if computers.clusters.any?{ |comp| machine.name.match("^#{comp.name}-") }
      machine.bogus << :unexpected_machine
    end
    next unless machine.bogus?
    fake           = Ironfan::Broker::Computer.new
    fake[:machine] = machine
    fake.name      = machine.name
    machine.users << fake
    computers     << fake
  end
end

Instance Method Details

#created?Boolean

Returns:

  • (Boolean)


52
53
54
# File 'lib/ironfan/provider/ec2/machine.rb', line 52

def created?
  not ['terminated', 'shutting-down'].include? state
end

#groupsObject



48
# File 'lib/ironfan/provider/ec2/machine.rb', line 48

def groups           ; Array(adaptee.groups)   ;   end

#keypairObject



50
# File 'lib/ironfan/provider/ec2/machine.rb', line 50

def keypair          ; key_pair ; end

#nameObject



44
45
46
47
# File 'lib/ironfan/provider/ec2/machine.rb', line 44

def name
  return id if (tags.nil? || tags.empty?)
  tags["Name"] || tags["name"] || id
end

#pending?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/ironfan/provider/ec2/machine.rb', line 55

def pending?
  state == "pending"
end

#perform_after_launch_tasks?Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/ironfan/provider/ec2/machine.rb', line 80

def perform_after_launch_tasks?
  true
end

#public_hostnameObject



49
# File 'lib/ironfan/provider/ec2/machine.rb', line 49

def public_hostname  ; dns_name ; end

#receive_adaptee(obj) ⇒ Object



139
140
141
142
# File 'lib/ironfan/provider/ec2/machine.rb', line 139

def receive_adaptee(obj)
  obj = Ec2.connection.servers.new(obj) if obj.is_a?(Hash)
  super
end

#running?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/ironfan/provider/ec2/machine.rb', line 58

def running?
  state == "running"
end

#ssh_keyObject



105
106
107
# File 'lib/ironfan/provider/ec2/machine.rb', line 105

def ssh_key
  keypair = cloud.keypair || computer.server.keypair_name
end

#startObject



68
69
70
71
72
# File 'lib/ironfan/provider/ec2/machine.rb', line 68

def start
  machine = self
  adaptee.start
  adaptee.wait_for{ machine.pending? or machine.running? }
end

#stopObject



74
75
76
77
78
# File 'lib/ironfan/provider/ec2/machine.rb', line 74

def stop
  machine = self
  adaptee.stop
  adaptee.wait_for{ machine.stopping? or machine.stopped? }
end

#stopped?Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/ironfan/provider/ec2/machine.rb', line 64

def stopped?
  state == "stopped"
end

#stopping?Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/ironfan/provider/ec2/machine.rb', line 61

def stopping?
  state == "stopping"
end

#to_display(style, values = {}) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/ironfan/provider/ec2/machine.rb', line 84

def to_display(style,values={})
  # style == :minimal
  values["State"] =             state.to_sym
  values["MachineID"] =         id
  values["Public IP"] =         public_ip_address
  values["Private IP"] =        private_ip_address
  values["Created On"] =        created_at.to_date
  return values if style == :minimal

  # style == :default
  values["Flavor"] =            flavor_id
  values["AZ"] =                availability_zone
  return values if style == :default

  # style == :expanded
  values["Image"] =             image_id
  values["Volumes"] =           volumes.map(&:id).join(', ')
  values["SSH Key"] =           key_name
  values
end

#to_sObject



109
110
111
112
# File 'lib/ironfan/provider/ec2/machine.rb', line 109

def to_s
  "<%-15s %-12s %-25s %-25s %-15s %-15s %-12s %-12s %s:%s>" % [
    self.class.handle, id, created_at, name, private_ip_address, public_ip_address, flavor_id, availability_zone, key_name, groups.join(',') ]
end