class EC2::Platform::Solaris::Image

This class encapsulate functionality to create an file loopback image from a volume. The image is created using mkfile. Sub-directories of the volume, including mounts of local filesystems, are copied to the image. Symbolic links are preserved wherever possible.

This class encapsulate functionality to create an file loopback image from a volume. The image is created using mkfile. Sub-directories of the volume, including mounts of local filesystems, are copied to the image. Symbolic links are preserved wherever possible.

Constants

ARCHIVE
DELAY
EXCLUDES
MOUNT
PROFILING
RETRIES
WORKSPACE

Public Class Methods

new( volume, filename, size, exclude, includes, filter, vfstab=nil, part_type=nil, arch=nil, script=nil, debug = false ) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 54
def initialize( volume,             # path to volume to be bundled
                filename,           # name of image file to create
                size,               # size of image file in MB
                exclude,            # list of directories to exclude
                includes,           # This does absolutely nothing on solaris - warrenr
                filter,             # Same as above - warrenr
                vfstab=nil,         # file system table to use
                part_type=nil,      # Disk partition type: MBR/GPT etc
                arch=nil,           # Architecture of the bundled volume
                script=nil,         # Post-cloning customization script
                debug = false )
  @volume = volume
  @filename = filename
  @size = size
  @exclude = exclude
  @debug = debug
  @arch = arch
  @script = script
  self.set_partition_type( part_type )
  if vfstab.nil? or vfstab == :legacy
    @vfstab = EC2::Platform::Solaris::Fstab::DEFAULT
  elsif File.exists? vfstab
    @vfstab = IO.read(vfstab)
  else
    @vfstab = vfstab
  end

  # Exclude the workspace if it is in the volume being bundled.
  @exclude << WORKSPACE if( WORKSPACE.index(volume) == 0 )
end

Public Instance Methods

make() click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 97
def make
  begin
    announce( "Cloning #{@volume} into image file #{@filename}...", true)
    announce( 'Excluding: ', true )
    @exclude.each { |x| announce( "\t #{x}", true ) }
    archive
    prepare
    replicate
  ensure
    cleanup
  end
end
set_partition_type( input ) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 86
def set_partition_type( input )
  input ||= EC2::Platform::PartitionType::NONE
  if input == EC2::Platform::PartitionType::NONE
    @part_type = EC2::Platform::PartitionType::NONE
  else
    raise NotImplementedError, "Disk images not supported for Solaris"
  end
end

Private Instance Methods

announce(something, force=false) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 289
def announce(something, force=false)
  STDOUT.puts( something ) if @debug or force
end
archive() click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 134
def archive
  FileUtils.mkdir_p( WORKSPACE )
  announce( 'Creating flash archive of file system...', true )
  exempt = []
  @exclude.each do |item|
    item = File.expand_path(item)
    # Since flarcreate does not allow you to exclude a mount-point from
    # a flash archive, we work around this by listing the files in that
    # directory and excluding them individually.
    if mounted? item
      exempt.concat( evaluate( 'ls -A ' + item).split(/\s/).map{|i| File.join( item, i ) } )
    else
      exempt << item
    end
  end
  exempt = exempt.join( ' -x ')

  invocation = ['flar create -n ec2.archive -S -R ' + @volume ]
  invocation << ( '-x ' + exempt ) unless exempt.empty?
  invocation << ARCHIVE
  evaluate( invocation.join( ' ' ) )
  raise FatalError.new( "Archive creation failed" ) unless File.exist?( ARCHIVE )

  asize = FileUtil.size( ARCHIVE ) / ( 1024 * 1024 )
  raise FatalError.new( "Archive too small" ) unless asize > 0
  raise FatalError.new( 'Archive exceeds target size' ) if asize > @size
end
cleanup() click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 258
def cleanup
  attempts = 0
  begin
    unmount( MOUNT )
  rescue ExecutionError
    announce "Unable to unmount image. Retrying after a short sleep."
    attempts += 1
    if attempts < RETRIES
      sleep DELAY
      retry
    else
      announce( "Unable to unmount image after #{RETRIES} attempts. Baling out...", true )
      unmount( MOUNT, true )
      if File.exist?( @filename )
        announce( "Deleting image file #{@filename}..." )
        FileUtils.rm_f( @filename )
      end
    end
  end
  unless @device.nil?
    devices = evaluate( 'lofiadm' ).split( /\n/ )
    devices.each do |item|
      execute( 'lofiadm -d' + @device ) if item.index( @device ) == 0
    end
  end
  execute( 'devfsadm -C' )
  FileUtils.rm_rf( WORKSPACE ) if File.directory?( WORKSPACE )
end
customize() click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 220
def customize
  return unless @script and File.executable?(@script)
  announce 'Customizing replicated volume mounted at %s with script %s' % [MOUNT, @script]
  output = evaluate('%s "%s"' % [@script, MOUNT])
  STDERR.puts output if @debug
end
evaluate( cmd, success = 0, verbattim = false ) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 307
def evaluate( cmd, success = 0, verbattim = false )
  verbattim ||= @debug
  cmd << ' 2> /dev/null' unless verbattim
  announce( "Evaluating: '#{cmd}' " )
  time = Time.now
  pid, stdin, stdout, stderr = Open4::popen4( cmd )
  ignore stdin
  pid, status = Process::waitpid2 pid
  unless status.exitstatus == success
    raise ExecutionError.new( "Failed to evaluate '#{cmd }'. Reason: #{stderr.read}." )
  end
  announce( "Time: #{Time.now - time}s", PROFILING )
  stdout.read
