aeolusproject/conductor

View on GitHub
src/app/models/deployable.rb

Summary

Maintainability
B
4 hrs
Test Coverage
#
#   Copyright 2011 Red Hat, Inc.
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

require 'util/conductor'

class Deployable < ActiveRecord::Base
  class << self
    include CommonFilterMethods
  end


  include Alberich::PermissionedObject

  validates_presence_of :name
  validates_uniqueness_of :name
  validates_length_of :name, :maximum => 1024

  validates_presence_of :xml
  validate :validate_deployable_xml, :if => Proc.new {|deployable|
                                              !deployable.xml.blank? }

  has_many :catalog_entries, :dependent => :destroy
  has_many :catalogs, :through => :catalog_entries
  belongs_to :pool_family

  belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
  after_create "assign_owner_roles(owner)"
  before_create :set_pool_family

  scope :without_catalog, lambda {
    deployable_ids_in_association = CatalogEntry.select(:deployable_id).map(&:deployable_id)
    where('id NOT IN (?)', deployable_ids_in_association)
  }

  PRESET_FILTERS_OPTIONS = []

  def perm_ancestors
    super + catalogs + catalogs.collect{|c| c.pool}.uniq + [pool_family]
  end

  def validate_deployable_xml
    begin
      deployable_xml = DeployableXML.new(xml)
      if !deployable_xml.validate!
        errors.add(:xml, _('file contains syntax errors'))
      elsif !deployable_xml.unique_assembly_names?
        errors.add(:xml, _('must contain unique Assembly names'))
      end
      validate_cycles_in_deployable_xml(deployable_xml)
      validate_references_in_deployable_xml(deployable_xml)
    rescue Nokogiri::XML::SyntaxError => e
      errors.add(:base, _('seems to be not valid Deployable XML: %s') % "#{e.message}")
    end
  end

  def validate_cycles_in_deployable_xml(deployable_xml)
    deployable_xml.dependency_graph.cycles.each do |cycle|
      cycle_str = cycle.map do |node|
        str = node[:assembly].to_s
        str << "[#{node[:service]}]" if node[:service].present?
        str
      end.join(' -> ')
      errors.add(:xml,
                 _('Contains cyclic reference between following Assemblies or services: %s') % cycle_str)
    end
  end

  def validate_references_in_deployable_xml(deployable_xml)
    deployable_xml.dependency_graph.not_existing_references.each do |reference|
      locale_params = {:from_assembly => reference[:assembly],
                       :from_service => reference[:service],
                       :from_param => reference[:reference][:from_param],
                       :to_param => reference[:reference][:param],
                       :to_assembly => reference[:reference][:assembly],
                       :to_service => reference[:reference][:service]}
      if reference[:no_return_param]
        errors.add(:xml,
                   _('Assembly %{from_assembly}, service %{from_service}, parameter %{from_param} references param %{to_param} which is not returned by Assembly %{to_assembly}') % locale_params)
      elsif reference[:reference][:service]
        errors.add(:xml,
                   _('Assembly %{from_assembly}, service %{from_service}, parameter %{from_param} references not existing Assembly %{to_assembly}, service %{to_service}') % locale_params)
      else
        errors.add(:xml,
                   _('Assembly %{from_assembly}, service %{from_service}, parameter %{from_param} references not existing Assembly %{to_assembly}') % locale_params)
      end
    end
  end

  def deployable_xml
    DeployableXML.new(xml)
  end

  def fetch_unique_images
    fetched_xml = deployable_xml
    return [] if fetched_xml.nil?

    fetched_xml.image_uuids.uniq.map do |image_uuid|
      Tim::BaseImage.find_by_uuid(image_uuid)
    end
  end

  #method used with uploading deployable xml in catalog_entries#new
  def xml=(data)
    #for new
    if data.is_a?(ActionDispatch::Http::UploadedFile)
      write_attribute :xml_filename, data.original_filename
      write_attribute :xml, data.tempfile.read
    #for update or new from_url
    else
      write_attribute :xml, data
    end
  end

  def set_from_image(image_id, name, hw_profile)
    image = Tim::BaseImage.find(image_id)
    doc = Nokogiri::XML ''
    doc.root = doc.create_element('deployable', :version => DeployableXML.version, :name => name)
    description = doc.create_element('description')
    doc.root << description
    assemblies = doc.create_element('assemblies')
    doc.root << assemblies
    assembly = doc.create_element('assembly', :name => image.name.gsub(/[^a-zA-Z0-9]+/, '-'), :hwp => hw_profile.name)
    assemblies << assembly
    img = doc.create_element('image', :id => image.uuid)
    assembly << img

    self.description = ''
    self.xml = doc.to_s
    self.xml_filename = name
  end

  #get details of image for deployable#show
  def get_image_details
    deployable_xml = DeployableXML.new(xml)
    uuids = deployable_xml.image_uuids
    images = []
    missing_images = []
    assemblies_array ||= []
    deployable_errors ||= []
    deployable_xml.assemblies.each do |assembly|
      assembly_hash = {}

      begin
        image = Tim::BaseImage.find_by_uuid(assembly.image_id)
      rescue Exception => e
        error = humanize_error(e.message)
      end

      if !error.nil?
        missing_images << assembly.image_id
        deployable_errors << error
      elsif image.nil?
        missing_images << assembly.image_id
        deployable_errors << _('%s: Image (UUID: %s) doesn\'t exist') % [assembly.name, assembly.image_id]
      else
        if image.pool_family != pool_family
          deployable_errors << _('The Deployable \'%s\' contains an image (UUID %s) in the wrong Environment (\'%s\' should be \'%s\') and cannot be used.') % [name, assembly.image_id, image.pool_family.name, pool_family.name]
        end
        images << image
        assembly_hash[:build_and_target_uuids] = get_build_and_target_uuids(image)
      end
      assembly_hash[:name] = assembly.name
      assembly_hash[:image_uuid] = assembly.image_id
      assembly_hash[:images_count] = assembly.images_count
      if assembly.hwp
        hwp = HardwareProfile.find_by_name(assembly.hwp)
        if hwp
          assembly_hash[:hwp_name] = hwp.name
          assembly_hash[:hwp_hdd] = hwp.storage.value
          assembly_hash[:hwp_ram] = hwp.memory.value
          assembly_hash[:hwp_arch] = hwp.architecture.value
        else
          deployable_errors << "#{assembly_hash[:name]}: " + _('Hardware Profile %s, which is specified in XML, does not exist.') % assembly.hwp
        end
      else
        deployable_errors << "#{assembly_hash[:name]}: " + _('Some attribute(s) are missing in the XML. Please check the file.')
      end
      assemblies_array << assembly_hash
      # TODO: check_audrey_api_compatibility uses icicle object which is not
      # supported in Tim yet
      #audrey_error = check_audrey_api_compatibility(image, assembly)
      audrey_error = nil
      deployable_errors << "#{assembly_hash[:name]}: " + audrey_error if not audrey_error.nil?
    end
    [assemblies_array, images, missing_images, deployable_errors]
  end

  def build_status(images, account)
    begin
      pimgs = images.map { |i| i.last_provider_image(account) }
      return :pushing if pimgs.any? { |pimg| pimg && pimg.pushing? }
      return :pushed if pimgs.all? { |pimg| pimg && pimg.pushed? }
      :not_pushed
    rescue Exception => e
      error = humanize_error(e.message)
      return error
    end
  end

  def to_polymorphic_path_param(polymorphic_path_extras)
    catalog = catalogs.find(polymorphic_path_extras[:catalog_id]) if (polymorphic_path_extras.present? &&
        polymorphic_path_extras.has_key?(:catalog_id))
    [catalog, self]
  end

  def get_build_and_target_uuids(image)
    latest_build = image.last_built_image_version
    [image.uuid,
     (latest_build ? latest_build.uuid : nil),
     (latest_build ? latest_build.target_images.collect { |ti| ti.factory_id} : nil)]
  end

  def set_pool_family
    self[:pool_family_id] = catalogs.first.pool_family_id
  end

  def check_service_params_types
    warnings = []
    deployable_xml = DeployableXML.new(xml)
    deployable_xml.assemblies.each do |assembly|
      assembly.services.each do |service|
        service.parameters.each do |param|
          if param.type_warning
            warnings << I18n.translate("deployables.flash.warning.param_type_attr",
                                 :service_name => service.name,
                                 :param_name => param.name)
          end
        end
      end
    end
    warnings
  end

  #def check_audrey_api_compatibility(image, assembly)
  #  # get icicle for agent
  #  icicle_uuid = image.latest_pushed_or_unpushed_build.target_images.first.icicle rescue nil
  #  icicle = Aeolus::Image::Warehouse::Icicle.find(icicle_uuid) if icicle_uuid
  #  agent_v = icicle ? icicle.packages.find_all { |p| p =~ /aeolus-audrey-agent(.*)/ } : ""
  #  agent_v = agent_v.present? ? agent_v.first.split('-')[3] : ""

  #  # calculate audrey api version
  #  audrey_api_v = if agent_v >= "0.5.0"
  #                    1..2
  #                  elsif agent_v >= "0.4.0"
  #                    1..1
  #                  else 0
  #                  end

  #  # initalize compatibility
  #  audrey_api_compat = 1

  #  # do cs_compat
  #  ## All agents are compatible with all Config Servers right now
  #  ## so no need to check this right now
  #  ## this check should call the cs passing the agent_v and validating
  #  ## the response the CS sends back

  #  # audrey api version 2 added service references, lets check for any
  #  assembly.services.each do |service|
  #    service.parameters.each do |param|
  #      audrey_api_compat = 2 if param.reference_service
  #    end
  #  end

  #  if audrey_api_v != 0
  #    audrey_api_v.include?(audrey_api_compat) ? nil : _('The Deployable uses features not supported by the Audrey agent installed in the latest built image.')
  #  end
  #end

  private

  def self.apply_search_filter(search)
    if search
      where("lower(name) LIKE :search", :search => "%#{search.downcase}%")
    else
      scoped
    end
  end

end