Class: BoxGrinder::EBSPlugin

Inherits:
BasePlugin show all
Defined in:
lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb

Constant Summary collapse

ROOT_DEVICE_NAME =
'/dev/sda1'
POLL_FREQ =

second

1
TIMEOUT =

seconds

1000
EC2_HOSTNAME_LOOKUP_TIMEOUT =
10

Instance Attribute Summary

Attributes inherited from BasePlugin

#deliverables, #plugin_info

Instance Method Summary collapse

Methods inherited from BasePlugin

#current_platform, #deliverables_exists?, #init, #initialize, #is_supported_os?, #is_supported_platform?, #merge_plugin_config, #read_plugin_config, #register_deliverable, #register_supported_os, #register_supported_platform, #run, #set_default_config_value, #subtype, #supported_oses, #validate_plugin_config

Methods included from Plugins

#plugin

Constructor Details

This class inherits a constructor from BoxGrinder::BasePlugin

Instance Method Details

#adjust_fstab(guestfs) ⇒ Object



241
242
243
244
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 241

def adjust_fstab(guestfs)
  guestfs.sh("cat /etc/fstab | grep -v '/mnt' | grep -v '/data' | grep -v 'swap' > /etc/fstab.new")
  guestfs.mv("/etc/fstab.new", "/etc/fstab")
end

#ami_by_name(name) ⇒ Object



189
190
191
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 189

def ami_by_name(name)
  @ec2helper.ami_by_name(name, @plugin_config['account_number'])
end

#device_for_suffix(suffix) ⇒ Object



250
251
252
253
254
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 250

def device_for_suffix(suffix)
  return "/dev/sd#{suffix}" if File.exists?("/dev/sd#{suffix}")
  return "/dev/xvd#{suffix}" if File.exists?("/dev/xvd#{suffix}")
  nil
end

#ebs_appliance_nameObject



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 225

def ebs_appliance_name
  base_path = "#{@appliance_config.name}/#{@appliance_config.os.name}/#{@appliance_config.os.version}/#{@appliance_config.version}.#{@appliance_config.release}"

  return "#{base_path}/#{@appliance_config.hardware.arch}" unless @plugin_config['snapshot']

  snapshot = 1

  while ami_by_name("#{base_path}-SNAPSHOT-#{snapshot}/#{@appliance_config.hardware.arch}")
    snapshot += 1
  end
  # Reuse the last key (if there was one)
  snapshot -=1 if snapshot > 1 and @plugin_config['overwrite']

  "#{base_path}-SNAPSHOT-#{snapshot}/#{@appliance_config.hardware.arch}"
end

#executeObject



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
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 80

