require 'timeout' require 'erb' require 'tilt/erb' # Handler for OpenNebula ImagePool # # @author Michal Kimle class Nifty::Backends::Utils::Opennebula::ImageHandler < Nifty::Backends::Utils::Opennebula::Handler IMAGE_STATE_READY = 'READY' IMAGE_STATE_DISABLED = 'DISABLED' IMAGE_STATE_USED = 'USED' IMAGE_STATE_ERROR = 'ERROR' TIME_FORMAT = '%Y%m%d%H%M%S' ATTRIBUTE_EXPIRATION = 'NIFTY_EXPIRATION' ATTRIBUTE_OUTDATED = 'NIFTY_OUTDATED' ATTRIBUTE_APPLIANCE_VERSION = 'NIFTY_APPLIANCE_VERSION' # Constructor # # @see Nifty::Backends::Utils::Opennebula::Handler#initialize def initialize(client) super(client) @pool = OpenNebula::ImagePool.new(client) end # Returns all images for given appliance id # # @param [String] appliance_id # @return [Array] array of images def images(appliance_id, users = []) reload! return pool.find_all { |image| image["TEMPLATE/#{ATTRIBUTE_APPLIANCE_ID}"] == appliance_id && users.include?(image['UNAME']) } unless users.blank? pool.find_all { |image| image["TEMPLATE/#{ATTRIBUTE_APPLIANCE_ID}"] == appliance_id } end # Returns all expired images (have attribute NIFTY_EXPIRATION) # # @return [Array] array of expired images def expired_images() reload! pool.find_all { |image| image["TEMPLATE/#{ATTRIBUTE_EXPIRATION}"] } end def unmanaged_images() reload! pool.find_all { |image| image["TEMPLATE/#{ATTRIBUTE_APPLIANCE_ID}"].nil? && image["TEMPLATE/#{Nifty::Backends::Opennebula::VMCATCHER_APPLIANCE_ID}"] } end def images_by_version(version, users = []) reload! return pool.find_all { |image| image["TEMPLATE/#{ATTRIBUTE_APPLIANCE_VERSION}"] == version && users.include?(image['UNAME']) } unless users.blank? pool.find_all { |image| image["TEMPLATE/#{ATTRIBUTE_APPLIANCE_VERSION}"] == version } end # Returns image with given id if exists # # @param [Fixnum] id # @return [OpenNebula::Image] image with given id def image_exist?(id) reload! pool.find { |image| image.id == id } end # Deletes image # # @param [OpenNebula::Image] image # @raise [Nifty::Errors::ApiCallTimeoutError] if image isn't deleted within a timeout def delete_image(image) id = image.id if image.state_str == IMAGE_STATE_USED logger.warn("Image with id #{id.inspect} cannot be removed, still in use") return end Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.info! } logger.debug("Deleting image with id #{id.inspect}") Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.delete } Timeout::timeout(Nifty::Backends::Utils::Opennebula::Handler.api_call_timeout) do while(image_exist?(id)) sleep(Nifty::Backends::Utils::Opennebula::Handler::API_POLLING_WAIT) end end rescue Timeout::Error fail Nifty::Errors::ApiCallTimeoutError, "Image with id #{id.inspect} was not deleted within timeout" end # Disables image # # @param [OpenNebula::Image] image # @raise [Nifty::Errors::ApiCallTimeoutError] if image isn't disabled within a timeout def disable_image(image) image_state = image.state_str id = image.id if image_state == IMAGE_STATE_DISABLED logger.debug("Image with id #{id.inspect} is already disabled, skipping") return end unless image_state == IMAGE_STATE_READY || image_state == IMAGE_STATE_ERROR logger.warn("Image with id #{id.inspect} cannot be disabled") return end Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.disable } Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.info! } Timeout::timeout(Nifty::Backends::Utils::Opennebula::Handler.api_call_timeout) do until(image.state_str == IMAGE_STATE_DISABLED) sleep(Nifty::Backends::Utils::Opennebula::Handler::API_POLLING_WAIT) Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.info! } end end end # Expires image # Renames image and add attribute NIFTY_EXPIRATION to image with timestamp as a value # # @param [OpenNebula::Image] image def expire_image(image) Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.info! } id = image.id if image["TEMPLATE/#{ATTRIBUTE_EXPIRATION}"] logger.debug("Image with id #{id.inspect} is already expired, skipping") return end disable_image(image) logger.debug("Expiring image with id #{id.inspect}") expiration_time = Time.now.strftime(TIME_FORMAT) expiration_attribute = "#{ATTRIBUTE_EXPIRATION} = \"#{expiration_time}\"" Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.rename("EXPIRED_#{expiration_time}_#{image.name}") } Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.update(expiration_attribute, true) } end # Outdates image # Adds atribute marking the image is outdated # # @param [OpenNebula::Image] image def outdate_image(image) Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.info! } id = image.id if image["TEMPLATE/#{ATTRIBUTE_OUTDATED}"] logger.debug("Image with id #{id.inspect} is already outdated, skipping") return end logger.debug("Outdating image with id #{id.inspect}") outdate_time = Time.now.strftime(TIME_FORMAT) outdate_attribute = "#{ATTRIBUTE_OUTDATED} = \"#{outdate_time}\"" Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.update(outdate_attribute, true) } end # Registers a new image # # @param [String] template template for image # @param [OpenNebula::Datastore] datastore datastore to register image to # @raise [Nifty::Errors::ApiCallTimeoutError] if image isn't ready within a timeout def register_image(template, datastore) image_alloc = ::OpenNebula::Image.build_xml image = ::OpenNebula::Image.new(image_alloc, client) logger.debug("Registering image with template\n#{template}") Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.allocate(template, datastore.id) } begin Timeout::timeout(Nifty::Backends::Utils::Opennebula::Handler.api_call_timeout) do Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.info! } until(image.state_str == IMAGE_STATE_READY) sleep(Nifty::Backends::Utils::Opennebula::Handler::API_POLLING_WAIT) Nifty::Backends::Utils::Opennebula::Helper.handle_opennebula_error { image.info! } end end image rescue Timeout::Error image.delete fail Nifty::Errors::ApiCallTimeoutError, "Image with id #{image.id.inspect} didn't become ready within timeout" end end # Prepares a template for an image # # @param [String] template_dir directory with templates # @param [Hash] data used while populating a template # @return [String] final template for an image def self.prepare_template(template_dir, data) template_location = File.join(template_dir, "image.erb") fail Nifty::Errors::ArgumentError, "Missing file 'image.erb' in template directory '#{template_dir}'" unless File.exist?(template_location) template = Tilt::ERBTemplate.new(template_location) template_content = template.render(Object.new, data) template = Tilt::ERBTemplate.new(File.join(Nifty::GEM_DIR, 'templates', 'image.erb')) nifty_template_content = template.render(Object.new, data) whole_template_content = template_content + nifty_template_content logger.debug "Populated image template:\n#{whole_template_content}" whole_template_content end end