end
execute( cmd, verbattim = false ) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 295
def execute( cmd, verbattim = false )
  verbattim ||= @debug
  invocation = [ cmd ]
  invocation << ' 2>&1 > /dev/null' unless verbattim
  announce( "Executing: '#{cmd}' " )
  time = Time.now
  raise ExecutionError.new( "Failed to execute '#{cmd}'.") unless system( invocation.join )
  announce( "Time: #{Time.now - time}s", PROFILING )
end
ignore(stuff) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 322
def ignore(stuff) stuff end
mount(device, mpoint) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 230
def mount(device, mpoint)
  FileUtils.mkdir_p(mpoint) if not FileUtil::exists?(mpoint)
  raise FatalError.new("image already mounted") if mounted?(mpoint)
  execute( 'sync' )
  execute( 'mount ' + device + ' ' + mpoint )
end
mounted?(mpoint) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 249
def mounted?(mpoint)
  EC2::Platform::Solaris::Mtab.load.entries.keys.include? mpoint
end
prepare() click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 116
def prepare
  FileUtils.mkdir_p( MOUNT )
  announce( 'Creating and formatting file-system image...', true )
  evaluate( "/usr/sbin/mkfile #{@size*1024*1024} #{@filename}" )

  announce( 'Formatting file-system image...' )
  execute( 'sync && devfsadm -C' )
  @device = evaluate('/usr/sbin/lofiadm -a ' + @filename).strip
  number = @device.split(/\//).last rescue nil
  raise FatalError.new('Failed to attach image to a device' ) unless number
  execute( "echo y | newfs /dev/rlofi/#{number} < /dev/null > /dev/null 2>&1", true )

  execute( 'sync' )
  mount( @device, MOUNT )
end
replicate() click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 164
def replicate
  announce( 'Replicating archive to image (this will take a while)...', true )
  # Extract flash archive into mounted image. The flar utility places
  # the output in a folder called 'archive'. Since we cannot override
  # this, we need to extract the content, move it to the image root
  # and delete remove the cruft
  extract = File.join( MOUNT, 'archive')
  execute( "flar split -S archive -d #{MOUNT} -f #{ARCHIVE}" )
  execute( "ls -A #{extract} | xargs -i mv #{extract}/'{}' #{MOUNT}" )
  FileUtils.rm_rf( File.join(MOUNT, 'archive') )
  FileUtils.rm_rf( File.join(MOUNT, 'identification') )

  announce 'Saving system configuration...'
  ['/boot/solaris/bootenv.rc', '/etc/vfstab', '/etc/path_to_inst'].each do |path|
    file = File.join( MOUNT, path )
    FileUtils.cp( file, file + '.phys' )
  end

  announce 'Fine-tuning system configuration...'
  execute( '/usr/sbin/sys-unconfig -R ' + MOUNT )
  bootenv = File.join( MOUNT, '/boot/solaris/bootenv.rc' )
  execute( "sed '/setprop bootpath/,/setprop console/d' < #{bootenv}.phys > #{bootenv}" )
  execute( "sed '/dsk/d' < #{MOUNT}/etc/vfstab.phys > #{MOUNT}/etc/vfstab" )

  FileUtils.rm_f( File.join(MOUNT, '/etc/rc2.d/S99dtlogin') )

  announce 'Creating missing image directories...'
  [ '/dev/dsk', '/dev/rdsk', '/dev/fd', '/etc/mnttab', ].each do |item|
    FileUtils.mkdir_p( File.join( MOUNT, item ) )
  end

  FileUtils.ln_s( '../../devices/xpvd/xdf@0:a', File.join( MOUNT, '/dev/dsk/c0d0s0' ) )
  FileUtils.ln_s( '../../devices/xpvd/xdf@0:a,raw', File.join( MOUNT, '/dev/rdsk/c0d0s0' ) )

  FileUtils.touch( File.join( MOUNT, Mtab::LOCATION ) )
  fstab = File.join( MOUNT, Fstab::LOCATION )
  File.open(fstab, 'w+') {|io| io << @vfstab }
  announce( "--->/etc/vfstab<---:\n" + @vfstab , true )

  execute( "bootadm update-archive -R #{MOUNT} > /dev/null 2>&1", true )

  announce( 'Disable xen services' )
  file = File.join( MOUNT, '/var/svc/profile/upgrade' )
  execute( 'echo "/usr/sbin/svcadm disable svc:/system/xctl/xend:default" >> ' + file )

  announce 'Setting up DHCP boot'
  FileUtils.touch( File.join( MOUNT, '/etc/hostname.xnf0' ) )
  FileUtils.touch( File.join( MOUNT, '/etc/dhcp.xnf0' ) )

  announce 'Setting keyboard layout'
  kbd = File.join( MOUNT, '/etc/default/kbd' )
  execute( "egrep '^LAYOUT' #{kbd} || echo 'LAYOUT=US-English' >> #{kbd}" )

  customize
end
unmount(mpoint, force=false) click to toggle source
# File lib/ec2/platform/solaris/image.rb, line 239
def unmount(mpoint, force=false)
  GC.start
  execute( 'sync && sync && sync' )
  if mounted?( mpoint ) then
    execute( 'umount ' + (force ? '-f ' : '') + mpoint )
  end
end