def execute
  @log.info Banner.message(EBS::Messages::EPHEMERAL_MESSAGE)

  ebs_appliance_description = "#{@appliance_config.summary} | Appliance version #{@appliance_config.version}.#{@appliance_config.release} | #{@appliance_config.hardware.arch} architecture"

  @log.debug "Checking if appliance is already registered..."
  ami = ami_by_name(ebs_appliance_name)

  if ami and @plugin_config['overwrite']
    @log.info "Overwrite is enabled. Stomping existing assets."
    stomp_ebs(ami)
  elsif ami
    @log.warn "EBS AMI '#{ami.name}' is already registered as '#{ami.id}' (region: #{@current_region})."
    return
  end

  @log.info "Creating new EBS volume..."
  size = 0
  @appliance_config.hardware.partitions.each_value { |partition| size += partition['size'] }

  # create_volume, ceiling to avoid non-Integer values as per https://issues.jboss.org/browse/BGBUILD-224
  volume = @ec2.volumes.create(:size => size.ceil.to_i, :availability_zone => @plugin_config['availability_zone'])

  @log.debug "Volume #{volume.id} created."
  @log.debug "Waiting for EBS volume #{volume.id} to be available..."

  # wait for volume to be created
  @ec2helper.wait_for_volume_status(:available, volume)

  # get first free device to mount the volume
  suffix = free_device_suffix
  device_name = "/dev/sd#{suffix}"
  @log.trace "Got free device suffix: '#{suffix}'"

  @log.trace "Reading current instance id..."
  # get_current_instance
  current_instance = @ec2.instances[@current_instance_id]

  @log.trace "Got: #{current_instance.id}"
  @log.info "Attaching created volume..."
  # attach the volume to current host
  volume.attach_to(current_instance, device_name)

  @log.debug "Waiting for EBS volume to be attached..."
  # wait for volume to be attached
  @ec2helper.wait_for_volume_status(:in_use, volume)

  @log.debug "Waiting for the attached EBS volume to be discovered by the OS"
  wait_for_volume_attachment(suffix)

  @log.info "Copying data to EBS volume..."

  @image_helper.customize([@previous_deliverables.disk, device_for_suffix(suffix)], :automount => false) do |guestfs, guestfs_helper|
    @image_helper.sync_filesystem(guestfs, guestfs_helper)

    @log.debug "Adjusting /etc/fstab..."
    adjust_fstab(guestfs)
  end

  @log.debug "Detaching EBS volume..."
  volume.attachments.map(&:delete)

  @log.debug "Waiting for EBS volume to become available..."
  @ec2helper.wait_for_volume_status(:available, volume)

  @log.info "Creating snapshot from EBS volume..."
  snapshot = @ec2.snapshots.create(
      :volume => volume,
      :description => ebs_appliance_description)

  @log.debug "Waiting for snapshot #{snapshot.id} to be completed..."
  @ec2helper.wait_for_snapshot_status(:completed, snapshot)

  @log.debug "Deleting temporary EBS volume..."
  volume.delete

  @log.info "Registering image..."

  optmap = {
    :name => ebs_appliance_name,
    :root_device_name => ROOT_DEVICE_NAME,
    :block_device_mappings => { 
      ROOT_DEVICE_NAME => {
        :snapshot => snapshot,
        :delete_on_termination => @plugin_config['delete_on_termination']
      }
    },
    :architecture => @appliance_config.hardware.base_arch,
    :kernel_id => @plugin_config['kernel'] || @ec2_endpoints[@current_region][:kernel][@appliance_config.hardware.base_arch.intern][:aki],
    :description => ebs_appliance_description
  }

  optmap.merge!(:ramdisk_id => @plugin_config['ramdisk']) if @plugin_config['ramdisk']

  unless @plugin_config['block_device_mappings'].empty?
    optmap[:block_device_mappings].merge!(@plugin_config['block_device_mappings']) 
  end

  @log.debug("Options map: #{optmap.inspect}")
  image = @ec2.images.create(optmap)

  @log.info "Waiting for the new EBS AMI to become available"
  @ec2helper.wait_for_image_state(:available, image)
  @log.info "EBS AMI '#{image.name}' registered: #{image.id} (region: #{@current_region})"
rescue Timeout::Error
  @log.error "An operation timed out. Manual intervention may be necessary to complete the task."
  raise
end

#free_device_suffixObject



256
257
258
259
260
261
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 256

def free_device_suffix
  ("f".."p").each do |suffix|
    return suffix unless File.exists?("/dev/sd#{suffix}") or File.exists?("/dev/xvd#{suffix}")
  end
  raise "Found too many attached devices. Cannot attach EBS volume."
end

#stomp_ebs(ami) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 200

def stomp_ebs(ami)
  #Find any instances that are running, if they are not stopped then abort.
  if live = @ec2helper.live_instances(ami)
    if @plugin_config['terminate_instances']
      @log.info "Terminating the following instances: #{live.collect{|i| "#{i.id} (#{i.status})"}.join(", ")}."
      terminate_instances(live)
    else
      raise "There are still instances of #{ami.id} running, you should terminate them after " <<
           "preserving any important data: #{live.collect{|i| "#{i.id} (#{i.status})"}.join(", ")}."
    end
  end

  @log.info("Finding the primary snapshot associated with #{ami.id}.")
  primary_snapshot = @ec2helper.snapshot_by_id(ami.block_device_mappings[ami.root_device_name].snapshot_id)

  @log.info("De-registering the EBS AMI.")
  ami.deregister
  @ec2helper.wait_for_image_death(ami)

  if !@plugin_config['preserve_snapshots'] and primary_snapshot
    @log.info("Deleting the primary snapshot.")
    primary_snapshot.delete
  end
end

#terminate_instances(instances) ⇒ Object



193
194
195
196
197
198
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 193

def terminate_instances(instances)
  instances.map(&:terminate)
  instances.each do |i|
    @ec2helper.wait_for_instance_death(i)
  end
end

#valid_platform?Boolean

Returns:

  • (Boolean)


263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 263

def valid_platform?
  begin
    region = EC2Helper::availability_zone_to_region(EC2Helper::current_availability_zone)
    return true if @ec2_endpoints.has_key? region
    @log.warn "You may be using an ec2 region that BoxGrinder Build is not aware of: #{region}, BoxGrinder Build knows of: #{@ec2_endpoints.join(", ")}"
  rescue Net::HTTPServerException => e
    @log.warn "An error was returned when attempting to retrieve the ec2 hostname: #{e.to_s}"
  rescue Timeout::Error => t
    @log.warn "A timeout occurred while attempting to retrieve the ec2 hostname: #{t.to_s}"
  end
  false
end

#validateObject

Raises:

  • (PluginValidationError)


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
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 38

def validate
  @ec2_endpoints = EC2Helper::endpoints

  raise PluginValidationError, "You are trying to run this plugin on an invalid platform. You can run the EBS delivery plugin only on EC2." unless valid_platform?

  @current_availability_zone = EC2Helper::current_availability_zone
  @current_instance_id = EC2Helper::current_instance_id
  @current_region = EC2Helper::availability_zone_to_region(@current_availability_zone)

  set_default_config_value('kernel', false)
  set_default_config_value('ramdisk', false)
  set_default_config_value('availability_zone', @current_availability_zone)
  set_default_config_value('delete_on_termination', true)
  set_default_config_value('overwrite', false)
  set_default_config_value('snapshot', false)
  set_default_config_value('preserve_snapshots', false)
  set_default_config_value('terminate_instances', false)

  set_default_config_value('account_number') do |_, _, value|
    value.to_s.gsub!(/-/, '')
  end

  set_default_config_value('block_device_mappings', {}) do |k, m, v|
    EC2Helper::block_device_mappings_validator(k, m, v)
  end

  validate_plugin_config(['access_key', 'secret_access_key', 'account_number'], 'http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#EBS_Delivery_Plugin')

  raise PluginValidationError, "You can only convert to EBS type AMI appliances converted to EC2 format. Use '-p ec2' switch. For more info about EC2 plugin see http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#EC2_Platform_Plugin." unless @previous_plugin_info[:name] == :ec2

  raise PluginValidationError, "You selected #{@plugin_config['availability_zone']} availability zone, but your instance is running in #{@current_availability_zone} zone. Please change availability zone in plugin configuration file to #{@current_availability_zone} (see http://boxgrinder.org/tutorials/boxgrinder-build-plugins/#EBS_Delivery_Plugin) or use another instance in #{@plugin_config['availability_zone']} zone to create your EBS AMI." if @plugin_config['availability_zone'] != @current_availability_zone

  AWS.config(:access_key_id => @plugin_config['access_key'],
    :secret_access_key => @plugin_config['secret_access_key'],
    :ec2_endpoint => @ec2_endpoints[@current_region][:endpoint],
    :max_retries => 5,
    :use_ssl => @plugin_config['use_ssl'])

  @ec2 = AWS::EC2.new
  @ec2helper = EC2Helper.new(@ec2, :log => @log)
end

#wait_for_volume_attachment(suffix) ⇒ Object



246
247
248
# File 'lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb', line 246

def wait_for_volume_attachment(suffix)
  @ec2helper.wait_with_timeout(POLL_FREQ, TIMEOUT){ device_for_suffix(suffix) != nil }
end